This is page 14 of 140. Use http://codebase.md/xmlui-org/xmlui?lines=false&page={x} to view the full context. # Directory Structure ``` ├── .changeset │ ├── config.json │ └── cool-queens-look.md ├── .eslintrc.cjs ├── .github │ ├── build-checklist.png │ ├── ISSUE_TEMPLATE │ │ ├── bug_report.md │ │ └── feature_request.md │ └── workflows │ ├── deploy-blog.yml │ ├── deploy-docs-optimized.yml │ ├── deploy-docs.yml │ ├── prepare-versions.yml │ ├── release-packages.yml │ ├── run-all-tests.yml │ └── run-smoke-tests.yml ├── .gitignore ├── .prettierrc.js ├── .vscode │ ├── launch.json │ └── settings.json ├── blog │ ├── .gitignore │ ├── .gitkeep │ ├── CHANGELOG.md │ ├── extensions.ts │ ├── index.html │ ├── index.ts │ ├── package.json │ ├── public │ │ ├── blog │ │ │ ├── images │ │ │ │ ├── blog-page-component.png │ │ │ │ ├── blog-scrabble.png │ │ │ │ ├── integrated-blog-search.png │ │ │ │ └── lorem-ipsum.png │ │ │ ├── lorem-ipsum.md │ │ │ ├── newest-post.md │ │ │ ├── older-post.md │ │ │ └── welcome-to-the-xmlui-blog.md │ │ ├── mockServiceWorker.js │ │ ├── netlify.toml │ │ ├── resources │ │ │ ├── favicon.ico │ │ │ ├── files │ │ │ │ └── for-download │ │ │ │ └── xmlui │ │ │ │ └── xmlui-standalone.umd.js │ │ │ ├── github.svg │ │ │ ├── llms.txt │ │ │ ├── logo-dark.svg │ │ │ ├── logo.svg │ │ │ ├── pg-popout.svg │ │ │ └── xmlui-logo.svg │ │ ├── serve.json │ │ └── web.config │ ├── scripts │ │ ├── download-latest-xmlui.js │ │ ├── generate-rss.js │ │ ├── get-releases.js │ │ └── utils.js │ ├── src │ │ ├── components │ │ │ ├── BlogOverview.xmlui │ │ │ ├── BlogPage.xmlui │ │ │ ├── Debug.xmlui │ │ │ └── PageNotFound.xmlui │ │ ├── config.ts │ │ ├── Main.xmlui │ │ ├── Main.xmlui.xs │ │ └── themes │ │ ├── docs-theme.ts │ │ ├── earthtone.ts │ │ ├── xmlui-gray-on-default.ts │ │ ├── xmlui-green-on-default.ts │ │ └── xmlui-orange-on-default.ts │ └── tsconfig.json ├── CONTRIBUTING.md ├── docs │ ├── .gitignore │ ├── CHANGELOG.md │ ├── ComponentRefLinks.txt │ ├── content │ │ ├── _meta.json │ │ ├── components │ │ │ ├── _meta.json │ │ │ ├── _overview.md │ │ │ ├── APICall.md │ │ │ ├── App.md │ │ │ ├── AppHeader.md │ │ │ ├── AppState.md │ │ │ ├── AutoComplete.md │ │ │ ├── Avatar.md │ │ │ ├── Backdrop.md │ │ │ ├── Badge.md │ │ │ ├── BarChart.md │ │ │ ├── Bookmark.md │ │ │ ├── Breakout.md │ │ │ ├── Button.md │ │ │ ├── Card.md │ │ │ ├── Carousel.md │ │ │ ├── ChangeListener.md │ │ │ ├── Checkbox.md │ │ │ ├── CHStack.md │ │ │ ├── ColorPicker.md │ │ │ ├── Column.md │ │ │ ├── ContentSeparator.md │ │ │ ├── CVStack.md │ │ │ ├── DataSource.md │ │ │ ├── DateInput.md │ │ │ ├── DatePicker.md │ │ │ ├── DonutChart.md │ │ │ ├── DropdownMenu.md │ │ │ ├── EmojiSelector.md │ │ │ ├── ExpandableItem.md │ │ │ ├── FileInput.md │ │ │ ├── FileUploadDropZone.md │ │ │ ├── FlowLayout.md │ │ │ ├── Footer.md │ │ │ ├── Form.md │ │ │ ├── FormItem.md │ │ │ ├── FormSection.md │ │ │ ├── Fragment.md │ │ │ ├── H1.md │ │ │ ├── H2.md │ │ │ ├── H3.md │ │ │ ├── H4.md │ │ │ ├── H5.md │ │ │ ├── H6.md │ │ │ ├── Heading.md │ │ │ ├── HSplitter.md │ │ │ ├── HStack.md │ │ │ ├── Icon.md │ │ │ ├── IFrame.md │ │ │ ├── Image.md │ │ │ ├── Items.md │ │ │ ├── LabelList.md │ │ │ ├── Legend.md │ │ │ ├── LineChart.md │ │ │ ├── Link.md │ │ │ ├── List.md │ │ │ ├── Logo.md │ │ │ ├── Markdown.md │ │ │ ├── MenuItem.md │ │ │ ├── MenuSeparator.md │ │ │ ├── ModalDialog.md │ │ │ ├── NavGroup.md │ │ │ ├── NavLink.md │ │ │ ├── NavPanel.md │ │ │ ├── NoResult.md │ │ │ ├── NumberBox.md │ │ │ ├── Option.md │ │ │ ├── Page.md │ │ │ ├── PageMetaTitle.md │ │ │ ├── Pages.md │ │ │ ├── Pagination.md │ │ │ ├── PasswordInput.md │ │ │ ├── PieChart.md │ │ │ ├── ProgressBar.md │ │ │ ├── Queue.md │ │ │ ├── RadioGroup.md │ │ │ ├── RealTimeAdapter.md │ │ │ ├── Redirect.md │ │ │ ├── Select.md │ │ │ ├── Slider.md │ │ │ ├── Slot.md │ │ │ ├── SpaceFiller.md │ │ │ ├── Spinner.md │ │ │ ├── Splitter.md │ │ │ ├── Stack.md │ │ │ ├── StickyBox.md │ │ │ ├── SubMenuItem.md │ │ │ ├── Switch.md │ │ │ ├── TabItem.md │ │ │ ├── Table.md │ │ │ ├── TableOfContents.md │ │ │ ├── Tabs.md │ │ │ ├── Text.md │ │ │ ├── TextArea.md │ │ │ ├── TextBox.md │ │ │ ├── Theme.md │ │ │ ├── TimeInput.md │ │ │ ├── Timer.md │ │ │ ├── ToneChangerButton.md │ │ │ ├── ToneSwitch.md │ │ │ ├── Tooltip.md │ │ │ ├── Tree.md │ │ │ ├── VSplitter.md │ │ │ ├── VStack.md │ │ │ ├── xmlui-animations │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ ├── Animation.md │ │ │ │ ├── FadeAnimation.md │ │ │ │ ├── FadeInAnimation.md │ │ │ │ ├── FadeOutAnimation.md │ │ │ │ ├── ScaleAnimation.md │ │ │ │ └── SlideInAnimation.md │ │ │ ├── xmlui-pdf │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ └── Pdf.md │ │ │ ├── xmlui-spreadsheet │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ └── Spreadsheet.md │ │ │ └── xmlui-website-blocks │ │ │ ├── _meta.json │ │ │ ├── _overview.md │ │ │ ├── Carousel.md │ │ │ ├── HelloMd.md │ │ │ ├── HeroSection.md │ │ │ └── ScrollToTop.md │ │ └── extensions │ │ ├── _meta.json │ │ ├── xmlui-animations │ │ │ ├── _meta.json │ │ │ ├── _overview.md │ │ │ ├── Animation.md │ │ │ ├── FadeAnimation.md │ │ │ ├── FadeInAnimation.md │ │ │ ├── FadeOutAnimation.md │ │ │ ├── ScaleAnimation.md │ │ │ └── SlideInAnimation.md │ │ └── xmlui-website-blocks │ │ ├── _meta.json │ │ ├── _overview.md │ │ ├── Carousel.md │ │ ├── HelloMd.md │ │ ├── HeroSection.md │ │ └── ScrollToTop.md │ ├── extensions.ts │ ├── index.html │ ├── index.ts │ ├── package.json │ ├── public │ │ ├── feed.rss │ │ ├── mockServiceWorker.js │ │ ├── pages │ │ │ ├── _meta.json │ │ │ ├── app-structure.md │ │ │ ├── build-editor-component.md │ │ │ ├── build-hello-world-component.md │ │ │ ├── components-intro.md │ │ │ ├── context-variables.md │ │ │ ├── forms.md │ │ │ ├── globals.md │ │ │ ├── glossary.md │ │ │ ├── helper-tags.md │ │ │ ├── hosted-deployment.md │ │ │ ├── howto │ │ │ │ ├── assign-a-complex-json-literal-to-a-component-variable.md │ │ │ │ ├── chain-a-refetch.md │ │ │ │ ├── debug-a-component.md │ │ │ │ ├── delay-a-datasource-until-another-datasource-is-ready.md │ │ │ │ ├── delegate-a-method.md │ │ │ │ ├── do-custom-form-validation.md │ │ │ │ ├── expose-a-method-from-a-component.md │ │ │ │ ├── filter-and-transform-data-from-an-api.md │ │ │ │ ├── group-items-in-list-by-a-property.md │ │ │ │ ├── handle-background-operations.md │ │ │ │ ├── hide-an-element-until-its-datasource-is-ready.md │ │ │ │ ├── make-a-set-of-equal-width-cards.md │ │ │ │ ├── make-a-table-responsive.md │ │ │ │ ├── modify-a-value-reported-in-a-column.md │ │ │ │ ├── paginate-a-list.md │ │ │ │ ├── pass-data-to-a-modal-dialog.md │ │ │ │ ├── react-to-button-click-not-keystrokes.md │ │ │ │ ├── set-the-initial-value-of-a-select-from-fetched-data.md │ │ │ │ ├── share-a-modaldialog-across-components.md │ │ │ │ ├── sync-selections-between-table-and-list-views.md │ │ │ │ ├── update-ui-optimistically.md │ │ │ │ ├── use-built-in-form-validation.md │ │ │ │ └── use-the-same-modaldialog-to-add-or-edit.md │ │ │ ├── howto.md │ │ │ ├── intro.md │ │ │ ├── layout.md │ │ │ ├── markup.md │ │ │ ├── mcp.md │ │ │ ├── modal-dialogs.md │ │ │ ├── news-and-reviews.md │ │ │ ├── reactive-intro.md │ │ │ ├── refactoring.md │ │ │ ├── routing-and-links.md │ │ │ ├── samples │ │ │ │ ├── color-palette.xmlui │ │ │ │ ├── color-values.xmlui │ │ │ │ ├── shadow-sizes.xmlui │ │ │ │ ├── spacing-sizes.xmlui │ │ │ │ ├── swatch.xmlui │ │ │ │ ├── theme-gallery-brief.xmlui │ │ │ │ └── theme-gallery.xmlui │ │ │ ├── scoping.md │ │ │ ├── scripting.md │ │ │ ├── styles-and-themes │ │ │ │ ├── common-units.md │ │ │ │ ├── layout-props.md │ │ │ │ ├── theme-variable-defaults.md │ │ │ │ ├── theme-variables.md │ │ │ │ └── themes.md │ │ │ ├── template-properties.md │ │ │ ├── test.md │ │ │ ├── tutorial-01.md │ │ │ ├── tutorial-02.md │ │ │ ├── tutorial-03.md │ │ │ ├── tutorial-04.md │ │ │ ├── tutorial-05.md │ │ │ ├── tutorial-06.md │ │ │ ├── tutorial-07.md │ │ │ ├── tutorial-08.md │ │ │ ├── tutorial-09.md │ │ │ ├── tutorial-10.md │ │ │ ├── tutorial-11.md │ │ │ ├── tutorial-12.md │ │ │ ├── universal-properties.md │ │ │ ├── user-defined-components.md │ │ │ ├── vscode.md │ │ │ ├── working-with-markdown.md │ │ │ ├── working-with-text.md │ │ │ ├── xmlui-animations │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ ├── Animation.md │ │ │ │ ├── FadeAnimation.md │ │ │ │ ├── FadeInAnimation.md │ │ │ │ ├── FadeOutAnimation.md │ │ │ │ ├── ScaleAnimation.md │ │ │ │ └── SlideInAnimation.md │ │ │ ├── xmlui-charts │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ ├── BarChart.md │ │ │ │ ├── DonutChart.md │ │ │ │ ├── LabelList.md │ │ │ │ ├── Legend.md │ │ │ │ ├── LineChart.md │ │ │ │ └── PieChart.md │ │ │ ├── xmlui-pdf │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ └── Pdf.md │ │ │ └── xmlui-spreadsheet │ │ │ ├── _meta.json │ │ │ ├── _overview.md │ │ │ └── Spreadsheet.md │ │ ├── resources │ │ │ ├── devdocs │ │ │ │ ├── debug-proxy-object-2.png │ │ │ │ ├── debug-proxy-object.png │ │ │ │ ├── table_editor_01.png │ │ │ │ ├── table_editor_02.png │ │ │ │ ├── table_editor_03.png │ │ │ │ ├── table_editor_04.png │ │ │ │ ├── table_editor_05.png │ │ │ │ ├── table_editor_06.png │ │ │ │ ├── table_editor_07.png │ │ │ │ ├── table_editor_08.png │ │ │ │ ├── table_editor_09.png │ │ │ │ ├── table_editor_10.png │ │ │ │ ├── table_editor_11.png │ │ │ │ ├── table-editor-01.png │ │ │ │ ├── table-editor-02.png │ │ │ │ ├── table-editor-03.png │ │ │ │ ├── table-editor-04.png │ │ │ │ ├── table-editor-06.png │ │ │ │ ├── table-editor-07.png │ │ │ │ ├── table-editor-08.png │ │ │ │ ├── table-editor-09.png │ │ │ │ └── xmlui-rendering-of-tiptap-markdown.png │ │ │ ├── favicon.ico │ │ │ ├── files │ │ │ │ ├── clients.json │ │ │ │ ├── daily-revenue.json │ │ │ │ ├── dashboard-stats.json │ │ │ │ ├── demo.xmlui │ │ │ │ ├── demo.xmlui.xs │ │ │ │ ├── downloads │ │ │ │ │ └── downloads.json │ │ │ │ ├── for-download │ │ │ │ │ ├── index-with-api.html │ │ │ │ │ ├── index.html │ │ │ │ │ ├── mockApi.js │ │ │ │ │ ├── start-darwin.sh │ │ │ │ │ ├── start-linux.sh │ │ │ │ │ ├── start.bat │ │ │ │ │ └── xmlui │ │ │ │ │ └── xmlui-standalone.umd.js │ │ │ │ ├── getting-started │ │ │ │ │ ├── cl-tutorial-final.zip │ │ │ │ │ ├── cl-tutorial.zip │ │ │ │ │ ├── cl-tutorial2.zip │ │ │ │ │ ├── cl-tutorial3.zip │ │ │ │ │ ├── cl-tutorial4.zip │ │ │ │ │ ├── cl-tutorial5.zip │ │ │ │ │ ├── cl-tutorial6.zip │ │ │ │ │ ├── getting-started.zip │ │ │ │ │ ├── hello-xmlui.zip │ │ │ │ │ ├── xmlui-empty.zip │ │ │ │ │ └── xmlui-starter.zip │ │ │ │ ├── howto │ │ │ │ │ └── component-icons │ │ │ │ │ └── up-arrow.svg │ │ │ │ ├── invoices.json │ │ │ │ ├── monthly-status.json │ │ │ │ ├── news-and-reviews.json │ │ │ │ ├── products.json │ │ │ │ ├── releases.json │ │ │ │ ├── tutorials │ │ │ │ │ ├── datasource │ │ │ │ │ │ └── api.ts │ │ │ │ │ └── p2do │ │ │ │ │ ├── api.ts │ │ │ │ │ └── todo-logo.svg │ │ │ │ └── xmlui.json │ │ │ ├── github.svg │ │ │ ├── images │ │ │ │ ├── apiaction-tutorial │ │ │ │ │ ├── add-success.png │ │ │ │ │ ├── apiaction-param.png │ │ │ │ │ ├── change-completed.png │ │ │ │ │ ├── change-in-progress.png │ │ │ │ │ ├── confirm-delete.png │ │ │ │ │ ├── data-error.png │ │ │ │ │ ├── data-progress.png │ │ │ │ │ ├── data-success.png │ │ │ │ │ ├── display-1.png │ │ │ │ │ ├── item-deleted.png │ │ │ │ │ ├── item-updated.png │ │ │ │ │ ├── missing-api-key.png │ │ │ │ │ ├── new-item-added.png │ │ │ │ │ └── test-message.png │ │ │ │ ├── chat-api │ │ │ │ │ └── domain-model.svg │ │ │ │ ├── components │ │ │ │ │ ├── image │ │ │ │ │ │ └── breakfast.jpg │ │ │ │ │ ├── markdown │ │ │ │ │ │ └── colors.png │ │ │ │ │ └── modal │ │ │ │ │ ├── deep_link_dialog_1.jpg │ │ │ │ │ └── deep_link_dialog_2.jpg │ │ │ │ ├── create-apps │ │ │ │ │ ├── collapsed-vertical.png │ │ │ │ │ ├── using-forms-warning-dialog.png │ │ │ │ │ └── using-forms.png │ │ │ │ ├── datasource-tutorial │ │ │ │ │ ├── data-with-header.png │ │ │ │ │ ├── filtered-data.png │ │ │ │ │ ├── filtered-items.png │ │ │ │ │ ├── initial-page-items.png │ │ │ │ │ ├── list-items.png │ │ │ │ │ ├── next-page-items.png │ │ │ │ │ ├── no-data.png │ │ │ │ │ ├── pagination-1.jpg │ │ │ │ │ ├── pagination-1.png │ │ │ │ │ ├── polling-1.png │ │ │ │ │ ├── refetch-data.png │ │ │ │ │ ├── slow-loading.png │ │ │ │ │ ├── test-message.png │ │ │ │ │ ├── Thumbs.db │ │ │ │ │ ├── unconventional-data.png │ │ │ │ │ └── unfiltered-items.png │ │ │ │ ├── flower.jpg │ │ │ │ ├── get-started │ │ │ │ │ ├── add-new-contact.png │ │ │ │ │ ├── app-modified.png │ │ │ │ │ ├── app-start.png │ │ │ │ │ ├── app-with-boxes.png │ │ │ │ │ ├── app-with-toast.png │ │ │ │ │ ├── boilerplate-structure.png │ │ │ │ │ ├── cl-initial.png │ │ │ │ │ ├── cl-start.png │ │ │ │ │ ├── contact-counts.png │ │ │ │ │ ├── contact-dialog-title.png │ │ │ │ │ ├── contact-dialog.png │ │ │ │ │ ├── contact-menus.png │ │ │ │ │ ├── contact-predicates.png │ │ │ │ │ ├── context-menu.png │ │ │ │ │ ├── dashboard-numbers.png │ │ │ │ │ ├── default-contact-list.png │ │ │ │ │ ├── delete-contact.png │ │ │ │ │ ├── delete-task.png │ │ │ │ │ ├── detailed-template.png │ │ │ │ │ ├── edit-contact-details.png │ │ │ │ │ ├── edited-contact-saved.png │ │ │ │ │ ├── empty-sections.png │ │ │ │ │ ├── filter-completed.png │ │ │ │ │ ├── fullwidth-desktop.png │ │ │ │ │ ├── fullwidth-mobile.png │ │ │ │ │ ├── initial-table.png │ │ │ │ │ ├── items-and-badges.png │ │ │ │ │ ├── loading-message.png │ │ │ │ │ ├── new-contact-button.png │ │ │ │ │ ├── new-contact-saved.png │ │ │ │ │ ├── no-empty-sections.png │ │ │ │ │ ├── personal-todo-initial.png │ │ │ │ │ ├── piechart.png │ │ │ │ │ ├── review-today.png │ │ │ │ │ ├── rudimentary-dashboard.png │ │ │ │ │ ├── section-collapsed.png │ │ │ │ │ ├── sectioned-items.png │ │ │ │ │ ├── sections-ordered.png │ │ │ │ │ ├── spacex-list-with-links.png │ │ │ │ │ ├── spacex-list.png │ │ │ │ │ ├── start-personal-todo-1.png │ │ │ │ │ ├── submit-new-contact.png │ │ │ │ │ ├── submit-new-task.png │ │ │ │ │ ├── syntax-highlighting.png │ │ │ │ │ ├── table-with-badge.png │ │ │ │ │ ├── template-with-card.png │ │ │ │ │ ├── test-emulated-api.png │ │ │ │ │ ├── Thumbs.db │ │ │ │ │ ├── todo-logo.png │ │ │ │ │ └── xmlui-tools.png │ │ │ │ ├── HelloApp.png │ │ │ │ ├── HelloApp2.png │ │ │ │ ├── logos │ │ │ │ │ ├── xmlui1.svg │ │ │ │ │ ├── xmlui2.svg │ │ │ │ │ ├── xmlui3.svg │ │ │ │ │ ├── xmlui4.svg │ │ │ │ │ ├── xmlui5.svg │ │ │ │ │ ├── xmlui6.svg │ │ │ │ │ └── xmlui7.svg │ │ │ │ ├── pdf │ │ │ │ │ └── dummy-pdf.jpg │ │ │ │ ├── rendering-engine │ │ │ │ │ ├── AppEngine-flow.svg │ │ │ │ │ ├── Component.svg │ │ │ │ │ ├── CompoundComponent.svg │ │ │ │ │ ├── RootComponent.svg │ │ │ │ │ └── tree-with-containers.svg │ │ │ │ ├── reviewers-guide │ │ │ │ │ ├── AppEngine-flow.svg │ │ │ │ │ └── incbutton-in-action.png │ │ │ │ ├── tools │ │ │ │ │ └── boilerplate-structure.png │ │ │ │ ├── try.svg │ │ │ │ ├── tutorial │ │ │ │ │ ├── app-chat-history.png │ │ │ │ │ ├── app-content-placeholder.png │ │ │ │ │ ├── app-header-and-content.png │ │ │ │ │ ├── app-links-channel-selected.png │ │ │ │ │ ├── app-links-click.png │ │ │ │ │ ├── app-navigation.png │ │ │ │ │ ├── finished-ex01.png │ │ │ │ │ ├── finished-ex02.png │ │ │ │ │ ├── hello.png │ │ │ │ │ ├── splash-screen-advanced.png │ │ │ │ │ ├── splash-screen-after-click.png │ │ │ │ │ ├── splash-screen-centered.png │ │ │ │ │ ├── splash-screen-events.png │ │ │ │ │ ├── splash-screen-expression.png │ │ │ │ │ ├── splash-screen-reuse-after.png │ │ │ │ │ ├── splash-screen-reuse-before.png │ │ │ │ │ └── splash-screen.png │ │ │ │ └── tutorial-01.png │ │ │ ├── llms.txt │ │ │ ├── logo-dark.svg │ │ │ ├── logo.svg │ │ │ ├── pg-popout.svg │ │ │ └── xmlui-logo.svg │ │ ├── serve.json │ │ └── web.config │ ├── scripts │ │ ├── download-latest-xmlui.js │ │ ├── generate-rss.js │ │ ├── get-releases.js │ │ └── utils.js │ ├── src │ │ ├── components │ │ │ ├── BlogOverview.xmlui │ │ │ ├── BlogPage.xmlui │ │ │ ├── Boxes.xmlui │ │ │ ├── Breadcrumb.xmlui │ │ │ ├── ChangeLog.xmlui │ │ │ ├── ColorPalette.xmlui │ │ │ ├── DocumentLinks.xmlui │ │ │ ├── DocumentPage.xmlui │ │ │ ├── DocumentPageNoTOC.xmlui │ │ │ ├── Icons.xmlui │ │ │ ├── IncButton.xmlui │ │ │ ├── IncButton2.xmlui │ │ │ ├── NameValue.xmlui │ │ │ ├── PageNotFound.xmlui │ │ │ ├── PaletteItem.xmlui │ │ │ ├── Palettes.xmlui │ │ │ ├── SectionHeader.xmlui │ │ │ ├── TBD.xmlui │ │ │ ├── Test.xmlui │ │ │ ├── ThemesIntro.xmlui │ │ │ ├── ThousandThemes.xmlui │ │ │ ├── TubeStops.xmlui │ │ │ ├── TubeStops.xmlui.xs │ │ │ └── TwoColumnCode.xmlui │ │ ├── config.ts │ │ ├── Main.xmlui │ │ └── themes │ │ ├── docs-theme.ts │ │ ├── earthtone.ts │ │ ├── xmlui-gray-on-default.ts │ │ ├── xmlui-green-on-default.ts │ │ └── xmlui-orange-on-default.ts │ └── tsconfig.json ├── LICENSE ├── package-lock.json ├── package.json ├── packages │ ├── xmlui-animations │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── Animation.tsx │ │ │ ├── AnimationNative.tsx │ │ │ ├── FadeAnimation.tsx │ │ │ ├── FadeInAnimation.tsx │ │ │ ├── FadeOutAnimation.tsx │ │ │ ├── index.tsx │ │ │ ├── ScaleAnimation.tsx │ │ │ └── SlideInAnimation.tsx │ │ └── tsconfig.json │ ├── xmlui-devtools │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── devtools │ │ │ │ ├── DevTools.tsx │ │ │ │ ├── DevToolsNative.module.scss │ │ │ │ ├── DevToolsNative.tsx │ │ │ │ ├── ModalDialog.module.scss │ │ │ │ ├── ModalDialog.tsx │ │ │ │ ├── ModalVisibilityContext.tsx │ │ │ │ ├── Tooltip.module.scss │ │ │ │ ├── Tooltip.tsx │ │ │ │ └── utils.ts │ │ │ ├── editor │ │ │ │ └── Editor.tsx │ │ │ └── index.tsx │ │ ├── tsconfig.json │ │ └── vite.config-overrides.ts │ ├── xmlui-hello-world │ │ ├── .gitignore │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── HelloWorld.module.scss │ │ │ ├── HelloWorld.tsx │ │ │ ├── HelloWorldNative.tsx │ │ │ └── index.tsx │ │ └── tsconfig.json │ ├── xmlui-os-frames │ │ ├── .gitignore │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── IPhoneFrame.module.scss │ │ │ ├── IPhoneFrame.tsx │ │ │ ├── MacOSAppFrame.module.scss │ │ │ ├── MacOSAppFrame.tsx │ │ │ ├── WindowsAppFrame.module.scss │ │ │ └── WindowsAppFrame.tsx │ │ └── tsconfig.json │ ├── xmlui-pdf │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ ├── components │ │ │ │ └── Pdf.xmlui │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── LazyPdfNative.tsx │ │ │ ├── Pdf.module.scss │ │ │ └── Pdf.tsx │ │ └── tsconfig.json │ ├── xmlui-playground │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── hooks │ │ │ │ ├── usePlayground.ts │ │ │ │ └── useToast.ts │ │ │ ├── index.tsx │ │ │ ├── playground │ │ │ │ ├── Box.module.scss │ │ │ │ ├── Box.tsx │ │ │ │ ├── CodeSelector.tsx │ │ │ │ ├── ConfirmationDialog.module.scss │ │ │ │ ├── ConfirmationDialog.tsx │ │ │ │ ├── Editor.tsx │ │ │ │ ├── Header.module.scss │ │ │ │ ├── Header.tsx │ │ │ │ ├── Playground.tsx │ │ │ │ ├── PlaygroundContent.module.scss │ │ │ │ ├── PlaygroundContent.tsx │ │ │ │ ├── PlaygroundNative.module.scss │ │ │ │ ├── PlaygroundNative.tsx │ │ │ │ ├── Preview.module.scss │ │ │ │ ├── Preview.tsx │ │ │ │ ├── Select.module.scss │ │ │ │ ├── StandalonePlayground.tsx │ │ │ │ ├── StandalonePlaygroundNative.module.scss │ │ │ │ ├── StandalonePlaygroundNative.tsx │ │ │ │ ├── ThemeSwitcher.module.scss │ │ │ │ ├── ThemeSwitcher.tsx │ │ │ │ ├── ToneSwitcher.tsx │ │ │ │ ├── Tooltip.module.scss │ │ │ │ ├── Tooltip.tsx │ │ │ │ └── utils.ts │ │ │ ├── providers │ │ │ │ ├── Toast.module.scss │ │ │ │ └── ToastProvider.tsx │ │ │ ├── state │ │ │ │ └── store.ts │ │ │ ├── themes │ │ │ │ └── theme.ts │ │ │ └── utils │ │ │ └── helpers.ts │ │ └── tsconfig.json │ ├── xmlui-search │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── Search.module.scss │ │ │ └── Search.tsx │ │ └── tsconfig.json │ ├── xmlui-spreadsheet │ │ ├── .gitignore │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── Spreadsheet.tsx │ │ │ └── SpreadsheetNative.tsx │ │ └── tsconfig.json │ └── xmlui-website-blocks │ ├── .gitignore │ ├── CHANGELOG.md │ ├── demo │ │ ├── components │ │ │ ├── HeroBackgroundBreakoutPage.xmlui │ │ │ ├── HeroBackgroundsPage.xmlui │ │ │ ├── HeroContentsPage.xmlui │ │ │ ├── HeroTextAlignPage.xmlui │ │ │ ├── HeroTextPage.xmlui │ │ │ └── HeroTonesPage.xmlui │ │ ├── Main.xmlui │ │ └── themes │ │ └── default.ts │ ├── index.html │ ├── index.ts │ ├── meta │ │ └── componentsMetadata.ts │ ├── package.json │ ├── public │ │ └── resources │ │ ├── building.jpg │ │ └── xmlui-logo.svg │ ├── src │ │ ├── Carousel │ │ │ ├── Carousel.module.scss │ │ │ ├── Carousel.tsx │ │ │ ├── CarouselContext.tsx │ │ │ └── CarouselNative.tsx │ │ ├── FancyButton │ │ │ ├── FancyButton.module.scss │ │ │ ├── FancyButton.tsx │ │ │ └── FancyButton.xmlui │ │ ├── Hello │ │ │ ├── Hello.tsx │ │ │ ├── Hello.xmlui │ │ │ └── Hello.xmlui.xs │ │ ├── HeroSection │ │ │ ├── HeroSection.module.scss │ │ │ ├── HeroSection.tsx │ │ │ └── HeroSectionNative.tsx │ │ ├── index.tsx │ │ ├── ScrollToTop │ │ │ ├── ScrollToTop.module.scss │ │ │ ├── ScrollToTop.tsx │ │ │ └── ScrollToTopNative.tsx │ │ └── vite-env.d.ts │ └── tsconfig.json ├── README.md ├── tools │ ├── codefence │ │ └── xmlui-code-fence-docs.md │ ├── create-app │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── create-app.ts │ │ ├── helpers │ │ │ ├── copy.ts │ │ │ ├── get-pkg-manager.ts │ │ │ ├── git.ts │ │ │ ├── install.ts │ │ │ ├── is-folder-empty.ts │ │ │ ├── is-writeable.ts │ │ │ ├── make-dir.ts │ │ │ └── validate-pkg.ts │ │ ├── index.ts │ │ ├── package.json │ │ ├── templates │ │ │ ├── default │ │ │ │ └── ts │ │ │ │ ├── gitignore │ │ │ │ ├── index.html │ │ │ │ ├── index.ts │ │ │ │ ├── public │ │ │ │ │ ├── mockServiceWorker.js │ │ │ │ │ ├── resources │ │ │ │ │ │ ├── favicon.ico │ │ │ │ │ │ └── xmlui-logo.svg │ │ │ │ │ └── serve.json │ │ │ │ └── src │ │ │ │ ├── components │ │ │ │ │ ├── ApiAware.xmlui │ │ │ │ │ ├── Home.xmlui │ │ │ │ │ ├── IncButton.xmlui │ │ │ │ │ └── PagePanel.xmlui │ │ │ │ ├── config.ts │ │ │ │ └── Main.xmlui │ │ │ ├── index.ts │ │ │ └── types.ts │ │ └── tsconfig.json │ ├── create-xmlui-hello-world │ │ ├── index.js │ │ └── package.json │ └── vscode │ ├── .gitignore │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ ├── .vscodeignore │ ├── build.sh │ ├── CHANGELOG.md │ ├── esbuild.js │ ├── eslint.config.mjs │ ├── formatter-docs.md │ ├── generate-test-sample.sh │ ├── LICENSE.md │ ├── package-lock.json │ ├── package.json │ ├── README.md │ ├── resources │ │ ├── xmlui-logo.png │ │ └── xmlui-markup-syntax-highlighting.png │ ├── src │ │ ├── extension.ts │ │ └── server.ts │ ├── syntaxes │ │ └── xmlui.tmLanguage.json │ ├── test-samples │ │ └── sample.xmlui │ ├── tsconfig.json │ └── tsconfig.tsbuildinfo ├── turbo.json └── xmlui ├── .gitignore ├── bin │ ├── bootstrap.js │ ├── build-lib.ts │ ├── build.ts │ ├── index.ts │ ├── preview.ts │ ├── start.ts │ ├── vite-xmlui-plugin.ts │ └── viteConfig.ts ├── CHANGELOG.md ├── conventions │ ├── component-qa-checklist.md │ ├── copilot-conventions.md │ ├── create-xmlui-components.md │ ├── mermaid.md │ ├── testing-conventions.md │ └── xmlui-in-a-nutshell.md ├── dev-docs │ ├── accessibility.md │ ├── build-system.md │ ├── build-xmlui.md │ ├── component-behaviors.md │ ├── containers.md │ ├── glossary.md │ ├── index.md │ ├── next │ │ ├── component-dev-guide.md │ │ ├── configuration-management-enhancement-summary.md │ │ ├── documentation-scripts-refactoring-complete-summary.md │ │ ├── documentation-scripts-refactoring-plan.md │ │ ├── duplicate-pattern-extraction-summary.md │ │ ├── error-handling-standardization-summary.md │ │ ├── generating-component-reference.md │ │ ├── index.md │ │ ├── logging-consistency-implementation-summary.md │ │ ├── project-build.md │ │ ├── project-structure.md │ │ ├── theme-context.md │ │ ├── tiptap-design-considerations.md │ │ ├── working-with-code.md │ │ ├── xmlui-runtime-architecture │ │ └── xmlui-wcag-accessibility-report.md │ ├── react-fundamentals.md │ ├── release-method.md │ ├── standalone-app.md │ ├── state-management.md │ └── xmlui-repo.md ├── package.json ├── playwright.config.ts ├── scripts │ ├── coverage-only.js │ ├── e2e-test-summary.js │ ├── generate-docs │ │ ├── build-downloads-map.mjs │ │ ├── build-pages-map.mjs │ │ ├── components-config.json │ │ ├── configuration-management.mjs │ │ ├── constants.mjs │ │ ├── create-theme-files.mjs │ │ ├── DocsGenerator.mjs │ │ ├── error-handling.mjs │ │ ├── extensions-config.json │ │ ├── folders.mjs │ │ ├── generate-summary-files.mjs │ │ ├── get-docs.mjs │ │ ├── input-handler.mjs │ │ ├── logger.mjs │ │ ├── logging-standards.mjs │ │ ├── MetadataProcessor.mjs │ │ ├── pattern-utilities.mjs │ │ └── utils.mjs │ ├── get-langserver-metadata.mjs │ ├── inline-links.mjs │ └── README-e2e-summary.md ├── src │ ├── abstractions │ │ ├── _conventions.md │ │ ├── ActionDefs.ts │ │ ├── AppContextDefs.ts │ │ ├── ComponentDefs.ts │ │ ├── ContainerDefs.ts │ │ ├── ExtensionDefs.ts │ │ ├── FunctionDefs.ts │ │ ├── RendererDefs.ts │ │ ├── scripting │ │ │ ├── BlockScope.ts │ │ │ ├── Compilation.ts │ │ │ ├── LogicalThread.ts │ │ │ ├── LoopScope.ts │ │ │ ├── modules.ts │ │ │ ├── ScriptParserError.ts │ │ │ ├── Token.ts │ │ │ ├── TryScope.ts │ │ │ └── TryScopeExp.ts │ │ └── ThemingDefs.ts │ ├── components │ │ ├── _conventions.md │ │ ├── abstractions.ts │ │ ├── Accordion │ │ │ ├── Accordion.md │ │ │ ├── Accordion.module.scss │ │ │ ├── Accordion.spec.ts │ │ │ ├── Accordion.tsx │ │ │ ├── AccordionContext.tsx │ │ │ ├── AccordionItem.tsx │ │ │ ├── AccordionItemNative.tsx │ │ │ └── AccordionNative.tsx │ │ ├── Animation │ │ │ └── AnimationNative.tsx │ │ ├── APICall │ │ │ ├── APICall.md │ │ │ ├── APICall.spec.ts │ │ │ ├── APICall.tsx │ │ │ └── APICallNative.tsx │ │ ├── App │ │ │ ├── App.md │ │ │ ├── App.module.scss │ │ │ ├── App.spec.ts │ │ │ ├── App.tsx │ │ │ ├── AppLayoutContext.ts │ │ │ ├── AppNative.tsx │ │ │ ├── AppStateContext.ts │ │ │ ├── doc-resources │ │ │ │ ├── condensed-sticky.xmlui │ │ │ │ ├── condensed.xmlui │ │ │ │ ├── horizontal-sticky.xmlui │ │ │ │ ├── horizontal.xmlui │ │ │ │ ├── vertical-full-header.xmlui │ │ │ │ ├── vertical-sticky.xmlui │ │ │ │ └── vertical.xmlui │ │ │ ├── IndexerContext.ts │ │ │ ├── LinkInfoContext.ts │ │ │ ├── SearchContext.tsx │ │ │ ├── Sheet.module.scss │ │ │ └── Sheet.tsx │ │ ├── AppHeader │ │ │ ├── AppHeader.md │ │ │ ├── AppHeader.module.scss │ │ │ ├── AppHeader.spec.ts │ │ │ ├── AppHeader.tsx │ │ │ └── AppHeaderNative.tsx │ │ ├── AppState │ │ │ ├── AppState.md │ │ │ ├── AppState.spec.ts │ │ │ ├── AppState.tsx │ │ │ └── AppStateNative.tsx │ │ ├── AutoComplete │ │ │ ├── AutoComplete.md │ │ │ ├── AutoComplete.module.scss │ │ │ ├── AutoComplete.spec.ts │ │ │ ├── AutoComplete.tsx │ │ │ ├── AutoCompleteContext.tsx │ │ │ └── AutoCompleteNative.tsx │ │ ├── Avatar │ │ │ ├── Avatar.md │ │ │ ├── Avatar.module.scss │ │ │ ├── Avatar.spec.ts │ │ │ ├── Avatar.tsx │ │ │ └── AvatarNative.tsx │ │ ├── Backdrop │ │ │ ├── Backdrop.md │ │ │ ├── Backdrop.module.scss │ │ │ ├── Backdrop.spec.ts │ │ │ ├── Backdrop.tsx │ │ │ └── BackdropNative.tsx │ │ ├── Badge │ │ │ ├── Badge.md │ │ │ ├── Badge.module.scss │ │ │ ├── Badge.spec.ts │ │ │ ├── Badge.tsx │ │ │ └── BadgeNative.tsx │ │ ├── Bookmark │ │ │ ├── Bookmark.md │ │ │ ├── Bookmark.module.scss │ │ │ ├── Bookmark.spec.ts │ │ │ ├── Bookmark.tsx │ │ │ └── BookmarkNative.tsx │ │ ├── Breakout │ │ │ ├── Breakout.module.scss │ │ │ ├── Breakout.spec.ts │ │ │ ├── Breakout.tsx │ │ │ └── BreakoutNative.tsx │ │ ├── Button │ │ │ ├── Button-style.spec.ts │ │ │ ├── Button.md │ │ │ ├── Button.module.scss │ │ │ ├── Button.spec.ts │ │ │ ├── Button.tsx │ │ │ └── ButtonNative.tsx │ │ ├── Card │ │ │ ├── Card.md │ │ │ ├── Card.module.scss │ │ │ ├── Card.spec.ts │ │ │ ├── Card.tsx │ │ │ └── CardNative.tsx │ │ ├── Carousel │ │ │ ├── Carousel.md │ │ │ ├── Carousel.module.scss │ │ │ ├── Carousel.spec.ts │ │ │ ├── Carousel.tsx │ │ │ ├── CarouselContext.tsx │ │ │ ├── CarouselItem.tsx │ │ │ ├── CarouselItemNative.tsx │ │ │ └── CarouselNative.tsx │ │ ├── ChangeListener │ │ │ ├── ChangeListener.md │ │ │ ├── ChangeListener.spec.ts │ │ │ ├── ChangeListener.tsx │ │ │ └── ChangeListenerNative.tsx │ │ ├── chart-color-schemes.ts │ │ ├── Charts │ │ │ ├── AreaChart │ │ │ │ ├── AreaChart.md │ │ │ │ ├── AreaChart.spec.ts │ │ │ │ ├── AreaChart.tsx │ │ │ │ └── AreaChartNative.tsx │ │ │ ├── BarChart │ │ │ │ ├── BarChart.md │ │ │ │ ├── BarChart.module.scss │ │ │ │ ├── BarChart.spec.ts │ │ │ │ ├── BarChart.tsx │ │ │ │ └── BarChartNative.tsx │ │ │ ├── DonutChart │ │ │ │ ├── DonutChart.spec.ts │ │ │ │ └── DonutChart.tsx │ │ │ ├── LabelList │ │ │ │ ├── LabelList.spec.ts │ │ │ │ ├── LabelList.tsx │ │ │ │ ├── LabelListNative.module.scss │ │ │ │ └── LabelListNative.tsx │ │ │ ├── Legend │ │ │ │ ├── Legend.spec.ts │ │ │ │ ├── Legend.tsx │ │ │ │ └── LegendNative.tsx │ │ │ ├── LineChart │ │ │ │ ├── LineChart.md │ │ │ │ ├── LineChart.module.scss │ │ │ │ ├── LineChart.spec.ts │ │ │ │ ├── LineChart.tsx │ │ │ │ └── LineChartNative.tsx │ │ │ ├── PieChart │ │ │ │ ├── PieChart.md │ │ │ │ ├── PieChart.spec.ts │ │ │ │ ├── PieChart.tsx │ │ │ │ ├── PieChartNative.module.scss │ │ │ │ └── PieChartNative.tsx │ │ │ ├── RadarChart │ │ │ │ ├── RadarChart.md │ │ │ │ ├── RadarChart.spec.ts │ │ │ │ ├── RadarChart.tsx │ │ │ │ └── RadarChartNative.tsx │ │ │ ├── Tooltip │ │ │ │ ├── TooltipContent.module.scss │ │ │ │ ├── TooltipContent.spec.ts │ │ │ │ └── TooltipContent.tsx │ │ │ └── utils │ │ │ ├── abstractions.ts │ │ │ └── ChartProvider.tsx │ │ ├── Checkbox │ │ │ ├── Checkbox.md │ │ │ ├── Checkbox.spec.ts │ │ │ └── Checkbox.tsx │ │ ├── CodeBlock │ │ │ ├── CodeBlock.module.scss │ │ │ ├── CodeBlock.spec.ts │ │ │ ├── CodeBlock.tsx │ │ │ ├── CodeBlockNative.tsx │ │ │ └── highlight-code.ts │ │ ├── collectedComponentMetadata.ts │ │ ├── ColorPicker │ │ │ ├── ColorPicker.md │ │ │ ├── ColorPicker.module.scss │ │ │ ├── ColorPicker.spec.ts │ │ │ ├── ColorPicker.tsx │ │ │ └── ColorPickerNative.tsx │ │ ├── Column │ │ │ ├── Column.md │ │ │ ├── Column.tsx │ │ │ ├── ColumnNative.tsx │ │ │ ├── doc-resources │ │ │ │ └── list-component-data.js │ │ │ └── TableContext.tsx │ │ ├── component-utils.ts │ │ ├── ComponentProvider.tsx │ │ ├── ComponentRegistryContext.tsx │ │ ├── container-helpers.tsx │ │ ├── ContentSeparator │ │ │ ├── ContentSeparator.md │ │ │ ├── ContentSeparator.module.scss │ │ │ ├── ContentSeparator.spec.ts │ │ │ ├── ContentSeparator.tsx │ │ │ └── ContentSeparatorNative.tsx │ │ ├── DataSource │ │ │ ├── DataSource.md │ │ │ └── DataSource.tsx │ │ ├── DateInput │ │ │ ├── DateInput.md │ │ │ ├── DateInput.module.scss │ │ │ ├── DateInput.spec.ts │ │ │ ├── DateInput.tsx │ │ │ └── DateInputNative.tsx │ │ ├── DatePicker │ │ │ ├── DatePicker.md │ │ │ ├── DatePicker.module.scss │ │ │ ├── DatePicker.spec.ts │ │ │ ├── DatePicker.tsx │ │ │ └── DatePickerNative.tsx │ │ ├── DropdownMenu │ │ │ ├── DropdownMenu.md │ │ │ ├── DropdownMenu.module.scss │ │ │ ├── DropdownMenu.spec.ts │ │ │ ├── DropdownMenu.tsx │ │ │ ├── DropdownMenuNative.tsx │ │ │ ├── MenuItem.md │ │ │ └── SubMenuItem.md │ │ ├── EmojiSelector │ │ │ ├── EmojiSelector.md │ │ │ ├── EmojiSelector.spec.ts │ │ │ ├── EmojiSelector.tsx │ │ │ └── EmojiSelectorNative.tsx │ │ ├── ExpandableItem │ │ │ ├── ExpandableItem.module.scss │ │ │ ├── ExpandableItem.spec.ts │ │ │ ├── ExpandableItem.tsx │ │ │ └── ExpandableItemNative.tsx │ │ ├── FileInput │ │ │ ├── FileInput.md │ │ │ ├── FileInput.module.scss │ │ │ ├── FileInput.spec.ts │ │ │ ├── FileInput.tsx │ │ │ └── FileInputNative.tsx │ │ ├── FileUploadDropZone │ │ │ ├── FileUploadDropZone.md │ │ │ ├── FileUploadDropZone.module.scss │ │ │ ├── FileUploadDropZone.spec.ts │ │ │ ├── FileUploadDropZone.tsx │ │ │ └── FileUploadDropZoneNative.tsx │ │ ├── FlowLayout │ │ │ ├── FlowLayout.md │ │ │ ├── FlowLayout.module.scss │ │ │ ├── FlowLayout.spec.ts │ │ │ ├── FlowLayout.spec.ts-snapshots │ │ │ │ └── Edge-cases-boxShadow-is-not-clipped-1-non-smoke-darwin.png │ │ │ ├── FlowLayout.tsx │ │ │ └── FlowLayoutNative.tsx │ │ ├── Footer │ │ │ ├── Footer.md │ │ │ ├── Footer.module.scss │ │ │ ├── Footer.spec.ts │ │ │ ├── Footer.tsx │ │ │ └── FooterNative.tsx │ │ ├── Form │ │ │ ├── Form.md │ │ │ ├── Form.module.scss │ │ │ ├── Form.spec.ts │ │ │ ├── Form.tsx │ │ │ ├── formActions.ts │ │ │ ├── FormContext.ts │ │ │ └── FormNative.tsx │ │ ├── FormItem │ │ │ ├── FormItem.md │ │ │ ├── FormItem.module.scss │ │ │ ├── FormItem.spec.ts │ │ │ ├── FormItem.tsx │ │ │ ├── FormItemNative.tsx │ │ │ ├── HelperText.module.scss │ │ │ ├── HelperText.tsx │ │ │ ├── ItemWithLabel.tsx │ │ │ └── Validations.ts │ │ ├── FormSection │ │ │ ├── FormSection.md │ │ │ ├── FormSection.ts │ │ │ └── FormSection.xmlui │ │ ├── Fragment │ │ │ ├── Fragment.spec.ts │ │ │ └── Fragment.tsx │ │ ├── Heading │ │ │ ├── abstractions.ts │ │ │ ├── H1.md │ │ │ ├── H1.spec.ts │ │ │ ├── H2.md │ │ │ ├── H2.spec.ts │ │ │ ├── H3.md │ │ │ ├── H3.spec.ts │ │ │ ├── H4.md │ │ │ ├── H4.spec.ts │ │ │ ├── H5.md │ │ │ ├── H5.spec.ts │ │ │ ├── H6.md │ │ │ ├── H6.spec.ts │ │ │ ├── Heading.md │ │ │ ├── Heading.module.scss │ │ │ ├── Heading.spec.ts │ │ │ ├── Heading.tsx │ │ │ └── HeadingNative.tsx │ │ ├── HoverCard │ │ │ ├── HoverCard.tsx │ │ │ └── HovercardNative.tsx │ │ ├── HtmlTags │ │ │ ├── HtmlTags.module.scss │ │ │ ├── HtmlTags.spec.ts │ │ │ └── HtmlTags.tsx │ │ ├── Icon │ │ │ ├── AdmonitionDanger.tsx │ │ │ ├── AdmonitionInfo.tsx │ │ │ ├── AdmonitionNote.tsx │ │ │ ├── AdmonitionTip.tsx │ │ │ ├── AdmonitionWarning.tsx │ │ │ ├── ApiIcon.tsx │ │ │ ├── ArrowDropDown.module.scss │ │ │ ├── ArrowDropDown.tsx │ │ │ ├── ArrowDropUp.module.scss │ │ │ ├── ArrowDropUp.tsx │ │ │ ├── ArrowLeft.module.scss │ │ │ ├── ArrowLeft.tsx │ │ │ ├── ArrowRight.module.scss │ │ │ ├── ArrowRight.tsx │ │ │ ├── Attach.tsx │ │ │ ├── Binding.module.scss │ │ │ ├── Binding.tsx │ │ │ ├── BoardIcon.tsx │ │ │ ├── BoxIcon.tsx │ │ │ ├── CheckIcon.tsx │ │ │ ├── ChevronDownIcon.tsx │ │ │ ├── ChevronLeft.tsx │ │ │ ├── ChevronRight.tsx │ │ │ ├── ChevronUpIcon.tsx │ │ │ ├── CodeFileIcon.tsx │ │ │ ├── CodeSandbox.tsx │ │ │ ├── CompactListIcon.tsx │ │ │ ├── ContentCopyIcon.tsx │ │ │ ├── DarkToLightIcon.tsx │ │ │ ├── DatabaseIcon.module.scss │ │ │ ├── DatabaseIcon.tsx │ │ │ ├── DocFileIcon.tsx │ │ │ ├── DocIcon.tsx │ │ │ ├── DotMenuHorizontalIcon.tsx │ │ │ ├── DotMenuIcon.tsx │ │ │ ├── EmailIcon.tsx │ │ │ ├── EmptyFolderIcon.tsx │ │ │ ├── ErrorIcon.tsx │ │ │ ├── ExpressionIcon.tsx │ │ │ ├── FillPlusCricleIcon.tsx │ │ │ ├── FilterIcon.tsx │ │ │ ├── FolderIcon.tsx │ │ │ ├── GlobeIcon.tsx │ │ │ ├── HomeIcon.tsx │ │ │ ├── HyperLinkIcon.tsx │ │ │ ├── Icon.md │ │ │ ├── Icon.module.scss │ │ │ ├── Icon.spec.ts │ │ │ ├── Icon.tsx │ │ │ ├── IconNative.tsx │ │ │ ├── ImageFileIcon.tsx │ │ │ ├── Inspect.tsx │ │ │ ├── LightToDark.tsx │ │ │ ├── LinkIcon.tsx │ │ │ ├── ListIcon.tsx │ │ │ ├── LooseListIcon.tsx │ │ │ ├── MoonIcon.tsx │ │ │ ├── MoreOptionsIcon.tsx │ │ │ ├── NoSortIcon.tsx │ │ │ ├── PDFIcon.tsx │ │ │ ├── PenIcon.tsx │ │ │ ├── PhoneIcon.tsx │ │ │ ├── PhotoIcon.tsx │ │ │ ├── PlusIcon.tsx │ │ │ ├── SearchIcon.tsx │ │ │ ├── ShareIcon.tsx │ │ │ ├── SortAscendingIcon.tsx │ │ │ ├── SortDescendingIcon.tsx │ │ │ ├── StarsIcon.tsx │ │ │ ├── SunIcon.tsx │ │ │ ├── svg │ │ │ │ ├── admonition_danger.svg │ │ │ │ ├── admonition_info.svg │ │ │ │ ├── admonition_note.svg │ │ │ │ ├── admonition_tip.svg │ │ │ │ ├── admonition_warning.svg │ │ │ │ ├── api.svg │ │ │ │ ├── arrow-dropdown.svg │ │ │ │ ├── arrow-left.svg │ │ │ │ ├── arrow-right.svg │ │ │ │ ├── arrow-up.svg │ │ │ │ ├── attach.svg │ │ │ │ ├── binding.svg │ │ │ │ ├── box.svg │ │ │ │ ├── bulb.svg │ │ │ │ ├── code-file.svg │ │ │ │ ├── code-sandbox.svg │ │ │ │ ├── dark_to_light.svg │ │ │ │ ├── database.svg │ │ │ │ ├── doc.svg │ │ │ │ ├── empty-folder.svg │ │ │ │ ├── expression.svg │ │ │ │ ├── eye-closed.svg │ │ │ │ ├── eye-dark.svg │ │ │ │ ├── eye.svg │ │ │ │ ├── file-text.svg │ │ │ │ ├── filter.svg │ │ │ │ ├── folder.svg │ │ │ │ ├── img.svg │ │ │ │ ├── inspect.svg │ │ │ │ ├── light_to_dark.svg │ │ │ │ ├── moon.svg │ │ │ │ ├── pdf.svg │ │ │ │ ├── photo.svg │ │ │ │ ├── share.svg │ │ │ │ ├── stars.svg │ │ │ │ ├── sun.svg │ │ │ │ ├── trending-down.svg │ │ │ │ ├── trending-level.svg │ │ │ │ ├── trending-up.svg │ │ │ │ ├── txt.svg │ │ │ │ ├── unknown-file.svg │ │ │ │ ├── unlink.svg │ │ │ │ └── xls.svg │ │ │ ├── TableDeleteColumnIcon.tsx │ │ │ ├── TableDeleteRowIcon.tsx │ │ │ ├── TableInsertColumnIcon.tsx │ │ │ ├── TableInsertRowIcon.tsx │ │ │ ├── TrashIcon.tsx │ │ │ ├── TrendingDownIcon.tsx │ │ │ ├── TrendingLevelIcon.tsx │ │ │ ├── TrendingUpIcon.tsx │ │ │ ├── TxtIcon.tsx │ │ │ ├── UnknownFileIcon.tsx │ │ │ ├── UnlinkIcon.tsx │ │ │ ├── UserIcon.tsx │ │ │ ├── WarningIcon.tsx │ │ │ └── XlsIcon.tsx │ │ ├── IconProvider.tsx │ │ ├── IconRegistryContext.tsx │ │ ├── IFrame │ │ │ ├── IFrame.md │ │ │ ├── IFrame.module.scss │ │ │ ├── IFrame.spec.ts │ │ │ ├── IFrame.tsx │ │ │ └── IFrameNative.tsx │ │ ├── Image │ │ │ ├── Image.md │ │ │ ├── Image.module.scss │ │ │ ├── Image.spec.ts │ │ │ ├── Image.tsx │ │ │ └── ImageNative.tsx │ │ ├── Input │ │ │ ├── index.ts │ │ │ ├── InputAdornment.module.scss │ │ │ ├── InputAdornment.tsx │ │ │ ├── InputDivider.module.scss │ │ │ ├── InputDivider.tsx │ │ │ ├── InputLabel.module.scss │ │ │ ├── InputLabel.tsx │ │ │ ├── PartialInput.module.scss │ │ │ └── PartialInput.tsx │ │ ├── InspectButton │ │ │ ├── InspectButton.module.scss │ │ │ └── InspectButton.tsx │ │ ├── Items │ │ │ ├── Items.md │ │ │ ├── Items.spec.ts │ │ │ ├── Items.tsx │ │ │ └── ItemsNative.tsx │ │ ├── Link │ │ │ ├── Link.md │ │ │ ├── Link.module.scss │ │ │ ├── Link.spec.ts │ │ │ ├── Link.tsx │ │ │ └── LinkNative.tsx │ │ ├── List │ │ │ ├── doc-resources │ │ │ │ └── list-component-data.js │ │ │ ├── List.md │ │ │ ├── List.module.scss │ │ │ ├── List.spec.ts │ │ │ ├── List.tsx │ │ │ └── ListNative.tsx │ │ ├── Logo │ │ │ ├── doc-resources │ │ │ │ └── xmlui-logo.svg │ │ │ ├── Logo.md │ │ │ ├── Logo.tsx │ │ │ └── LogoNative.tsx │ │ ├── Markdown │ │ │ ├── CodeText.module.scss │ │ │ ├── CodeText.tsx │ │ │ ├── Markdown.md │ │ │ ├── Markdown.module.scss │ │ │ ├── Markdown.spec.ts │ │ │ ├── Markdown.tsx │ │ │ ├── MarkdownNative.tsx │ │ │ ├── parse-binding-expr.ts │ │ │ └── utils.ts │ │ ├── metadata-helpers.ts │ │ ├── ModalDialog │ │ │ ├── ConfirmationModalContextProvider.tsx │ │ │ ├── Dialog.module.scss │ │ │ ├── Dialog.tsx │ │ │ ├── ModalDialog.md │ │ │ ├── ModalDialog.module.scss │ │ │ ├── ModalDialog.spec.ts │ │ │ ├── ModalDialog.tsx │ │ │ ├── ModalDialogNative.tsx │ │ │ └── ModalVisibilityContext.tsx │ │ ├── NavGroup │ │ │ ├── NavGroup.md │ │ │ ├── NavGroup.module.scss │ │ │ ├── NavGroup.spec.ts │ │ │ ├── NavGroup.tsx │ │ │ ├── NavGroupContext.ts │ │ │ └── NavGroupNative.tsx │ │ ├── NavLink │ │ │ ├── NavLink.md │ │ │ ├── NavLink.module.scss │ │ │ ├── NavLink.spec.ts │ │ │ ├── NavLink.tsx │ │ │ └── NavLinkNative.tsx │ │ ├── NavPanel │ │ │ ├── NavPanel.md │ │ │ ├── NavPanel.module.scss │ │ │ ├── NavPanel.spec.ts │ │ │ ├── NavPanel.tsx │ │ │ └── NavPanelNative.tsx │ │ ├── NestedApp │ │ │ ├── AppWithCodeView.module.scss │ │ │ ├── AppWithCodeView.tsx │ │ │ ├── AppWithCodeViewNative.tsx │ │ │ ├── defaultProps.tsx │ │ │ ├── logo.svg │ │ │ ├── NestedApp.module.scss │ │ │ ├── NestedApp.tsx │ │ │ ├── NestedAppNative.tsx │ │ │ ├── Tooltip.module.scss │ │ │ ├── Tooltip.tsx │ │ │ └── utils.ts │ │ ├── NoResult │ │ │ ├── NoResult.md │ │ │ ├── NoResult.module.scss │ │ │ ├── NoResult.spec.ts │ │ │ ├── NoResult.tsx │ │ │ └── NoResultNative.tsx │ │ ├── NumberBox │ │ │ ├── numberbox-abstractions.ts │ │ │ ├── NumberBox.md │ │ │ ├── NumberBox.module.scss │ │ │ ├── NumberBox.spec.ts │ │ │ ├── NumberBox.tsx │ │ │ └── NumberBoxNative.tsx │ │ ├── Option │ │ │ ├── Option.md │ │ │ ├── Option.spec.ts │ │ │ ├── Option.tsx │ │ │ ├── OptionNative.tsx │ │ │ └── OptionTypeProvider.tsx │ │ ├── PageMetaTitle │ │ │ ├── PageMetaTilteNative.tsx │ │ │ ├── PageMetaTitle.md │ │ │ ├── PageMetaTitle.spec.ts │ │ │ └── PageMetaTitle.tsx │ │ ├── Pages │ │ │ ├── Page.md │ │ │ ├── Pages.md │ │ │ ├── Pages.module.scss │ │ │ ├── Pages.tsx │ │ │ └── PagesNative.tsx │ │ ├── Pagination │ │ │ ├── Pagination.md │ │ │ ├── Pagination.module.scss │ │ │ ├── Pagination.spec.ts │ │ │ ├── Pagination.tsx │ │ │ └── PaginationNative.tsx │ │ ├── PositionedContainer │ │ │ ├── PositionedContainer.module.scss │ │ │ ├── PositionedContainer.tsx │ │ │ └── PositionedContainerNative.tsx │ │ ├── ProfileMenu │ │ │ ├── ProfileMenu.module.scss │ │ │ └── ProfileMenu.tsx │ │ ├── ProgressBar │ │ │ ├── ProgressBar.md │ │ │ ├── ProgressBar.module.scss │ │ │ ├── ProgressBar.spec.ts │ │ │ ├── ProgressBar.tsx │ │ │ └── ProgressBarNative.tsx │ │ ├── Queue │ │ │ ├── Queue.md │ │ │ ├── Queue.spec.ts │ │ │ ├── Queue.tsx │ │ │ ├── queueActions.ts │ │ │ └── QueueNative.tsx │ │ ├── RadioGroup │ │ │ ├── RadioGroup.md │ │ │ ├── RadioGroup.module.scss │ │ │ ├── RadioGroup.spec.ts │ │ │ ├── RadioGroup.tsx │ │ │ ├── RadioGroupNative.tsx │ │ │ ├── RadioItem.tsx │ │ │ └── RadioItemNative.tsx │ │ ├── RealTimeAdapter │ │ │ ├── RealTimeAdapter.tsx │ │ │ └── RealTimeAdapterNative.tsx │ │ ├── Redirect │ │ │ ├── Redirect.md │ │ │ ├── Redirect.spec.ts │ │ │ └── Redirect.tsx │ │ ├── ResponsiveBar │ │ │ ├── README.md │ │ │ ├── ResponsiveBar.md │ │ │ ├── ResponsiveBar.module.scss │ │ │ ├── ResponsiveBar.spec.ts │ │ │ ├── ResponsiveBar.tsx │ │ │ └── ResponsiveBarNative.tsx │ │ ├── Select │ │ │ ├── HiddenOption.tsx │ │ │ ├── MultiSelectOption.tsx │ │ │ ├── OptionContext.ts │ │ │ ├── Select.md │ │ │ ├── Select.module.scss │ │ │ ├── Select.spec.ts │ │ │ ├── Select.tsx │ │ │ ├── SelectContext.tsx │ │ │ ├── SelectNative.tsx │ │ │ ├── SelectOption.tsx │ │ │ └── SimpleSelect.tsx │ │ ├── SelectionStore │ │ │ ├── SelectionStore.md │ │ │ ├── SelectionStore.tsx │ │ │ └── SelectionStoreNative.tsx │ │ ├── Slider │ │ │ ├── Slider.md │ │ │ ├── Slider.module.scss │ │ │ ├── Slider.spec.ts │ │ │ ├── Slider.tsx │ │ │ └── SliderNative.tsx │ │ ├── Slot │ │ │ ├── Slot.md │ │ │ ├── Slot.spec.ts │ │ │ └── Slot.ts │ │ ├── SlotItem.tsx │ │ ├── SpaceFiller │ │ │ ├── SpaceFiller.md │ │ │ ├── SpaceFiller.module.scss │ │ │ ├── SpaceFiller.spec.ts │ │ │ ├── SpaceFiller.tsx │ │ │ └── SpaceFillerNative.tsx │ │ ├── Spinner │ │ │ ├── Spinner.md │ │ │ ├── Spinner.module.scss │ │ │ ├── Spinner.spec.ts │ │ │ ├── Spinner.tsx │ │ │ └── SpinnerNative.tsx │ │ ├── Splitter │ │ │ ├── HSplitter.md │ │ │ ├── HSplitter.spec.ts │ │ │ ├── Splitter.md │ │ │ ├── Splitter.module.scss │ │ │ ├── Splitter.spec.ts │ │ │ ├── Splitter.tsx │ │ │ ├── SplitterNative.tsx │ │ │ ├── utils.ts │ │ │ ├── VSplitter.md │ │ │ └── VSplitter.spec.ts │ │ ├── Stack │ │ │ ├── CHStack.md │ │ │ ├── CHStack.spec.ts │ │ │ ├── CVStack.md │ │ │ ├── CVStack.spec.ts │ │ │ ├── HStack.md │ │ │ ├── HStack.spec.ts │ │ │ ├── Stack.md │ │ │ ├── Stack.module.scss │ │ │ ├── Stack.spec.ts │ │ │ ├── Stack.tsx │ │ │ ├── StackNative.tsx │ │ │ ├── VStack.md │ │ │ └── VStack.spec.ts │ │ ├── StickyBox │ │ │ ├── StickyBox.md │ │ │ ├── StickyBox.module.scss │ │ │ ├── StickyBox.tsx │ │ │ └── StickyBoxNative.tsx │ │ ├── Switch │ │ │ ├── Switch.md │ │ │ ├── Switch.spec.ts │ │ │ └── Switch.tsx │ │ ├── Table │ │ │ ├── doc-resources │ │ │ │ └── list-component-data.js │ │ │ ├── react-table-config.d.ts │ │ │ ├── Table.md │ │ │ ├── Table.module.scss │ │ │ ├── Table.spec.ts │ │ │ ├── Table.tsx │ │ │ ├── TableNative.tsx │ │ │ └── useRowSelection.tsx │ │ ├── TableOfContents │ │ │ ├── TableOfContents.module.scss │ │ │ ├── TableOfContents.spec.ts │ │ │ ├── TableOfContents.tsx │ │ │ └── TableOfContentsNative.tsx │ │ ├── Tabs │ │ │ ├── TabContext.tsx │ │ │ ├── TabItem.md │ │ │ ├── TabItem.tsx │ │ │ ├── TabItemNative.tsx │ │ │ ├── Tabs.md │ │ │ ├── Tabs.module.scss │ │ │ ├── Tabs.spec.ts │ │ │ ├── Tabs.tsx │ │ │ └── TabsNative.tsx │ │ ├── Text │ │ │ ├── Text.md │ │ │ ├── Text.module.scss │ │ │ ├── Text.spec.ts │ │ │ ├── Text.tsx │ │ │ └── TextNative.tsx │ │ ├── TextArea │ │ │ ├── TextArea.md │ │ │ ├── TextArea.module.scss │ │ │ ├── TextArea.spec.ts │ │ │ ├── TextArea.tsx │ │ │ ├── TextAreaNative.tsx │ │ │ ├── TextAreaResizable.tsx │ │ │ └── useComposedRef.ts │ │ ├── TextBox │ │ │ ├── TextBox.md │ │ │ ├── TextBox.module.scss │ │ │ ├── TextBox.spec.ts │ │ │ ├── TextBox.tsx │ │ │ └── TextBoxNative.tsx │ │ ├── Theme │ │ │ ├── NotificationToast.tsx │ │ │ ├── Theme.md │ │ │ ├── Theme.module.scss │ │ │ ├── Theme.spec.ts │ │ │ ├── Theme.tsx │ │ │ └── ThemeNative.tsx │ │ ├── TimeInput │ │ │ ├── TimeInput.md │ │ │ ├── TimeInput.module.scss │ │ │ ├── TimeInput.spec.ts │ │ │ ├── TimeInput.tsx │ │ │ ├── TimeInputNative.tsx │ │ │ └── utils.ts │ │ ├── Timer │ │ │ ├── Timer.md │ │ │ ├── Timer.spec.ts │ │ │ ├── Timer.tsx │ │ │ └── TimerNative.tsx │ │ ├── Toggle │ │ │ ├── Toggle.module.scss │ │ │ └── Toggle.tsx │ │ ├── ToneChangerButton │ │ │ ├── ToneChangerButton.md │ │ │ ├── ToneChangerButton.spec.ts │ │ │ └── ToneChangerButton.tsx │ │ ├── ToneSwitch │ │ │ ├── ToneSwitch.md │ │ │ ├── ToneSwitch.module.scss │ │ │ ├── ToneSwitch.spec.ts │ │ │ ├── ToneSwitch.tsx │ │ │ └── ToneSwitchNative.tsx │ │ ├── Tooltip │ │ │ ├── Tooltip.md │ │ │ ├── Tooltip.module.scss │ │ │ ├── Tooltip.spec.ts │ │ │ ├── Tooltip.tsx │ │ │ └── TooltipNative.tsx │ │ ├── Tree │ │ │ ├── testData.ts │ │ │ ├── Tree-dynamic.spec.ts │ │ │ ├── Tree-icons.spec.ts │ │ │ ├── Tree.md │ │ │ ├── Tree.spec.ts │ │ │ ├── TreeComponent.module.scss │ │ │ ├── TreeComponent.tsx │ │ │ └── TreeNative.tsx │ │ ├── TreeDisplay │ │ │ ├── TreeDisplay.md │ │ │ ├── TreeDisplay.module.scss │ │ │ ├── TreeDisplay.tsx │ │ │ └── TreeDisplayNative.tsx │ │ ├── ValidationSummary │ │ │ ├── ValidationSummary.module.scss │ │ │ └── ValidationSummary.tsx │ │ └── VisuallyHidden.tsx │ ├── components-core │ │ ├── abstractions │ │ │ ├── ComponentRenderer.ts │ │ │ ├── LoaderRenderer.ts │ │ │ ├── standalone.ts │ │ │ └── treeAbstractions.ts │ │ ├── action │ │ │ ├── actions.ts │ │ │ ├── APICall.tsx │ │ │ ├── FileDownloadAction.tsx │ │ │ ├── FileUploadAction.tsx │ │ │ ├── NavigateAction.tsx │ │ │ └── TimedAction.tsx │ │ ├── ApiBoundComponent.tsx │ │ ├── appContext │ │ │ ├── date-functions.ts │ │ │ ├── math-function.ts │ │ │ └── misc-utils.ts │ │ ├── AppContext.tsx │ │ ├── behaviors │ │ │ ├── Behavior.tsx │ │ │ └── CoreBehaviors.tsx │ │ ├── component-hooks.ts │ │ ├── ComponentDecorator.tsx │ │ ├── ComponentViewer.tsx │ │ ├── CompoundComponent.tsx │ │ ├── constants.ts │ │ ├── DebugViewProvider.tsx │ │ ├── descriptorHelper.ts │ │ ├── devtools │ │ │ ├── InspectorDialog.module.scss │ │ │ ├── InspectorDialog.tsx │ │ │ └── InspectorDialogVisibilityContext.tsx │ │ ├── EngineError.ts │ │ ├── event-handlers.ts │ │ ├── InspectorButton.module.scss │ │ ├── InspectorContext.tsx │ │ ├── interception │ │ │ ├── abstractions.ts │ │ │ ├── ApiInterceptor.ts │ │ │ ├── ApiInterceptorProvider.tsx │ │ │ ├── apiInterceptorWorker.ts │ │ │ ├── Backend.ts │ │ │ ├── Errors.ts │ │ │ ├── IndexedDb.ts │ │ │ ├── initMock.ts │ │ │ ├── InMemoryDb.ts │ │ │ ├── ReadonlyCollection.ts │ │ │ └── useApiInterceptorContext.tsx │ │ ├── loader │ │ │ ├── ApiLoader.tsx │ │ │ ├── DataLoader.tsx │ │ │ ├── ExternalDataLoader.tsx │ │ │ ├── Loader.tsx │ │ │ ├── MockLoaderRenderer.tsx │ │ │ └── PageableLoader.tsx │ │ ├── LoaderComponent.tsx │ │ ├── markup-check.ts │ │ ├── parts.ts │ │ ├── renderers.ts │ │ ├── rendering │ │ │ ├── AppContent.tsx │ │ │ ├── AppRoot.tsx │ │ │ ├── AppWrapper.tsx │ │ │ ├── buildProxy.ts │ │ │ ├── collectFnVarDeps.ts │ │ │ ├── ComponentAdapter.tsx │ │ │ ├── ComponentWrapper.tsx │ │ │ ├── Container.tsx │ │ │ ├── containers.ts │ │ │ ├── ContainerWrapper.tsx │ │ │ ├── ErrorBoundary.module.scss │ │ │ ├── ErrorBoundary.tsx │ │ │ ├── InvalidComponent.module.scss │ │ │ ├── InvalidComponent.tsx │ │ │ ├── nodeUtils.ts │ │ │ ├── reducer.ts │ │ │ ├── renderChild.tsx │ │ │ ├── StandaloneComponent.tsx │ │ │ ├── StateContainer.tsx │ │ │ ├── UnknownComponent.module.scss │ │ │ ├── UnknownComponent.tsx │ │ │ └── valueExtractor.ts │ │ ├── reportEngineError.ts │ │ ├── RestApiProxy.ts │ │ ├── script-runner │ │ │ ├── asyncProxy.ts │ │ │ ├── AttributeValueParser.ts │ │ │ ├── bannedFunctions.ts │ │ │ ├── BindingTreeEvaluationContext.ts │ │ │ ├── eval-tree-async.ts │ │ │ ├── eval-tree-common.ts │ │ │ ├── eval-tree-sync.ts │ │ │ ├── ParameterParser.ts │ │ │ ├── process-statement-async.ts │ │ │ ├── process-statement-common.ts │ │ │ ├── process-statement-sync.ts │ │ │ ├── ScriptingSourceTree.ts │ │ │ ├── simplify-expression.ts │ │ │ ├── statement-queue.ts │ │ │ └── visitors.ts │ │ ├── StandaloneApp.tsx │ │ ├── StandaloneExtensionManager.ts │ │ ├── TableOfContentsContext.tsx │ │ ├── theming │ │ │ ├── _themes.scss │ │ │ ├── component-layout-resolver.ts │ │ │ ├── extendThemeUtils.ts │ │ │ ├── hvar.ts │ │ │ ├── layout-resolver.ts │ │ │ ├── parse-layout-props.ts │ │ │ ├── StyleContext.tsx │ │ │ ├── StyleRegistry.ts │ │ │ ├── ThemeContext.tsx │ │ │ ├── ThemeProvider.tsx │ │ │ ├── themes │ │ │ │ ├── base-utils.ts │ │ │ │ ├── palette.ts │ │ │ │ ├── root.ts │ │ │ │ ├── solid.ts │ │ │ │ ├── theme-colors.ts │ │ │ │ └── xmlui.ts │ │ │ ├── themeVars.module.scss │ │ │ ├── themeVars.ts │ │ │ ├── transformThemeVars.ts │ │ │ └── utils.ts │ │ ├── utils │ │ │ ├── actionUtils.ts │ │ │ ├── audio-utils.ts │ │ │ ├── compound-utils.ts │ │ │ ├── css-utils.ts │ │ │ ├── DataLoaderQueryKeyGenerator.ts │ │ │ ├── date-utils.ts │ │ │ ├── extractParam.ts │ │ │ ├── hooks.tsx │ │ │ ├── LruCache.ts │ │ │ ├── mergeProps.ts │ │ │ ├── misc.ts │ │ │ ├── request-params.ts │ │ │ ├── statementUtils.ts │ │ │ └── treeUtils.ts │ │ └── xmlui-parser.ts │ ├── index-standalone.ts │ ├── index.scss │ ├── index.ts │ ├── language-server │ │ ├── server-common.ts │ │ ├── server-web-worker.ts │ │ ├── server.ts │ │ ├── services │ │ │ ├── common │ │ │ │ ├── docs-generation.ts │ │ │ │ ├── lsp-utils.ts │ │ │ │ ├── metadata-utils.ts │ │ │ │ └── syntax-node-utilities.ts │ │ │ ├── completion.ts │ │ │ ├── diagnostic.ts │ │ │ ├── format.ts │ │ │ └── hover.ts │ │ └── xmlui-metadata-generated.mjs │ ├── logging │ │ ├── LoggerContext.tsx │ │ ├── LoggerInitializer.tsx │ │ ├── LoggerService.ts │ │ └── xmlui.ts │ ├── logo.svg │ ├── parsers │ │ ├── common │ │ │ ├── GenericToken.ts │ │ │ ├── InputStream.ts │ │ │ └── utils.ts │ │ ├── scripting │ │ │ ├── code-behind-collect.ts │ │ │ ├── Lexer.ts │ │ │ ├── modules.ts │ │ │ ├── Parser.ts │ │ │ ├── ParserError.ts │ │ │ ├── ScriptingNodeTypes.ts │ │ │ ├── TokenTrait.ts │ │ │ ├── TokenType.ts │ │ │ └── tree-visitor.ts │ │ ├── style-parser │ │ │ ├── errors.ts │ │ │ ├── source-tree.ts │ │ │ ├── StyleInputStream.ts │ │ │ ├── StyleLexer.ts │ │ │ ├── StyleParser.ts │ │ │ └── tokens.ts │ │ └── xmlui-parser │ │ ├── CharacterCodes.ts │ │ ├── diagnostics.ts │ │ ├── fileExtensions.ts │ │ ├── index.ts │ │ ├── lint.ts │ │ ├── parser.ts │ │ ├── ParserError.ts │ │ ├── scanner.ts │ │ ├── syntax-kind.ts │ │ ├── syntax-node.ts │ │ ├── transform.ts │ │ ├── utils.ts │ │ ├── xmlui-serializer.ts │ │ └── xmlui-tree.ts │ ├── react-app-env.d.ts │ ├── syntax │ │ ├── monaco │ │ │ ├── grammar.monacoLanguage.ts │ │ │ ├── index.ts │ │ │ ├── xmlui-dark.ts │ │ │ ├── xmlui-light.ts │ │ │ └── xmluiscript.monacoLanguage.ts │ │ └── textMate │ │ ├── index.ts │ │ ├── xmlui-dark.json │ │ ├── xmlui-light.json │ │ ├── xmlui.json │ │ └── xmlui.tmLanguage.json │ ├── testing │ │ ├── assertions.ts │ │ ├── component-test-helpers.ts │ │ ├── ComponentDrivers.ts │ │ ├── drivers │ │ │ ├── DateInputDriver.ts │ │ │ ├── ModalDialogDriver.ts │ │ │ ├── NumberBoxDriver.ts │ │ │ ├── TextBoxDriver.ts │ │ │ ├── TimeInputDriver.ts │ │ │ ├── TimerDriver.ts │ │ │ └── TreeDriver.ts │ │ ├── fixtures.ts │ │ ├── infrastructure │ │ │ ├── index.html │ │ │ ├── main.tsx │ │ │ ├── public │ │ │ │ ├── mockServiceWorker.js │ │ │ │ ├── resources │ │ │ │ │ ├── bell.svg │ │ │ │ │ ├── box.svg │ │ │ │ │ ├── doc.svg │ │ │ │ │ ├── eye.svg │ │ │ │ │ ├── flower-640x480.jpg │ │ │ │ │ ├── sun.svg │ │ │ │ │ ├── test-image-100x100.jpg │ │ │ │ │ └── txt.svg │ │ │ │ └── serve.json │ │ │ └── TestBed.tsx │ │ └── themed-app-test-helpers.ts │ └── vite-env.d.ts ├── tests │ ├── components │ │ ├── CodeBlock │ │ │ └── hightlight-code.test.ts │ │ ├── playground-pattern.test.ts │ │ └── Tree │ │ └── Tree-states.test.ts │ ├── components-core │ │ ├── abstractions │ │ │ └── treeAbstractions.test.ts │ │ ├── container │ │ │ └── buildProxy.test.ts │ │ ├── interception │ │ │ ├── orderBy.test.ts │ │ │ ├── ReadOnlyCollection.test.ts │ │ │ └── request-param-converter.test.ts │ │ ├── scripts-runner │ │ │ ├── AttributeValueParser.test.ts │ │ │ ├── eval-tree-arrow-async.test.ts │ │ │ ├── eval-tree-arrow.test.ts │ │ │ ├── eval-tree-func-decl-async.test.ts │ │ │ ├── eval-tree-func-decl.test.ts │ │ │ ├── eval-tree-pre-post.test.ts │ │ │ ├── eval-tree-regression.test.ts │ │ │ ├── eval-tree.test.ts │ │ │ ├── function-proxy.test.ts │ │ │ ├── parser-regression.test.ts │ │ │ ├── process-event.test.ts │ │ │ ├── process-function.test.ts │ │ │ ├── process-implicit-context.test.ts │ │ │ ├── process-statement-asgn.test.ts │ │ │ ├── process-statement-destruct.test.ts │ │ │ ├── process-statement-regs.test.ts │ │ │ ├── process-statement-sync.test.ts │ │ │ ├── process-statement.test.ts │ │ │ ├── process-switch-sync.test.ts │ │ │ ├── process-switch.test.ts │ │ │ ├── process-try-sync.test.ts │ │ │ ├── process-try.test.ts │ │ │ └── test-helpers.ts │ │ ├── test-metadata-handler.ts │ │ ├── theming │ │ │ ├── border-segments.test.ts │ │ │ ├── component-layout.resolver.test.ts │ │ │ ├── layout-property-parser.test.ts │ │ │ ├── layout-resolver.test.ts │ │ │ ├── layout-resolver2.test.ts │ │ │ ├── layout-vp-override.test.ts │ │ │ └── padding-segments.test.ts │ │ └── utils │ │ ├── date-utils.test.ts │ │ ├── format-human-elapsed-time.test.ts │ │ └── LruCache.test.ts │ ├── language-server │ │ ├── completion.test.ts │ │ ├── format.test.ts │ │ ├── hover.test.ts │ │ └── mockData.ts │ └── parsers │ ├── common │ │ └── input-stream.test.ts │ ├── markdown │ │ └── parse-binding-expression.test.ts │ ├── parameter-parser.test.ts │ ├── paremeter-parser.test.ts │ ├── scripting │ │ ├── eval-tree-arrow.test.ts │ │ ├── eval-tree-pre-post.test.ts │ │ ├── eval-tree.test.ts │ │ ├── function-proxy.test.ts │ │ ├── lexer-literals.test.ts │ │ ├── lexer-misc.test.ts │ │ ├── module-parse.test.ts │ │ ├── parser-arrow.test.ts │ │ ├── parser-assignments.test.ts │ │ ├── parser-binary.test.ts │ │ ├── parser-destructuring.test.ts │ │ ├── parser-errors.test.ts │ │ ├── parser-expressions.test.ts │ │ ├── parser-function.test.ts │ │ ├── parser-literals.test.ts │ │ ├── parser-primary.test.ts │ │ ├── parser-regex.test.ts │ │ ├── parser-statements.test.ts │ │ ├── parser-unary.test.ts │ │ ├── process-event.test.ts │ │ ├── process-implicit-context.test.ts │ │ ├── process-statement-asgn.test.ts │ │ ├── process-statement-destruct.test.ts │ │ ├── process-statement-regs.test.ts │ │ ├── process-statement-sync.test.ts │ │ ├── process-statement.test.ts │ │ ├── process-switch-sync.test.ts │ │ ├── process-switch.test.ts │ │ ├── process-try-sync.test.ts │ │ ├── process-try.test.ts │ │ ├── simplify-expression.test.ts │ │ ├── statement-hooks.test.ts │ │ └── test-helpers.ts │ ├── style-parser │ │ ├── generateHvarChain.test.ts │ │ ├── parseHVar.test.ts │ │ ├── parser.test.ts │ │ └── tokens.test.ts │ └── xmlui │ ├── lint.test.ts │ ├── parser.test.ts │ ├── scanner.test.ts │ ├── transform.attr.test.ts │ ├── transform.circular.test.ts │ ├── transform.element.test.ts │ ├── transform.errors.test.ts │ ├── transform.escape.test.ts │ ├── transform.regression.test.ts │ ├── transform.script.test.ts │ ├── transform.test.ts │ └── xmlui.ts ├── tests-e2e │ ├── api-bound-component-regression.spec.ts │ ├── api-call-as-extracted-component.spec.ts │ ├── assign-to-object-or-array-regression.spec.ts │ ├── binding-regression.spec.ts │ ├── children-as-template-context-vars.spec.ts │ ├── compound-component.spec.ts │ ├── context-vars-regression.spec.ts │ ├── data-bindings.spec.ts │ ├── datasource-and-api-usage-in-var.spec.ts │ ├── datasource-direct-binding.spec.ts │ ├── datasource-onLoaded-regression.spec.ts │ ├── modify-array-item-regression.spec.ts │ ├── namespaces.spec.ts │ ├── push-to-array-regression.spec.ts │ ├── screen-breakpoints.spec.ts │ ├── scripting.spec.ts │ ├── state-scope-in-pages.spec.ts │ └── state-var-scopes.spec.ts ├── tsconfig.bin.json ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── vitest.config.ts ``` # Files -------------------------------------------------------------------------------- /xmlui/src/components-core/utils/audio-utils.ts: -------------------------------------------------------------------------------- ```typescript /** * Audio utility functions */ /** * Generates a beep sound using the Web Audio API * @param frequency - The frequency of the beep in Hz (default: 800) * @param durationInMs - The duration of the beep in milliseconds (default: 200) */ export function beep(frequency: number = 800, durationInMs: number = 200): void { // Check if Web Audio API is supported if (typeof window === 'undefined' || !window.AudioContext && !(window as any).webkitAudioContext) { console.warn('Web Audio API is not supported in this environment'); return; } try { // Create audio context (handle both standard and webkit prefixed versions) const AudioContextClass = window.AudioContext || (window as any).webkitAudioContext; const audioContext = new AudioContextClass(); // Create oscillator for the tone const oscillator = audioContext.createOscillator(); const gainNode = audioContext.createGain(); // Connect nodes: oscillator -> gain -> destination oscillator.connect(gainNode); gainNode.connect(audioContext.destination); // Configure oscillator oscillator.frequency.setValueAtTime(frequency, audioContext.currentTime); oscillator.type = 'sine'; // Use sine wave for a clean tone // Configure gain (volume) with fade in/out to avoid clicking const currentTime = audioContext.currentTime; const endTime = currentTime + durationInMs / 1000; const fadeTime = Math.min(0.01, durationInMs / 1000 / 4); // Fade time is 1/4 of duration or 10ms, whichever is smaller gainNode.gain.setValueAtTime(0, currentTime); gainNode.gain.linearRampToValueAtTime(0.3, currentTime + fadeTime); // Fade in gainNode.gain.setValueAtTime(0.3, endTime - fadeTime); gainNode.gain.linearRampToValueAtTime(0, endTime); // Fade out // Start and stop the oscillator oscillator.start(currentTime); oscillator.stop(endTime); // Clean up after the beep is done oscillator.addEventListener('ended', () => { oscillator.disconnect(); gainNode.disconnect(); audioContext.close(); }); } catch (error) { console.warn('Failed to generate beep:', error); } } /** * Generates a sequence of beeps * @param count - Number of beeps * @param frequency - The frequency of each beep in Hz (default: 800) * @param durationInMs - The duration of each beep in milliseconds (default: 200) * @param intervalInMs - The interval between beeps in milliseconds (default: 300) */ export function beepSequence( count: number, frequency: number = 800, durationInMs: number = 200, intervalInMs: number = 300 ): void { for (let i = 0; i < count; i++) { setTimeout(() => { beep(frequency, durationInMs); }, i * intervalInMs); } } /** * Predefined beep patterns for common use cases */ export const BeepPatterns = { /** Short, high-pitched beep for success */ success: () => beep(1000, 150), /** Lower frequency beep for errors */ error: () => beep(400, 300), /** Quick beep for notifications */ notification: () => beep(800, 100), /** Double beep for warnings */ warning: () => beepSequence(2, 600, 150, 200), /** Triple beep for alerts */ alert: () => beepSequence(3, 900, 100, 150), } as const; ``` -------------------------------------------------------------------------------- /docs/content/components/ChangeListener.md: -------------------------------------------------------------------------------- ```markdown # ChangeListener [#changelistener] `ChangeListener` is an invisible component that watches for changes in values and triggers actions in response. It's essential for creating reactive behavior when you need to respond to data changes, state updates, or component property modifications outside of normal event handlers. **Key features:** - **Value monitoring**: Watches any expression, variable, or component property for changes - **Throttling support**: Prevents excessive triggering with `throttleWaitInMs` for rapid changes - **Previous/new values**: Access both old and new values in the change handler - **Reactive patterns**: Coordinates between components or triggers side effects ## Properties [#properties] ### `listenTo` [#listento] Value to the changes of which this component listens. If this property is not set, the `ChangeListener` is inactive. The following sample demonstrates using this property. Every time the user clicks the button, a counter is incremented. The `ChangeListener` component watches the counter's value. Whenever it changes, the component fires the `didChange` event, which stores whether the new counter value is even into the `isEven` variable. ```xmlui-pg copy display name="Example: listenTo" <App var.counter="{0}" var.isEven="{false}"> <Button label="Increment counter" onClick="{counter++}" /> <ChangeListener listenTo="{counter}" onDidChange="isEven = counter % 2 == 0" /> <Text>Counter is {counter} which {isEven? "is": "isn't"} even.</Text> </App> ``` ### `throttleWaitInMs` (default: 0) [#throttlewaitinms-default-0] This variable sets a throttling time (in milliseconds) to apply when executing the `didChange` event handler. All changes within that throttling time will only fire the `didChange` event once. The following example works like the previous one (in the `listen` prop's description). However, the user can reset or set the throttling time to 3 seconds. You can observe that while the throttling time is 3 seconds, the counter increments on every click, but `isEven` only refreshes once within 3 seconds. ```xmlui-pg copy display name="Example: throttleWaitInMs" <App var.counter="{0}" var.isEven="{false}" var.throttle="{0}"> <HStack> <Button label="Increment counter" onClick="{counter++}" /> <Button label="Set 3 sec throttling" onClick="throttle = 3000" /> <Button label="Reset throttling" onClick="throttle = 0" /> </HStack> <ChangeListener listenTo="{counter}" throttleWaitInMs="{throttle}" onDidChange="isEven = counter % 2 == 0" /> <Text>Counter is {counter} which {isEven? "is": "isn't"} even.</Text> </App> ``` ## Events [#events] ### `didChange` [#didchange] This event is triggered when value of ChangeListener has changed. This event is fired when the component observes a value change (within the specified throttling interval). Define the event handler that responds to that change (as the previous samples demonstrate). The event argument is an object with `prevValue` and `newValue` properties that (as their name suggests) contain the previous and the new values. ## Exposed Methods [#exposed-methods] This component does not expose any methods. ## Styling [#styling] This component does not have any styles. ``` -------------------------------------------------------------------------------- /xmlui/tests/components-core/container/buildProxy.test.ts: -------------------------------------------------------------------------------- ```typescript import { describe, expect, it } from "vitest"; import { buildProxy , ProxyCallbackArgs } from "../../../src/components-core/rendering/buildProxy"; describe("proxy", () => { it("buildProxy keeps proxied reference on get", async () => { const testObject = { name: "John Doe", address: { city: "Budapest", street: { kind: "road", name: "Main", number: 1, }, }, }; const proxyObject = buildProxy(testObject, () => {}); expect(proxyObject.address).equal(proxyObject.address); expect(proxyObject.address).eql({ city: "Budapest", street: { kind: "road", name: "Main", number: 1, }, }); expect(proxyObject.name).equal(proxyObject.name); expect(proxyObject.name).equal("John Doe"); expect(proxyObject.address.street).equal(proxyObject.address.street); expect(proxyObject.address.street).eql({ kind: "road", name: "Main", number: 1, }); }); it("buildProxy observes change #1", async () => { const testObject = { name: "John Doe", address: { city: "Budapest", street: { kind: "road", name: "Main", number: 1, }, }, }; const changes: ProxyCallbackArgs[] = []; const proxyObject = buildProxy(testObject, (change) => {changes.push(change)}); proxyObject.name = "Jane Doe"; expect(changes.length).equal(1); const change = changes[0]; expect(change.action).equal("set"); expect(change.path).equal("name"); expect(change.pathArray).eql(["name"]); expect(change.newValue).equal("Jane Doe"); expect(change.previousValue).equal("John Doe"); }); it("buildProxy observes change #2", async () => { const testObject = { name: "John Doe", address: { city: "Budapest", street: { kind: "road", name: "Main", number: 1, }, }, }; const changes: ProxyCallbackArgs[] = []; const proxyObject = buildProxy(testObject, (change) => {changes.push(change)}); proxyObject.address.city = "Dunakeszi"; expect(changes.length).equal(1); const change = changes[0]; expect(change.action).equal("set"); expect(change.path).equal("address.city"); expect(change.pathArray).eql(["address", "city"]); expect(change.newValue).equal("Dunakeszi"); expect(change.previousValue).equal("Budapest"); }); it("buildProxy observes change #3", async () => { const testObject = { name: "John Doe", address: { city: "Budapest", street: { kind: "road", name: "Main", number: 1, }, }, }; const changes: ProxyCallbackArgs[] = []; const proxyObject = buildProxy(testObject, (change) => {changes.push(change)}); proxyObject.address.street.name = "Kossuth"; expect(changes.length).equal(1); const change = changes[0]; expect(change.action).equal("set"); expect(change.path).equal("address.street.name"); expect(change.pathArray).eql(["address", "street", "name"]); expect(change.newValue).equal("Kossuth"); expect(change.previousValue).equal("Main"); }); }); ``` -------------------------------------------------------------------------------- /xmlui/src/components-core/renderers.ts: -------------------------------------------------------------------------------- ```typescript import type { ComponentDef, ComponentMetadata, } from "../abstractions/ComponentDefs"; import type { ComponentRendererFn, ComponentRendererDef, CompoundComponentRendererInfo, } from "../abstractions/RendererDefs"; import type { LoaderRenderer, LoaderRendererDef } from "./abstractions/LoaderRenderer"; /** * This helper function creates a component renderer definition from its arguments. * @param type The unique identifier of the component definition * @param renderer The function that renders the component definition into a React node * @param metadata Optional hints to help fix the rendering errors coming from invalid component property definitions * @returns The view renderer definition composed of the arguments */ export function createComponentRenderer<TMd extends ComponentMetadata>( type: string, metadata: TMd, renderer: ComponentRendererFn<ComponentDef<any>>, ): ComponentRendererDef { return { type, renderer, metadata, }; } /** * Create a non-visual component used for encapsulating property values * @param type Component type * @param metadata Component metadata */ export function createPropHolderComponent<TMd extends ComponentMetadata>( type: string, metadata?: TMd, ) { return createComponentRenderer(type, metadata, () => { throw new Error("Prop holder component, shouldn't render"); }); } /** * AppEngine uses React as its UI framework to declare and render loaders. This type declares a function that renders * a particular loader definition into its React representation. * * The type parameter of the function refers to the loader definition the function is about to render. * @param type The unique ID of the loader * @param renderer The function that renders the loader * @param hints Optional hints to help fix the rendering errors coming from invalid component property definitions */ export function createLoaderRenderer<TMd extends ComponentMetadata>( type: string, renderer: LoaderRenderer<TMd>, hints?: TMd, ): LoaderRendererDef { return { type, renderer, hints, }; } /** * This helper function creates a user defined component renderer definition from its arguments. * @param metadata The metadata of the user-defined component * @param componentMarkup The XMLUI markup that defines the user-defined component * @param codeBehind Optional code-behind script that contains variable and function definitions * used by the component * @returns The view renderer definition composed of the arguments */ export function createUserDefinedComponentRenderer<TMd extends ComponentMetadata>( metadata: TMd, def: any, codeBehind?: any, ): CompoundComponentRendererInfo { // --- Parse the component definition from the markup // --- Parse the optional code-behind script const component = def.component.component;; if (codeBehind) { if (codeBehind.vars) { component.vars ??= {}; component.vars = { ...component.vars, ...codeBehind.vars }; } if (codeBehind.functions) { component.functions ??= {}; component.functions = { ...component.functions, ...codeBehind.functions, }; } } // --- Done. return { compoundComponentDef: def.component, metadata, }; } ``` -------------------------------------------------------------------------------- /xmlui/src/components/NavPanel/NavPanel.module.scss: -------------------------------------------------------------------------------- ```scss @use "../../components-core/theming/themes" as t; // --- This code snippet is required to collect the theme variables used in this module $component: "NavPanel"; $themeVars: (); @function createThemeVar($componentVariable) { $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global; @return t.getThemeVar($themeVars, $componentVariable); } $themeVars: t.composeBorderVars($themeVars, $component); $backgroundColor-NavPanel: createThemeVar("backgroundColor-#{$component}"); $backgroundColor-NavPanel-horizontal: createThemeVar("backgroundColor-#{$component}-horizontal"); $boxShadow-NavPanel: createThemeVar("boxShadow-#{$component}"); $themeVars: t.composePaddingVars($themeVars, $component); $themeVars: t.composePaddingVars($themeVars, "logo-#{$component}"); $marginBottom-logo-NavPanel: createThemeVar("marginBottom-logo-#{$component}"); $maxWidth-content-NavPanel: createThemeVar("maxWidth-content-App"); $height-NavPanel: createThemeVar("height-AppHeader"); $paddingVertical-AppHeader: createThemeVar("paddingVertical-AppHeader"); $alignment-content-AppHeader: createThemeVar("alignment-content-AppHeader"); @layer components { .wrapper { --footer-height: 0; //temp solution, it's because of callbackDrive, settings stickyBox to the bottom (we'll have to introduce a smarter stickyBox) height: 100%; width: 100%; flex-shrink: 0; box-shadow: $boxShadow-NavPanel; @include t.paddingVars($themeVars, $component); @include t.borderVars($themeVars, $component); overflow: auto; position: relative; display: flex; flex-direction: column; scrollbar-width: thin; &:not(.condensed){ background-color: $backgroundColor-NavPanel; } &.vertical { scrollbar-gutter: stable; } &.horizontal{ box-shadow: none; height: $height-NavPanel; background-color: $backgroundColor-NavPanel-horizontal; &:not(.condensed){ .wrapperInner{ padding-inline: createThemeVar("paddingHorizontal-#{$component}"); justify-content: $alignment-content-AppHeader; } } .wrapperInner{ height: 100%; flex-direction: row; max-width: $maxWidth-content-NavPanel; //align-items: center; width: 100%; margin: 0 auto; justify-content: $alignment-content-AppHeader; } } } .wrapperInner{ display: flex; flex-direction: column; justify-content: $alignment-content-AppHeader; } .logoWrapper{ &:not(:empty) { display: flex; flex-shrink: 0; height: $height-NavPanel; padding-top: calc(#{createThemeVar("paddingVertical-logo-#{$component}")} + #{$paddingVertical-AppHeader}); padding-bottom: calc(#{createThemeVar("paddingVertical-logo-#{$component}")} + #{$paddingVertical-AppHeader}); padding-inline: createThemeVar("paddingHorizontal-logo-#{$component}"); margin-bottom: $marginBottom-logo-NavPanel; justify-content: createThemeVar("horizontalAlignment-logo-#{$component}"); } &.inDrawer { //to make room for the close button min-height: 32px; } } } // --- We export the theme variables to add them to the component renderer :export{ themeVars: t.json-stringify($themeVars) } ``` -------------------------------------------------------------------------------- /xmlui/src/components/ResponsiveBar/ResponsiveBar.spec.ts: -------------------------------------------------------------------------------- ```typescript import { expect, test } from "../../testing/fixtures"; test.describe("ResponsiveBar", () => { test("renders children in horizontal layout", async ({ initTestBed, page }) => { await initTestBed(` <ResponsiveBar testId="responsive-bar"> <Button testId="btn1" label="Button 1" /> <Button testId="btn2" label="Button 2" /> <Button testId="btn3" label="Button 3" /> </ResponsiveBar> `); const responsiveBar = page.getByTestId("responsive-bar"); await expect(responsiveBar).toBeVisible(); // All buttons should be visible initially await expect(responsiveBar.locator('[data-testid="btn1"]').first()).toBeVisible(); await expect(responsiveBar.locator('[data-testid="btn2"]').first()).toBeVisible(); await expect(responsiveBar.locator('[data-testid="btn3"]').first()).toBeVisible(); }); test("moves overflowing items to dropdown when container is too narrow", async ({ initTestBed, page }) => { await initTestBed(` <ResponsiveBar testId="responsive-bar" style="width: 200px;"> <Button testId="btn1" label="Very Long Button 1" /> <Button testId="btn2" label="Very Long Button 2" /> <Button testId="btn3" label="Very Long Button 3" /> <Button testId="btn4" label="Very Long Button 4" /> </ResponsiveBar> `); const responsiveBar = page.getByTestId("responsive-bar"); await expect(responsiveBar).toBeVisible(); // Wait for the component to finish measuring and laying out await page.waitForTimeout(100); // Check if overflow dropdown is present const overflowDropdown = responsiveBar.locator(".overflowDropdown").first(); // If there's an overflow dropdown, some items should be moved there if (await overflowDropdown.isVisible()) { // Click the overflow button to see the dropdown menu const overflowTrigger = overflowDropdown.locator("svg, button").first(); await overflowTrigger.click(); // Wait for dropdown to appear await page.waitForTimeout(100); // There should be some menu items in the dropdown const menuItems = page.locator('[role="menuitem"]'); const menuItemCount = await menuItems.count(); expect(menuItemCount).toBeGreaterThan(0); } }); test("responds to container resize", async ({ initTestBed, page }) => { await initTestBed(` <ResponsiveBar testId="responsive-bar" style="width: 400px; border: 1px solid red;"> <Button testId="btn1" label="Button 1" /> <Button testId="btn2" label="Button 2" /> <Button testId="btn3" label="Button 3" /> <Button testId="btn4" label="Button 4" /> </ResponsiveBar> `); const responsiveBar = page.getByTestId("responsive-bar"); await expect(responsiveBar).toBeVisible(); // Initially, check if all buttons are visible or some are in overflow await page.waitForTimeout(100); // Check the current state const btn1Visible = await responsiveBar.locator('[data-testid="btn1"]').first().isVisible(); const btn4Visible = await responsiveBar.locator('[data-testid="btn4"]').first().isVisible(); // Verify the component is working - at least the first button should be visible expect(btn1Visible).toBe(true); }); }); ``` -------------------------------------------------------------------------------- /xmlui/dev-docs/next/index.md: -------------------------------------------------------------------------------- ```markdown # XMLUI Developer's Guide Welcome to the XMLUI developer's guide. This documentation covers the technical architecture, build processes, and development workflows for the entire XMLUI monorepo ecosystem. This guide is specifically for developers working on the XMLUI framework, its extensions, and related tools. For user-facing documentation and component guides, visit the main XMLUI documentation website. ## Getting Started **Prerequisites:** - **Node.js**: Version 18.12.0 or higher - **npm**: Version 10.9.2 or higher (comes with Node.js) - **Git**: For version control 1. **Fork and Clone the Repository** ```bash # Fork the repository on GitHub first, then clone your fork git clone https://github.com/YOUR_USERNAME/xmlui.git cd xmlui ``` 2. **Install Dependencies** ```bash # Install all workspace dependencies npm install ``` 3. **Build Documentation** ```bash # Build documentation site npm run build-docs ``` 4. **Build the Framework** ```bash # Build core framework and all extension packages npm run build-xmlui ``` 5. **Run Tests** (Optional) ```bash # Run the complete test suite npm run test-xmlui # Or run just smoke tests for faster feedback npm run test-xmlui-smoke ``` 6. **Start Development** ```bash # Start the documentation site with live reload cd docs npm start ``` ## Project Artifacts XMLUI is organized as a monorepo containing **12+ buildable artifacts** across multiple workspaces. Each workspace has its own `package.json` and produces specific build outputs: ### Core Framework - **`xmlui`** (`xmlui/`) - Main framework library with components, parsers, and CLI tools ### Extension Packages (7 packages) - **`xmlui-animations`** (`packages/xmlui-animations/`) - Animation components with React Spring integration - **`xmlui-devtools`** (`packages/xmlui-devtools/`) - Developer tools and debugging utilities - **`xmlui-os-frames`** (`packages/xmlui-os-frames/`) - OS-specific window frames and chrome - **`xmlui-pdf`** (`packages/xmlui-pdf/`) - PDF generation and display components - **`xmlui-playground`** (`packages/xmlui-playground/`) - Interactive code playground and editor - **`xmlui-search`** (`packages/xmlui-search/`) - Search and filtering components - **`xmlui-spreadsheet`** (`packages/xmlui-spreadsheet/`) - Spreadsheet and data grid components ### Applications & Tools - **`xmlui-e2e`** (`tests/`) - End-to-end test suite and test bed application - **`create-xmlui-app`** (`tools/create-app/`) - CLI tool for scaffolding new projects - **`xmlui-vscode`** (`tools/vscode/`) - VS Code extension for language support ### Documentation - **`xmlui-docs`** (`docs/`) - User documentation website with interactive playground ## Documentation Structure - [**Project Structure**](./project-structure.md) - Developer documentation for monorepo structure and build artifacts - [**Working with Code**](./working-with-code.md) - Development workflow and common tasks for contributing to XMLUI - [**Project Build**](./project-build.md) - Build system documentation using Turborepo for monorepo orchestration - [**Generate Component Reference Documentation**](./generating-component-reference.md) - How to generate and maintain component reference documentation ``` -------------------------------------------------------------------------------- /xmlui/dev-docs/next/theme-context.md: -------------------------------------------------------------------------------- ```markdown # ThemeContext - XMLUI's Theme Management System ## What is ThemeContext? ThemeContext is XMLUI's **React Context-based theming system** that provides centralized theme management throughout your application. It's like a global state manager specifically for themes and visual appearance. ## Two Main Hooks ### `useTheme()` - Current Theme Information ```typescript import { useTheme } from "../../components-core/theming/ThemeContext"; function MyComponent() { const theme = useTheme(); // Access current theme properties: console.log(theme.activeThemeTone); // "light" or "dark" console.log(theme.activeTheme); // Current theme object console.log(theme.getResourceUrl); // Function to get theme-specific resources } ``` ### `useThemes()` - Theme Control & Management ```typescript import { useThemes } from "../../components-core/theming/ThemeContext"; function ThemeController() { const { activeThemeTone, setActiveThemeTone, themes } = useThemes(); // Read current state: console.log('Current tone:', activeThemeTone); // "light" or "dark" // Change themes: setActiveThemeTone("dark"); // Switch to dark mode setActiveThemeTone("light"); // Switch to light mode // Access available themes: console.log('Available themes:', themes); } ``` ## Key Properties & Methods | Property | Type | Description | |----------|------|-------------| | `activeThemeTone` | `light` or `dark` | Current theme tone | | `setActiveThemeTone` | `function` | Function to change theme tone | | `activeTheme` | `object` | Current theme configuration | | `setActiveThemeId` | `function` | Function to change theme | | `themes` | `array` | Available theme definitions | | `getResourceUrl` | `function` | Get theme-specific resource URLs | ## How Components Use It ### Reading Theme State ```typescript // ToneSwitch uses it to sync with theme state const { activeThemeTone } = useThemes(); const isDarkMode = activeThemeTone === "dark"; ``` ### Controlling Themes ```typescript // ToneChangerButton uses it to toggle themes const { activeThemeTone, setActiveThemeTone } = useThemes(); const toggleTheme = () => { setActiveThemeTone(activeThemeTone === "light" ? "dark" : "light"); }; ``` ### Theme-Aware Styling ```typescript // AppHeader uses it for theme-specific logos const { activeThemeTone } = useTheme(); const logoUrl = useResourceUrl(`resource:logo-${activeThemeTone}`); ``` ## Provider Setup The ThemeContext is typically provided at the app level through the `<App>` component or `<Theme>` components, making theme state available to all child components. ## Benefits - **Centralized theme management** - Single source of truth - **Reactive updates** - Components auto-update when theme changes - **Type-safe** - TypeScript support for theme properties - **Resource management** - Theme-specific assets and URLs - **Cross-component sync** - All components stay in sync automatically A themed component doesn't need to manage its own state. It simply: 1. **Reads** the current theme from context 2. **Displays** the appropriate visual state 3. **Updates** the global theme when clicked 4. **Automatically re-renders** when theme changes elsewhere The ThemeContext is the "invisible glue" that makes XMLUI's theming system work seamlessly across all components. ``` -------------------------------------------------------------------------------- /xmlui/src/components/AppHeader/AppHeader.module.scss: -------------------------------------------------------------------------------- ```scss @use "../../components-core/theming/themes" as t; // --- This code snippet is required to collect the theme variables used in this module $component: "AppHeader"; $themeVars: (); @function createThemeVar($componentVariable) { $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global; @return t.getThemeVar($themeVars, $componentVariable); } // --- Theme vars for paddings and borders $themeVars: t.composePaddingVars($themeVars, $component); $themeVars: t.composePaddingVars($themeVars, "logo-#{$component}"); $themeVars: t.composeBorderVars($themeVars, $component); // --- Other $width-logo-AppHeader: createThemeVar("width-logo-#{$component}"); $alignment-content-AppHeader: createThemeVar("alignment-content-#{$component}"); @layer components { .header { position: relative; height: createThemeVar("height-#{$component}"); box-sizing: content-box; background-color: createThemeVar("backgroundColor-#{$component}"); @include t.borderVars($themeVars, $component); } .headerInner { height: 100%; flex: 1; gap: t.$space-4; flex-shrink: 0; display: flex; flex-direction: row; align-items: center; max-width: createThemeVar("maxWidth-content-#{$component}"); padding-inline: t.getThemeVar($themeVars, "paddingHorizontal-#{$component}"); @include t.paddingVars($themeVars, $component); &.full { max-width: createThemeVar("maxWidth-#{$component}"); } width: 100%; margin: 0 auto; } .childrenWrapper { --stack-gap-default: #{t.$space-2}; display: flex; flex-direction: row; flex: 1; min-width: 0; height: 100%; align-items: center; gap: var(--stack-gap-default); justify-content: $alignment-content-AppHeader; } .subNavPanelSlot{ display: flex; flex-direction: row; } .logoAndTitle { display: flex; align-items: center; gap: t.$space-4; height: 100%; &:not(:empty) { padding-right: t.$space-2; } &:empty { display: none; } } .logoContainer:not(:empty) { flex-shrink: 0; display: flex; width: $width-logo-AppHeader; height: 100%; align-items: center; @include t.paddingVars($themeVars, "logo-#{$component}"); } .customLogoContainer { display: flex; height: 100%; align-items: center; & > img { height: 100%; } } .rightItems { --stack-gap-default: #{t.$space-2}; gap: var(--stack-gap-default); height: 100%; &:not(:empty) { padding-left: t.$space-4; } display: flex; flex-direction: row; align-items: center; } .appHub { text-decoration: none; margin-right: t.$space-4; width: 40px; padding: t.$space-2; color: t.$textColor-subtitle; cursor: pointer; &:hover { color: t.$textColor-secondary; } svg { width: 100%; height: 100%; } } .drawerToggle.drawerToggle{ padding: createThemeVar("padding-drawerToggle-#{$component}") !important; display: none; @include t.withMaxScreenSize(2) { display: block; } } .logoLink{ padding: 0; height: 100%; &>:first-child { height: 100%; } } } // --- We export the theme variables to add them to the component renderer :export { themeVars: t.json-stringify($themeVars); } ``` -------------------------------------------------------------------------------- /xmlui/src/components/SelectionStore/SelectionStoreNative.tsx: -------------------------------------------------------------------------------- ```typescript import React, { type ReactNode, useLayoutEffect, useMemo, useState, useContext, useRef, } from "react"; import { isEqual, noop } from "lodash-es"; import type { RegisterComponentApiFn, UpdateStateFn } from "../../abstractions/RendererDefs"; import { useEvent } from "../../components-core/utils/misc"; import { EMPTY_ARRAY } from "../../components-core/constants"; export const defaultProps = { idKey: "id", selectedItems: EMPTY_ARRAY, }; type SelectionStoreProps = { idKey?: string; updateState: UpdateStateFn; children: ReactNode; selectedItems: any[]; registerComponentApi?: RegisterComponentApiFn; }; const EMPTY_SELECTION_STATE = { value: EMPTY_ARRAY, }; export const StandaloneSelectionStore = ({ children }) => { const [selection, setSelection] = useState(EMPTY_SELECTION_STATE); return ( <SelectionStore updateState={setSelection} selectedItems={selection.value}> {children} </SelectionStore> ); }; export const SelectionStore = ({ updateState = noop, idKey = defaultProps.idKey, children, selectedItems = defaultProps.selectedItems, registerComponentApi = noop, }: SelectionStoreProps) => { const [items, setItems] = useState<any[]>(selectedItems); const valueInitializedRef = useRef(false); const currentItemsRef = useRef<any[]>(selectedItems); const refreshSelection = useEvent((allItems: any[] = EMPTY_ARRAY) => { const safeAllItems = allItems || EMPTY_ARRAY; const safeSelectedItems = selectedItems || EMPTY_ARRAY; setItems(safeAllItems); currentItemsRef.current = safeAllItems; // Update the ref synchronously let value = safeAllItems.filter( (item) => !!safeSelectedItems.find((si) => si && item && si[idKey] === item[idKey]), ); if (!isEqual(safeSelectedItems, value) || !valueInitializedRef.current) { valueInitializedRef.current = true; updateState({ value, }); } }); const setSelectedRowIds = useEvent((rowIds: any) => { const safeItems = currentItemsRef.current || EMPTY_ARRAY; // Use ref instead of state updateState({ value: safeItems.filter((item) => rowIds.includes(item[idKey])) }); }); const clearSelection = useEvent(() => { setSelectedRowIds(EMPTY_ARRAY); }); useLayoutEffect(() => { registerComponentApi({ clearSelection, setSelectedRowIds, refreshSelection, }); }, [clearSelection, setSelectedRowIds, registerComponentApi, refreshSelection]); // --- Pass this selection context to the provider const contextValue = useMemo(() => { return { selectedItems, setSelectedRowIds, refreshSelection, idKey, }; }, [refreshSelection, selectedItems, setSelectedRowIds, idKey]); return <SelectionContext.Provider value={contextValue}>{children}</SelectionContext.Provider>; }; // Defines the elements of the current selection tracking interface SelectionState { selectedItems: any[]; setSelectedRowIds: React.Dispatch<React.SetStateAction<string[]>>; refreshSelection: (allItems: any[]) => void; idKey: string; } // Represents the default selection context const SelectionContext = React.createContext<SelectionState>(null); // This React hook takes care of retrieving the current selection context export function useSelectionContext() { return useContext(SelectionContext); } ``` -------------------------------------------------------------------------------- /packages/xmlui-animations/src/AnimationNative.tsx: -------------------------------------------------------------------------------- ```typescript import { animated, useSpring, useInView } from "@react-spring/web"; import React, { Children, forwardRef, useEffect, useMemo, useState } from "react"; import { useCallback } from "react"; import type { RegisterComponentApiFn } from "xmlui"; export type AnimationProps = { children?: React.ReactNode; animation: any; registerComponentApi?: RegisterComponentApiFn; onStop?: () => void; onStart?: () => void; animateWhenInView?: boolean; duration?: number; reverse?: boolean; loop?: boolean; once?: boolean; delay?: number; }; const AnimatedComponent = animated( forwardRef(function InlineComponentDef(props: any, ref) { const { children, ...rest } = props; return React.cloneElement(children, { ...rest, ref }); }), ); export const defaultProps: Pick<AnimationProps, "delay" | "animateWhenInView" |"reverse" | "loop" | "once"> = { delay: 0, animateWhenInView: false, reverse: false, loop: false, once: false }; export const Animation = ({ children, registerComponentApi, animation, duration, onStop, onStart, delay = defaultProps.delay, animateWhenInView = defaultProps.animateWhenInView, reverse = defaultProps.reverse, loop = defaultProps.loop, once = defaultProps.once, }: AnimationProps) => { const [_animation] = useState(animation); const [toggle, setToggle] = useState(false); const [reset, setReset] = useState(false); const [count, setCount] = useState(0); const times = 1; const animationSettings = useMemo<any>( () => ({ from: _animation.from, to: _animation.to, config: { ..._animation.config, duration, }, delay, loop, reset, reverse: toggle, onRest: () => { onStop?.(); if (loop) { if (reverse) { setCount(count + 1); setToggle(!toggle); } setReset(true); } else { if (reverse && count < times) { setCount(count + 1); setToggle(!toggle); } } }, onStart: () => onStart?.(), }), [ _animation.config, _animation.from, _animation.to, count, delay, duration, loop, onStart, onStop, reset, reverse, toggle, ], ); const [springs, api] = useSpring( () => ({ ...animationSettings, }), [animationSettings], ); const [ref, animationStyles] = useInView(() => animationSettings, { once, }); const startAnimation = useCallback(() => { api.start(_animation); return () => { api.stop(); }; }, [_animation, api]); const stopAnimation = useCallback(() => { api.stop(); }, [api]); useEffect(() => { registerComponentApi?.({ start: startAnimation, stop: stopAnimation, }); }, [registerComponentApi, startAnimation, stopAnimation]); const content = useMemo(() => { return Children.map(children, (child, index) => animateWhenInView ? ( <AnimatedComponent style={animationStyles} key={index} ref={ref}> {child} </AnimatedComponent> ) : ( <AnimatedComponent style={springs} key={index}> {child} </AnimatedComponent> ), ); }, [animateWhenInView, animationStyles, children, ref, springs]); return content; }; ``` -------------------------------------------------------------------------------- /docs/public/pages/tutorial-07.md: -------------------------------------------------------------------------------- ```markdown # Invoices Here is the app's `Invoices` page, with a cached subset of data. To view the table full-width, and optionally make changes, use the  icon to pop out to the full XMLUI playground. ```xmlui-pg ---app display <App> <Invoices /> </App> ---comp <Component name="Invoices" > <DataSource id="invoices" url="/resources/files/invoices.json" transformResult="{(data) => data.slice(0, 10)}" method="GET" /> <Theme maxWidth-ModalDialog="50%"> <ModalDialog id="detailsDialog"> <InvoiceDetails details="{$params[0]}" /> </ModalDialog> </Theme> <HStack> <H1>Invoices</H1> <SpaceFiller /> <Button enabled="{false}" label="Create Invoice" onClick="navigate('/invoices/new')" /> </HStack> <Table data="{invoices}"> <Column canSort="true" bindTo="invoice_number" /> <Column canSort="true" bindTo="client" /> <Column canSort="true" bindTo="issue_date" /> <Column canSort="true" bindTo="due_date" /> <Column canSort="true" bindTo="paid_date" /> <Column canSort="true" header="total"> ${$item.total} </Column> <Column canSort="true" header="Status"> <StatusBadge status="{$item.status}" /> </Column> <Column header="Details"> <Icon name="doc-outline" /> </Column> </Table> </Component> ---comp <Component name="StatusBadge" var.statusColors="{{ draft: { background: '#f59e0b', label: 'white' }, sent: { background: '#3b82f6', label: 'white' }, paid: { background: '#10b981', label: 'white' } }}" > <Badge enabled="{$props.enabled}" value="{$props.status}" colorMap="{statusColors}" variant="pill" /> </Component> ``` The `Create Invoice` button is disabled for this part of the demo. Here is the `Invoices` component. ```xmlui /detailsDialog/ <Component name="Invoices"> <HStack> <H1>Invoices</H1> <SpaceFiller/> <Button label="Create Invoice" onClick="navigate('/invoices/new')"/> </HStack> <Table data="/api/invoices"> <Column bindTo="invoice_number"/> <Column bindTo="client"/> <Column bindTo="issue_date"/> <Column bindTo="due_date"/> <Column bindTo="paid_date"/> <Column bindTo="total"> ${$item.total} </Column> <Column bindTo="status"> <StatusBadge status="{$item.status}"/> </Column> <Column canSort="{false}" header="Details"> <Icon name="doc-outline" onClick="detailsDialog.open($item)"/> </Column> </Table> <ModalDialog id="detailsDialog" maxWidth="50%"> <InvoiceDetails details="{$param}"/> </ModalDialog> </Component> ``` ## A ModalDialog The id attribute of the [ModalDialog](/components/ModalDialog) maps to the `onClick` handler of the `Details` column. We'll see later how, when clicked, it loads the `InvoiceDetails` component into a `ModalDialog`. When enabled, the `CreateInvoice` button uses the global function `navigate` to go to the page defined by the `CreateInvoice` component. Two of the columns in the table, `Status` and `Details`, show how a [Column](/components/Column) can contain XMLUI markup that may include user-defined (`StatusBadge`) and/or built-in (`Icon`) components. ``` -------------------------------------------------------------------------------- /xmlui/src/components/TableOfContents/TableOfContents.tsx: -------------------------------------------------------------------------------- ```typescript import styles from "./TableOfContents.module.scss"; import { createComponentRenderer } from "../../components-core/renderers"; import { parseScssVar } from "../../components-core/theming/themeVars"; import { TableOfContents, defaultProps } from "./TableOfContentsNative"; import { useIndexerContext } from "../App/IndexerContext"; import { createMetadata } from "../metadata-helpers"; const COMP = "TableOfContents"; const COMP_CHILD = "TableOfContentsItem"; export const TableOfContentsMd = createMetadata({ status: "stable", description: "`TableOfContents` component collects [Heading](/components/Heading) and " + "[Bookmark](/components/Bookmark) within the current page and displays them in a navigable tree.", props: { smoothScrolling: { description: `This property indicates that smooth scrolling is used while scrolling the selected table ` + `of contents items into view.`, valueType: "boolean", defaultValue: defaultProps.smoothScrolling, }, maxHeadingLevel: { description: "Defines the maximum heading level (1 to 6) to include in the table of contents. " + "For example, if it is 2, then `H1` and `H2` are displayed, but lower levels " + "(`H3` to `H6`) are not.", valueType: "number", defaultValue: defaultProps.maxHeadingLevel, }, omitH1: { description: "If true, the `H1` heading is not included in the table of contents. " + "This is useful when the `H1` is used for the page title and you want to avoid duplication.", valueType: "boolean", defaultValue: false, }, }, themeVars: parseScssVar(styles.themeVars), defaultThemeVars: { [`padding-${COMP}`]: "$space-2", [`textColor-${COMP_CHILD}`]: "$color-secondary-500", [`textColor-${COMP_CHILD}--hover`]: "$textColor-primary", [`fontSize-${COMP_CHILD}`]: "$fontSize-sm", [`wordWrap-${COMP_CHILD}`]: "break-word", [`paddingVertical-${COMP_CHILD}`]: "$space-1", [`paddingLeft-${COMP_CHILD}`]: "$space-1", [`paddingLeft-${COMP_CHILD}-level-2`]: "$space-3", [`paddingLeft-${COMP_CHILD}-level-3`]: "$space-5", [`paddingLeft-${COMP_CHILD}-level-4`]: "$space-6", [`paddingLeft-${COMP_CHILD}-level-5`]: "$space-6", [`paddingLeft-${COMP_CHILD}-level-6`]: "$space-6", [`fontWeight-${COMP_CHILD}`]: "$fontWeight-bold", [`fontWeight-${COMP_CHILD}-level-3`]: "normal", [`fontWeight-${COMP_CHILD}-level-4`]: "normal", [`fontWeight-${COMP_CHILD}-level-5`]: "normal", [`fontWeight-${COMP_CHILD}-level-6`]: "normal", [`fontStyle-${COMP_CHILD}-level-6`]: "italic", [`color-${COMP_CHILD}--active`]: "$color-primary-500", }, }); function IndexAwareTableOfContents(props) { const { indexing } = useIndexerContext(); if (indexing) { return null; } return <TableOfContents {...props} />; } export const tableOfContentsRenderer = createComponentRenderer( COMP, TableOfContentsMd, ({ className, node, extractValue }) => { return ( <IndexAwareTableOfContents className={className} smoothScrolling={extractValue.asOptionalBoolean(node.props?.smoothScrolling)} maxHeadingLevel={extractValue.asOptionalNumber(node.props?.maxHeadingLevel)} omitH1={extractValue.asOptionalBoolean(node.props?.omitH1)} /> ); }, ); ``` -------------------------------------------------------------------------------- /xmlui/tests/parsers/parameter-parser.test.ts: -------------------------------------------------------------------------------- ```typescript import { describe, expect, it } from "vitest"; import { parseParameterString } from "../../src/components-core/script-runner/ParameterParser"; import { Expression } from "../../src/components-core/script-runner/ScriptingSourceTree"; import { T_BINARY_EXPRESSION, T_LITERAL } from "../../src/parsers/scripting/ScriptingNodeTypes"; describe("parseParameterString", () => { it("Empty string works", () => { // --- Act const result = parseParameterString(""); // --- Assert expect(result.length).toBe(0); }); it("String literal works", () => { // --- Act const result = parseParameterString("abc"); // --- Assert expect(result.length).toBe(1); expect(result[0].type).toBe("literal"); expect(result[0].value).toBe("abc"); }); it("Single expression works", () => { // --- Act const result = parseParameterString("{a+b}"); // --- Assert expect(result.length).toBe(1); expect(result[0].type).toBe("expression"); expect((result[0].value as Expression).type).toBe(T_BINARY_EXPRESSION); }); it("Combination works #1", () => { // --- Act const result = parseParameterString("hello{a+b}"); // --- Assert expect(result.length).toBe(2); expect(result[0].type).toBe("literal"); expect(result[0].value).toBe("hello"); expect(result[1].type).toBe("expression"); expect((result[1].value as Expression).type).toBe(T_BINARY_EXPRESSION); }); it("Combination works #2", () => { // --- Act const result = parseParameterString("{a+b}world"); // --- Assert expect(result.length).toBe(2); expect(result[0].type).toBe("expression"); expect((result[0].value as Expression).type).toBe(T_BINARY_EXPRESSION); expect(result[1].type).toBe("literal"); expect(result[1].value).toBe("world"); }); it("Combination works #3", () => { // --- Act const result = parseParameterString("hello{a+b}world"); // --- Assert expect(result.length).toBe(3); expect(result[0].type).toBe("literal"); expect(result[0].value).toBe("hello"); expect(result[1].type).toBe("expression"); expect((result[1].value as Expression).type).toBe(T_BINARY_EXPRESSION); expect(result[2].type).toBe("literal"); expect(result[2].value).toBe("world"); }); it("Single escape works #1", () => { // --- Act const result = parseParameterString("\\{a+b}"); // --- Assert expect(result.length).toBe(1); expect(result[0].type).toBe("literal"); expect(result[0].value).toBe("{a+b}"); }); it("Single escape works #2", () => { // --- Act const result = parseParameterString("\\{{a+b}"); // --- Assert expect(result.length).toBe(2); expect(result[0].type).toBe("literal"); expect(result[0].value).toBe("{"); expect(result[1].type).toBe("expression"); expect((result[1].value as Expression).type).toBe(T_BINARY_EXPRESSION); }); it("Single escape works #3", () => { // --- Act const result = parseParameterString("/\\{{3}$/"); // --- Assert expect(result.length).toBe(3); expect(result[0].type).toBe("literal"); expect(result[0].value).toBe("/{"); expect(result[1].type).toBe("expression"); expect((result[1].value as Expression).type).toBe(T_LITERAL); expect(result[2].type).toBe("literal"); expect(result[2].value).toBe("$/"); }); }); ``` -------------------------------------------------------------------------------- /xmlui/tests/parsers/paremeter-parser.test.ts: -------------------------------------------------------------------------------- ```typescript import { describe, expect, it } from "vitest"; import { parseParameterString } from "../../src/components-core/script-runner/ParameterParser"; import { Expression } from "../../src/components-core/script-runner/ScriptingSourceTree"; import { T_BINARY_EXPRESSION, T_LITERAL } from "../../src/parsers/scripting/ScriptingNodeTypes"; describe("parseParameterString", () => { it("Empty string works", () => { // --- Act const result = parseParameterString(""); // --- Assert expect(result.length).toBe(0); }); it("String literal works", () => { // --- Act const result = parseParameterString("abc"); // --- Assert expect(result.length).toBe(1); expect(result[0].type).toBe("literal"); expect(result[0].value).toBe("abc"); }); it("Single expression works", () => { // --- Act const result = parseParameterString("{a+b}"); // --- Assert expect(result.length).toBe(1); expect(result[0].type).toBe("expression"); expect((result[0].value as Expression).type).toBe(T_BINARY_EXPRESSION); }); it("Combination works #1", () => { // --- Act const result = parseParameterString("hello{a+b}"); // --- Assert expect(result.length).toBe(2); expect(result[0].type).toBe("literal"); expect(result[0].value).toBe("hello"); expect(result[1].type).toBe("expression"); expect((result[1].value as Expression).type).toBe(T_BINARY_EXPRESSION); }); it("Combination works #2", () => { // --- Act const result = parseParameterString("{a+b}world"); // --- Assert expect(result.length).toBe(2); expect(result[0].type).toBe("expression"); expect((result[0].value as Expression).type).toBe(T_BINARY_EXPRESSION); expect(result[1].type).toBe("literal"); expect(result[1].value).toBe("world"); }); it("Combination works #3", () => { // --- Act const result = parseParameterString("hello{a+b}world"); // --- Assert expect(result.length).toBe(3); expect(result[0].type).toBe("literal"); expect(result[0].value).toBe("hello"); expect(result[1].type).toBe("expression"); expect((result[1].value as Expression).type).toBe(T_BINARY_EXPRESSION); expect(result[2].type).toBe("literal"); expect(result[2].value).toBe("world"); }); it("Single escape works #1", () => { // --- Act const result = parseParameterString("\\{a+b}"); // --- Assert expect(result.length).toBe(1); expect(result[0].type).toBe("literal"); expect(result[0].value).toBe("{a+b}"); }); it("Single escape works #2", () => { // --- Act const result = parseParameterString("\\{{a+b}"); // --- Assert expect(result.length).toBe(2); expect(result[0].type).toBe("literal"); expect(result[0].value).toBe("{"); expect(result[1].type).toBe("expression"); expect((result[1].value as Expression).type).toBe(T_BINARY_EXPRESSION); }); it("Single escape works #3", () => { // --- Act const result = parseParameterString("/\\{{3}$/"); // --- Assert expect(result.length).toBe(3); expect(result[0].type).toBe("literal"); expect(result[0].value).toBe("/{"); expect(result[1].type).toBe("expression"); expect((result[1].value as Expression).type).toBe(T_LITERAL); expect(result[2].type).toBe("literal"); expect(result[2].value).toBe("$/"); }); }); ``` -------------------------------------------------------------------------------- /packages/xmlui-playground/src/playground/Select.module.scss: -------------------------------------------------------------------------------- ```scss @use "xmlui/themes.scss" as themes; .RadixMenuContent { display: flex; flex-direction: column; min-width: 140px; width: fit-content; background-color: themes.$backgroundColor; border-radius: 6px; padding: 6px; gap: 5rem; box-shadow: 0px 10px 38px -10px rgba(22, 23, 24, 0.35), 0px 10px 20px -15px rgba(22, 23, 24, 0.2); animation-duration: 400ms; animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); will-change: transform, opacity; } :is(html[class~=dark] .RadixMenuContent) { background-color: rgba(17,17,17,var(--tw-bg-opacity)); } .SelectViewport { padding: 5px; } :is(html[class~=dark] .SelectViewport) { --tw-bg-opacity: 1; background-color: hsl(var(--nextra-primary-hue)var(--nextra-primary-saturation)77%/.1); } .RadixMenuItem { font-size: 1rem; line-height: 1; --tw-text-opacity: 1; color: rgba(107,114,128,var(--tw-text-opacity)); border-radius: 3px; display: flex; align-items: center; padding: .5rem; position: relative; user-select: none; cursor: pointer; } :is(html[class~=dark] .RadixMenuItem) { --tw-text-opacity: 1; color: rgba(163,163,163,var(--tw-text-opacity)); } .RadixMenuItem[data-highlighted] { outline: none; background-color: rgba(243,244,246,var(--tw-bg-opacity)); color: rgba(51,65,85,var(--tw-text-opacity)); } :is(html[class~=dark] .RadixMenuItem[data-highlighted]) { background-color: hsl(var(--nextra-primary-hue)var(--nextra-primary-saturation)94%/.05); } .RadixMenuItem[data-state=checked] { background-color: hsl(var(--nextra-primary-hue)var(--nextra-primary-saturation)94%/var(--tw-bg-opacity)); --tw-text-opacity: 1; color: hsl(var(--nextra-primary-hue)var(--nextra-primary-saturation)32%/var(--tw-text-opacity)); } :is(html[class~=dark] .RadixMenuItem[data-state=checked]) { --tw-text-opacity: 1; color: hsl(var(--nextra-primary-hue)var(--nextra-primary-saturation)45%/var(--tw-text-opacity)); background-color: hsl(var(--nextra-primary-hue)var(--nextra-primary-saturation)66%/.1); } .SelectLabel { width: 100%; padding: 1rem .5rem .2rem; font-size: 0.875rem; font-weight: 500; line-height: 25px; color: rgba(107,114,128,var(--tw-text-opacity)); } :is(html[class~=dark] .SelectLabel) { --tw-text-opacity: 1; color: rgba(163,163,163,var(--tw-text-opacity)); } .RadixMenuItemIndicator { position: absolute; left: 0; width: 25px; display: inline-flex; align-items: center; justify-content: center; } .RadixMenuContent[data-side='top'] { animation-name: slideDownAndFade; } .RadixMenuContent[data-side='right'] { animation-name: slideLeftAndFade; } .RadixMenuContent[data-side='bottom'] { animation-name: slideUpAndFade; } .RadixMenuContent[data-side='left'] { animation-name: slideRightAndFade; } @keyframes slideUpAndFade { from { opacity: 0; transform: translateY(2px); } to { opacity: 1; transform: translateY(0); } } @keyframes slideRightAndFade { from { opacity: 0; transform: translateX(-2px); } to { opacity: 1; transform: translateX(0); } } @keyframes slideDownAndFade { from { opacity: 0; transform: translateY(-2px); } to { opacity: 1; transform: translateY(0); } } @keyframes slideLeftAndFade { from { opacity: 0; transform: translateX(2px); } to { opacity: 1; transform: translateX(0); } } ``` -------------------------------------------------------------------------------- /xmlui/src/components/App/Sheet.tsx: -------------------------------------------------------------------------------- ```typescript import * as React from "react"; import * as SheetPrimitive from "@radix-ui/react-dialog"; import classnames from "classnames"; import { VisuallyHidden } from "@radix-ui/react-visually-hidden"; import styles from "./Sheet.module.scss"; import { useTheme } from "../../components-core/theming/ThemeContext"; import { Icon } from "../../components/Icon/IconNative"; //based on this: https://ui.shadcn.com/docs/components/sheet const Sheet = SheetPrimitive.Root; const SheetPortal = SheetPrimitive.Portal; const SheetOverlay = React.forwardRef< React.ElementRef<typeof SheetPrimitive.Overlay>, React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay> >(({ className, ...props }, ref) => ( <SheetPrimitive.Overlay className={classnames(styles.overlay, className)} {...props} ref={ref} /> )); SheetOverlay.displayName = SheetPrimitive.Overlay.displayName; interface SheetContentProps extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content> { side: "top" | "bottom" | "left" | "right"; } const SheetContent = React.forwardRef<React.ElementRef<typeof SheetPrimitive.Content>, SheetContentProps>( ({ side = "left", className, children, ...props }, ref) => { const { root } = useTheme(); return ( <SheetPortal container={root}> <SheetOverlay /> <SheetPrimitive.Content forceMount={true} ref={ref} className={classnames( styles.sheetContent, { [styles.top]: side === "top", [styles.bottom]: side === "bottom", [styles.left]: side === "left", [styles.right]: side === "right", }, className )} {...props} > {children} <SheetPrimitive.Close className={styles.close}> <Icon name={"close"} /> <VisuallyHidden>Close</VisuallyHidden> </SheetPrimitive.Close> </SheetPrimitive.Content> </SheetPortal> ); } ); SheetContent.displayName = SheetPrimitive.Content.displayName; const SheetHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => ( <div className={classnames("flex flex-col space-y-2 text-center sm:text-left", className)} {...props} /> ); SheetHeader.displayName = "SheetHeader"; const SheetFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => ( <div className={classnames("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)} {...props} /> ); SheetFooter.displayName = "SheetFooter"; const SheetTitle = React.forwardRef< React.ElementRef<typeof SheetPrimitive.Title>, React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title> >(({ className, ...props }, ref) => ( <SheetPrimitive.Title ref={ref} className={classnames("text-lg font-semibold text-foreground", className)} {...props} /> )); SheetTitle.displayName = SheetPrimitive.Title.displayName; const SheetDescription = React.forwardRef< React.ElementRef<typeof SheetPrimitive.Description>, React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description> >(({ className, ...props }, ref) => ( <SheetPrimitive.Description ref={ref} className={classnames("text-sm text-muted-foreground", className)} {...props} /> )); SheetDescription.displayName = SheetPrimitive.Description.displayName; export { Sheet, SheetContent, }; ``` -------------------------------------------------------------------------------- /xmlui/src/components-core/script-runner/AttributeValueParser.ts: -------------------------------------------------------------------------------- ```typescript import type { ParsedPropertyValue } from "../../abstractions/scripting/Compilation"; import type { Expression } from "./ScriptingSourceTree"; import { Parser } from "../../parsers/scripting/Parser"; let lastParseId = 0; /** * This function parses a parameter string and splits them into string literal and binding expression sections * @param source String to parse * @returns Parameter string sections */ export function parseAttributeValue(source: string): ParsedPropertyValue { const result: ParsedPropertyValue = { __PARSED: true, parseId: ++lastParseId, segments: [], }; if (source === undefined || source === null) return result; let phase = ParsePhase.StringLiteral; let section = ""; let escape = ""; for (let i = 0; i < source.length; i++) { const ch = source[i]; switch (phase) { case ParsePhase.StringLiteral: if (ch === "\\") { phase = ParsePhase.Escape; escape = "\\"; } else if (ch === "{") { // --- A new expression starts, close the previous string literal if (section !== "") { result.segments.push({ literal: section, }); } // --- Start a new section section = ""; phase = ParsePhase.ExprStart; } else { section += ch; } break; case ParsePhase.Escape: if (ch === "\\") { // --- Go on with escape escape += ch; break; } if (ch === "{") { // --- End escape as a literal section without the first "\" escape character section += escape.substring(1) + ch; } else { // --- End escape as a literal section with the full sequence section += escape + ch; } phase = ParsePhase.StringLiteral; break; case ParsePhase.ExprStart: const exprSource = source.substring(i); const parser = new Parser(source.substring(i)); let expr: Expression | null = null; try { expr = parser.parseExpr(); } catch (err) { throw new Error(`Cannot parse expression: '${exprSource}': ${err}`); } const tail = parser.getTail(); if (!tail || tail.trim().length < 1 || tail.trim()[0] !== "}") { // --- Unclosed expression, back to its beginning throw new Error(`Unclosed expression: '${source}'\n'${exprSource}'`); } else { // --- Successfully parsed expression, get dependencies result.segments.push({ expr, }); // --- Skip the parsed part of the expression, and start a new literal section i = source.length - tail.length; section = ""; } phase = ParsePhase.StringLiteral; break; } } // --- Process the last segment switch (phase) { case ParsePhase.StringLiteral: if (section !== "") { result.segments.push({ literal: section, }); } break; case ParsePhase.Escape: result.segments.push({ literal: section + escape, }); break; case ParsePhase.ExprStart: result.segments.push({ literal: section + "{", }); break; } // --- Done. return result; } enum ParsePhase { StringLiteral, Escape, ExprStart, } ``` -------------------------------------------------------------------------------- /xmlui/src/parsers/xmlui-parser/syntax-kind.ts: -------------------------------------------------------------------------------- ```typescript /** tokens and nodes combined. Order is significant since the functions below use the numeric values of this enum to check for a range of possible values*/ export const enum SyntaxKind { Unknown = 0, EndOfFileToken = 1, // Trivia (skipped tokens) CommentTrivia = 2, NewLineTrivia = 3, WhitespaceTrivia = 4, // Effective tokens Identifier = 5, /** < */ OpenNodeStart = 6, /** </ */ CloseNodeStart = 7, /** > */ NodeEnd = 8, /** /> */ NodeClose = 9, /** : */ Colon = 10, /** = */ Equal = 11, /** string literal */ StringLiteral = 12, /** <![CDATA[ ... ]]> */ CData = 13, /** <script>...</script> */ Script = 14, // A token created by the parser which contains arbitrary text, but not the '<' character TextNode = 15, // XMLUI entities AmpersandEntity = 16, LessThanEntity = 17, GreaterThanEntity = 18, SingleQuoteEntity = 19, DoubleQuoteEntity = 20, // Syntax node types ElementNode = 21, AttributeNode = 22, AttributeKeyNode = 23, ContentListNode = 24, AttributeListNode = 25, TagNameNode = 26, // should be the last syntax node type ErrorNode = 27, } export function isTrivia(token: SyntaxKind): boolean { return token >= SyntaxKind.CommentTrivia && token <= SyntaxKind.WhitespaceTrivia; } export function isInnerNode(token: SyntaxKind): boolean { return token >= SyntaxKind.ElementNode && token <= SyntaxKind.ErrorNode; } export function getSyntaxKindStrRepr(kind: SyntaxKind): string { switch (kind) { case SyntaxKind.Unknown: return "Unknown"; case SyntaxKind.EndOfFileToken: return "EndOfFileToken"; case SyntaxKind.CommentTrivia: return "CommentTrivia"; case SyntaxKind.NewLineTrivia: return "NewLineTrivia"; case SyntaxKind.WhitespaceTrivia: return "WhitespaceTrivia"; case SyntaxKind.Identifier: return "Identifier"; case SyntaxKind.OpenNodeStart: return "OpenNodeStart"; case SyntaxKind.CloseNodeStart: return "CloseNodeStart"; case SyntaxKind.NodeEnd: return "NodeEnd"; case SyntaxKind.NodeClose: return "NodeClose"; case SyntaxKind.Colon: return "Colon"; case SyntaxKind.Equal: return "Equal"; case SyntaxKind.StringLiteral: return "StringLiteral"; case SyntaxKind.CData: return "CData"; case SyntaxKind.Script: return "Script"; case SyntaxKind.AmpersandEntity: return "AmpersandEntity"; case SyntaxKind.LessThanEntity: return "LessThanEntity"; case SyntaxKind.GreaterThanEntity: return "GreaterThanEntity"; case SyntaxKind.SingleQuoteEntity: return "SingleQuoteEntity"; case SyntaxKind.DoubleQuoteEntity: return "DoubleQuoteEntity"; case SyntaxKind.ElementNode: return "ElementNode"; case SyntaxKind.AttributeNode: return "AttributeNode"; case SyntaxKind.TextNode: return "TextNode"; case SyntaxKind.ContentListNode: return "ContentListNode"; case SyntaxKind.AttributeListNode: return "AttributeListNode"; case SyntaxKind.TagNameNode: return "TagNameNode"; case SyntaxKind.ErrorNode: return "ErrorNode"; case SyntaxKind.AttributeKeyNode: return "AttributeKeyNode"; } return assertUnreachable(kind); } function assertUnreachable(x: never): never { throw new Error("Didn't expect to get here"); } ``` -------------------------------------------------------------------------------- /docs/content/components/Option.md: -------------------------------------------------------------------------------- ```markdown # Option [#option] `Option` defines selectable items for choice-based components, providing both the underlying value and display text for selection interfaces. It serves as a non-visual data structure that describes individual choices within [Select](/components/Select), [AutoComplete](/components/AutoComplete), and other selection components. **Key features:** - **Value and label separation**: Define what gets stored (value) separately from what users see (label) - **Automatic fallbacks**: Uses label as value or value as label when only one is provided - **Custom templates**: Support for rich content via optionTemplate property or child components - **State management**: Built-in enabled/disabled states for individual options - **Data integration**: Works seamlessly with Items components for dynamic option lists ## Using `Option` [#using-option] ### With `AutoComplete` [#with-autocomplete] ```xmlui-pg copy {4-6} display name="Example: Option in a AutoComplete" height="275px" <App> <Text value="Selected ID: {myComp.value}"/> <AutoComplete id="myComp"> <Option label="John, Smith" value="john" /> <Option label="Jane, Clint" value="jane" disabled="true" /> <Option label="Herbert, Frank" value="herbert" /> </AutoComplete> </App> ``` ### With `Select` [#with-select] ```xmlui-pg copy {4-6} display name="Example: Option in a Select" height="275px" <App> <Text value="Selected ID: {mySelect.value}"/> <Select id="mySelect"> <Option label="John, Smith" value="john" /> <Option label="Jane, Clint" value="jane" /> <Option label="Herbert, Frank" value="herbert" /> </Select> </App> ``` ## Properties [#properties] ### `enabled` (default: true) [#enabled-default-true] This boolean property indicates whether the option is enabled or disabled. ### `keywords` [#keywords] An array of keywords that can be used for searching and filtering the option. These keywords are not displayed but help users find the option through search. ### `label` [#label] This property defines the text to display for the option. If `label` is not defined, `Option` will use the `value` as the label. >[!INFO] > If `Option` does not define any of the `label` or `value` properties, the option will not be rendered. ```xmlui-pg copy display name="Example: Using label" height="275px" <App> <Text value="Selected ID: {mySelect.value}"/> <Select id="mySelect"> <Option /> <Option label="Vanilla" value="van"/> <Option label="Chocolate" value="choc" /> <Option value="pist" /> </Select> </App> ``` ### `value` [#value] This property defines the value of the option. If `value` is not defined, `Option` will use the `label` as the value. If neither is defined, the option is not displayed. >[!INFO] > If `Option` does not define any of the `label` or `value` properties, the option will not be rendered. ```xmlui-pg copy display name="Example: Using value" height="275px" <App> <Text value="Selected ID: {mySelect.value}"/> <Select id="mySelect"> <Option /> <Option label="Vanilla" /> <Option label="Chocolate" value="chocolate" /> <Option label="Pistachio" value="pistachio" /> </Select> </App> ``` ## Events [#events] This component does not have any events. ## Exposed Methods [#exposed-methods] This component does not expose any methods. ## Styling [#styling] This component does not have any styles. ``` -------------------------------------------------------------------------------- /docs/public/pages/howto/handle-background-operations.md: -------------------------------------------------------------------------------- ```markdown # Handle background operations Use the Queue component for async processing that doesn't block user interaction. ```xmlui-pg copy display name="Background file processing with progress feedback" ---comp display {21-30} /uploadQueue/ <Component name="BackgroundProcessor" var.items="{[]}" var.processedCount="{0}" var.errorCount="{0}" var.completed="{false}"> <VStack gap="$space-4"> <!-- Single action button --> <Button label="Upload 5 Files" onClick="items = [ { id: 1, filename: 'document.pdf', size: 2048576, type: 'application/pdf' }, { id: 2, filename: 'image.jpg', size: 1024000, type: 'image/jpeg' }, { id: 3, filename: 'corrupted-file.txt', size: 512, type: 'text/plain' }, { id: 4, filename: 'data.csv', size: 4096000, type: 'text/csv' }, { id: 5, filename: 'presentation.pptx', size: 8192000, type: 'application/vnd.openxmlformats-officedocument.presentationml.presentation' } ]; uploadQueue.enqueueItems(items)" enabled="{!completed}" themeColor="primary" /> <!-- Background upload queue --> <Queue id="uploadQueue" clearAfterFinish="true" onProcess="processing => { // Update progress processing.reportProgress(processedCount + 1); // Make API call to upload objects (delay happens server-side) const result = Actions.callApi({ url: '/api/objects', method: 'POST', body: processing.item }); processedCount++; return result; }" onProcessError="(error, processing) => { errorCount++; console.error('Processing failed:', error.message, processing.item); // Return true to show default error display return true; }" onComplete="() => { console.log('All files processed'); completed = true; }"> <property name="progressFeedback"> <HStack> <Spinner size="sm" /> <Text>Processing item {processedCount + 1}...</Text> </HStack> </property> <property name="resultFeedback"> <HStack> <Icon name="checkmark"/> <Text> All {processedCount} items processed successfully! </Text> </HStack> </property> </Queue> <!-- Status display --> <Card when="{uploadQueue.getQueueLength() > 0 || processedCount > 0}"> <VStack> <HStack> <Text>Queue length: {uploadQueue.getQueueLength()}</Text> <Text>Processed: {processedCount}</Text> </HStack> </VStack> </Card> <!-- Interactive element while processing --> <Card when="{uploadQueue.getQueueLength() > 0}"> <VStack> <Slider label="Adjust slider while uploads are running" minValue="1" maxValue="10" initialValue="5" step="1" /> </VStack> </Card> </VStack> </Component> ---app display <App> <BackgroundProcessor /> </App> ---api { "apiUrl": "/api", "initialize": "$state.files = []", "operations": { "process-file": { "url": "/objects", "method": "post", "bodyParamTypes": { "filename": "string", "size": "number", "type": "string" }, "handler": "delay(3000); return { success: true, message: 'File processed successfully' };" } } } ``` ``` -------------------------------------------------------------------------------- /xmlui/src/components-core/interception/Errors.ts: -------------------------------------------------------------------------------- ```typescript //stolen from axios export enum HttpStatusCode { Continue = 100, SwitchingProtocols = 101, Processing = 102, EarlyHints = 103, Ok = 200, Created = 201, Accepted = 202, NonAuthoritativeInformation = 203, NoContent = 204, ResetContent = 205, PartialContent = 206, MultiStatus = 207, AlreadyReported = 208, ImUsed = 226, MultipleChoices = 300, MovedPermanently = 301, Found = 302, SeeOther = 303, NotModified = 304, UseProxy = 305, Unused = 306, TemporaryRedirect = 307, PermanentRedirect = 308, BadRequest = 400, Unauthorized = 401, PaymentRequired = 402, Forbidden = 403, NotFound = 404, MethodNotAllowed = 405, NotAcceptable = 406, ProxyAuthenticationRequired = 407, RequestTimeout = 408, Conflict = 409, Gone = 410, LengthRequired = 411, PreconditionFailed = 412, PayloadTooLarge = 413, UriTooLong = 414, UnsupportedMediaType = 415, RangeNotSatisfiable = 416, ExpectationFailed = 417, ImATeapot = 418, MisdirectedRequest = 421, UnprocessableEntity = 422, Locked = 423, FailedDependency = 424, TooEarly = 425, UpgradeRequired = 426, PreconditionRequired = 428, TooManyRequests = 429, RequestHeaderFieldsTooLarge = 431, UnavailableForLegalReasons = 451, InternalServerError = 500, NotImplemented = 501, BadGateway = 502, ServiceUnavailable = 503, GatewayTimeout = 504, HttpVersionNotSupported = 505, VariantAlsoNegotiates = 506, InsufficientStorage = 507, LoopDetected = 508, NotExtended = 510, NetworkAuthenticationRequired = 511, } export class HttpError extends Error { status; details; constructor(status: number, details?: Record<string, any>) { super(details?.message || "Not found"); this.details = details; this.status = status; Object.setPrototypeOf(this, HttpError.prototype); } } export class NotFoundError extends HttpError { constructor(details?: Record<string, any>) { super(HttpStatusCode.NotFound, details); Object.setPrototypeOf(this, NotFoundError.prototype); } } export class UnauthorizedError extends HttpError { constructor(details?: Record<string, any>) { super(HttpStatusCode.Unauthorized, details); Object.setPrototypeOf(this, UnauthorizedError.prototype); } } export class ConflictError extends HttpError { constructor(details?: Record<string, any>) { super(HttpStatusCode.Conflict, details); Object.setPrototypeOf(this, ConflictError.prototype); } } function convertErrorDetails(messageOrDetails: string | Record<string, any>) { let details; if (messageOrDetails) { if (typeof messageOrDetails === "string") { details = { message: messageOrDetails, }; } else { details = messageOrDetails; } } return details; } const Errors = { NotFound404: (messageOrDetails: string | Record<string, any>) => { return new NotFoundError(convertErrorDetails(messageOrDetails)); }, Unauthorized401: (messageOrDetails: string | Record<string, any>) => { return new UnauthorizedError(convertErrorDetails(messageOrDetails)); }, HttpError: (errorCode: number, messageOrDetails: string | Record<string, any>) => { return new HttpError(errorCode, convertErrorDetails(messageOrDetails)); }, Conflict409: (messageOrDetails: string | Record<string, any>) => { return new ConflictError(convertErrorDetails(messageOrDetails)); } }; export default Errors; ``` -------------------------------------------------------------------------------- /docs/public/pages/modal-dialogs.md: -------------------------------------------------------------------------------- ```markdown # Modal Dialogs A `ModalDialog` can be invoked **declaratively** in markup or **imperatively** from code. This is the declarative method. You don't need to invoke the `ModalDialog`'s `open()` and `close()` functions directly. The `when` attribute controls opening and closing. ```xmlui-pg display {2, 3, 19} <App> <variable name="isDialogShown" value="{false}"/> <ModalDialog when="{isDialogShown}" onClose="{ isDialogShown = false }"> Leslie is always number one to the coffee machine. He has a competitive personality but gets along with a lot people. </ModalDialog> <NavPanel> <NavLink label="Users" to="/" icon="user" /> </NavPanel> <Pages> <Page url="/"> <Card avatarUrl="https://i.pravatar.cc/100" title="Leslie Peters" subtitle="Executive Manager"> Leslie is pretty smart when it comes to business. <Button label="Details" onClick="isDialogShown = true" /> </Card> </Page> </Pages> </App> ``` This is the imperative method. You invoke `ModalDialog`'s `open()` and `close()` functions explicitly via its ID. ```xmlui-pg display {3, 7, 19} <App> <ModalDialog id="dialog" title="Leslie Peters"> Leslie is always number one to the coffee machine. He has a competitive personality but gets along with a lot people. <Button label="Close" onClick="dialog.close()" /> </ModalDialog> <NavPanel> <NavLink label="Users" to="/" icon="user" /> </NavPanel> <Pages> <Page url="/"> <Card avatarUrl="https://i.pravatar.cc/100" title="Leslie Peters" subtitle="Executive Manager"> Leslie is pretty smart when it comes to business. <Button label="Details" onClick="dialog.open()" /> </Card> </Page> </Pages> </App> ``` When embedding a form in a dialog, the form's cancel and successful submit actions automatically close the dialog hosting the form (unless you change this logic). Note that you can pass data via `dialog.open()`, `ModalDialog` receives it as `$param`. ```xmlui-pg display {3, 23} height="400px" <App> <ModalDialog id="dialog"> <Text> ID: { $param } </Text> <Form data="{{ name: 'Leslie', age: 32 }}" onSubmit="(formData) => console.log(formData)" > <FormItem bindTo="name" label="User Name" /> <FormItem bindTo="age" label="Age" type="integer" zeroOrPositive="true" /> </Form> </ModalDialog> <NavPanel> <NavLink label="Users" to="/" icon="user" /> </NavPanel> <Pages> <Page url="/"> <variable name="employeeId" value="{ 123 }" /> <Card avatarUrl="https://i.pravatar.cc/100" title="Leslie Peters" subtitle="Executive Manager"> Leslie is pretty smart when it comes to business. <Button label="Details" onClick="dialog.open(employeeId)" /> </Card> </Page> </Pages> </App> ``` `ModalDialog` supports a few kinds of customization. For example, you can hide the close button displayed in the top-right dialog corner and add a restyled title to dialogs. ```xmlui-pg display height="220px" <App> <Button label="Open Dialog" onClick="dialog.open()" /> <ModalDialog id="dialog" title="Example Dialog" closeButtonVisible="false"> <Button label="Close Dialog" onClick="dialog.close()" /> </ModalDialog> </App> ``` See the [ModalDialog](/components/ModalDialog) reference for all properties and events. ``` -------------------------------------------------------------------------------- /xmlui/src/components/Accordion/AccordionItemNative.tsx: -------------------------------------------------------------------------------- ```typescript import { type ForwardedRef, forwardRef, type ReactNode, useEffect, useId, useMemo, useState, } from "react"; import * as RAccordion from "@radix-ui/react-accordion"; import classnames from "classnames"; import styles from "../../components/Accordion/Accordion.module.scss"; import { useAccordionContext } from "../../components/Accordion/AccordionContext"; import Icon from "../../components/Icon/IconNative"; function defaultRenderer(header: string) { return <div>{header}</div>; } type Props = { id: string; /** * The header of the accordion. */ header: string; headerRenderer?: (header: string) => ReactNode; /** * The content of the accordion. */ content: ReactNode; initiallyExpanded?: boolean; style?: React.CSSProperties; className?: string; }; export const defaultProps: Pick<Props, "initiallyExpanded" | "headerRenderer"> = { initiallyExpanded: false, headerRenderer: defaultRenderer, }; export const AccordionItemComponent = forwardRef(function AccordionItemComponent( { id, header, headerRenderer = defaultProps.headerRenderer, content, initiallyExpanded = defaultProps.initiallyExpanded, style, className, ...rest }: Props, forwardedRef: ForwardedRef<HTMLDivElement>, ) { const generatedId = useId(); const itemId = useMemo(() => (id ? `${id}` : generatedId), [id, generatedId]); const triggerId = useMemo(() => `trigger_${itemId}`, [itemId]); const { rotateExpanded, expandedItems, hideIcon, expandedIcon, collapsedIcon, triggerPosition, expandItem, register, unRegister, } = useAccordionContext(); const expanded = useMemo(() => (expandedItems ?? []).includes(itemId), [itemId, expandedItems]); const [initialised, setInitialised] = useState(false); useEffect(() => { if (!initialised) { setInitialised(true); if (initiallyExpanded) { expandItem(itemId); } } }, [expandItem, itemId, initiallyExpanded, initialised]); useEffect(() => { register(triggerId); }, [register, triggerId]); useEffect(() => { return () => { unRegister(triggerId); }; }, [triggerId, unRegister]); return ( <RAccordion.Item id={itemId} key={itemId} value={itemId} className={classnames(styles.item, className)} ref={forwardedRef} style={style} > <RAccordion.Header className={styles.header}> <RAccordion.Trigger {...rest} id={triggerId} className={classnames(styles.trigger, { [styles.triggerStart]: triggerPosition === "start", })} > {headerRenderer(header)} {!hideIcon && ( <span style={{ transform: expanded && !expandedIcon ? `rotate(${rotateExpanded})` : "rotate(0deg)", transition: "transform 300ms cubic-bezier(0.87, 0, 0.13, 1)", }} > <Icon name={!expanded ? collapsedIcon : expandedIcon || collapsedIcon} className={styles.chevron} aria-hidden="true" /> </span> )} </RAccordion.Trigger> </RAccordion.Header> <RAccordion.Content className={styles.contentWrapper}> <div className={styles.content}>{content}</div> </RAccordion.Content> </RAccordion.Item> ); }); ``` -------------------------------------------------------------------------------- /xmlui/src/components/CodeBlock/CodeBlock.module.scss: -------------------------------------------------------------------------------- ```scss @use "../../components-core/theming/themes" as t; $themeVars: (); @function createThemeVar($componentVariable) { $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global; @return t.getThemeVar($themeVars, $componentVariable); } $themeVars: t.composePaddingVars($themeVars, "CodeBlock"); $themeVars: t.composeBorderVars($themeVars, "CodeBlock"); $backgroundColor-CodeBlock: createThemeVar("backgroundColor-CodeBlock"); $backgroundColor-CodeBlock-header: createThemeVar("backgroundColor-CodeBlock-header"); $color-CodeBlock-headerSeparator: createThemeVar("color-CodeBlock-headerSeparator"); $marginTop-CodeBlock: createThemeVar("marginTop-CodeBlock"); $marginBottom-CodeBlock: createThemeVar("marginBottom-CodeBlock"); $backgroundColor-CodeBlock-highlightRow: createThemeVar("backgroundColor-CodeBlock-highlightRow"); $backgroundColor-CodeBlock-highlightString: createThemeVar("backgroundColor-CodeBlock-highlightString"); $borderColor-CodeBlock-highlightString-emphasis: createThemeVar("borderColor-CodeBlock-highlightString-emphasis"); $borderRadius-CodeBlock: createThemeVar("borderRadius-CodeBlock"); $border-CodeBlock: createThemeVar("border-CodeBlock"); $boxShadow-CodeBlock: createThemeVar("boxShadow-CodeBlock"); $height-CodeBlock: createThemeVar("height-CodeBlock"); $paddingHorizontal-content-CodeBlock: createThemeVar("paddingHorizontal-content-CodeBlock"); $paddingVertical-content-CodeBlock: createThemeVar("paddingVertical-content-CodeBlock"); @layer components { .codeBlock { @include t.borderVars($themeVars, "CodeBlock"); @include t.paddingVars($themeVars, "CodeBlock"); margin-top: $marginTop-CodeBlock; margin-bottom: $marginBottom-CodeBlock; background-color: $backgroundColor-CodeBlock; height: $height-CodeBlock; border-radius: $borderRadius-CodeBlock; //overflow: hidden; border: $border-CodeBlock; box-shadow: $boxShadow-CodeBlock; } .copyButton { opacity: 0.7; &:hover { opacity: 1; } } .codeBlockHeader { padding: t.$space-1; padding-left: t.$space-3; background-color: $backgroundColor-CodeBlock-header; border-bottom: $color-CodeBlock-headerSeparator solid 2px; font-size: t.$fontSize-sm; border-start-start-radius: t.getThemeVar($themeVars, "borderStartStartRadius-CodeBlock"); border-start-end-radius: t.getThemeVar($themeVars, "borderStartEndRadius-CodeBlock"); } .codeBlockContent { padding: $paddingVertical-content-CodeBlock $paddingHorizontal-content-CodeBlock; position: relative; display: flex; //align-items: center; min-height: 48px; height: 100%; overflow: auto; .codeBlockCopyButton { position: absolute; top: t.$space-1_5; right: t.$space-1_5; z-index: 1; display: none; background-color: $backgroundColor-CodeBlock; } &:hover { .codeBlockCopyButton { display: block; } } pre { flex-grow: 1; } } :global { .codeBlockHighlightRow { background-color: $backgroundColor-CodeBlock-highlightRow; } .codeBlockHighlightString { background-color: $backgroundColor-CodeBlock-highlightString; padding: 1px; } .codeBlockHighlightStringEmphasis { border: 2px solid $borderColor-CodeBlock-highlightString-emphasis; border-radius: 2px; padding: 1px; } } } :export { themeVars: t.json-stringify($themeVars); } ``` -------------------------------------------------------------------------------- /docs/content/components/Bookmark.md: -------------------------------------------------------------------------------- ```markdown # Bookmark [#bookmark] As its name suggests, this component places a bookmark into its parent component's view. The component has an `id` that you can use in links to navigate (scroll to) the bookmark's location. > [!INFO] > Pop out the examples in this article to view them on full screen. ## Using Bookmark [#using-bookmark] Use `Bookmark` as a standalone tag or wrap children with it. > [!INFO] > We suggest using a standalone bookmark, which does not increase the nesting depth of the source code, whenever possible. Note that a standalone bookmark will act as an additional child for its parent component, which can affect the layout (a `Stack` puts `gap`s between `Bookmark`s too). ### Standalone [#standalone] Add an `id` property to `Bookmark` instances and use the same identifiers in links with hash tags, as the following example shows: ```xmlui-pg copy display height="320px" name="Example: standalone Bookmark" ---app display copy <App layout="vertical-full-header" scrollWholePage="false"> <NavPanel> <Link to="/#red">Jump to red</Link> <Link to="/#green">Jump to green</Link> <Link to="/#blue">Jump to blue</Link> </NavPanel> <Pages> <Page url="/"> <Bookmark id="red"> <VStack height="200px" backgroundColor="red" /> </Bookmark> <Bookmark id="green"> <VStack height="200px" backgroundColor="green" /> </Bookmark> <Bookmark id="blue"> <VStack height="200px" backgroundColor="blue" /> </Bookmark> </Page> </Pages> </App> ---desc Clicking a link scrolls the bookmarked component adjacent to the corresponding `Bookmark` tag into the view: ``` ### With nested children [#with-nested-children] Alternatively, you can nest components into `Bookmark`: ```xmlui-pg copy display height="320px" name="Example: Bookmark with nested children" ---app display copy <App layout="vertical-full-header" scrollWholePage="false"> <NavPanel> <Link to="/#red">Jump to red</Link> <Link to="/#green">Jump to green</Link> <Link to="/#blue">Jump to blue</Link> </NavPanel> <Pages> <Page url="/"> <Bookmark id="red"> <VStack height="200px" backgroundColor="red" /> </Bookmark> <Bookmark id="green"> <VStack height="200px" backgroundColor="green" /> </Bookmark> <Bookmark id="blue"> <VStack height="200px" backgroundColor="blue" /> </Bookmark> </Page> </Pages> </App> ---desc You can try; this example works like the previous one: ``` ## Properties [#properties] ### `id` [#id] The unique identifier of the bookmark. You can use this identifier in links to navigate to this component's location. If this identifier is not set, you cannot programmatically visit this bookmark. ### `level` (default: 1) [#level-default-1] The level of the bookmark. The level is used to determine the bookmark's position in the table of contents. ### `omitFromToc` (default: false) [#omitfromtoc-default-false] If true, this bookmark will be excluded from the table of contents. ### `title` [#title] Defines the text to display the bookmark in the table of contents. If this property is empty, the text falls back to the value of `id`. ## Events [#events] This component does not have any events. ## Exposed Methods [#exposed-methods] ### `scrollIntoView` [#scrollintoview] Scrolls the bookmark into view. **Signature**: `scrollIntoView()` ## Styling [#styling] This component does not have any styles. ``` -------------------------------------------------------------------------------- /xmlui/src/components-core/loader/ExternalDataLoader.tsx: -------------------------------------------------------------------------------- ```typescript import { useCallback } from "react"; import type { LoaderErrorFn, LoaderInProgressChangedFn, LoaderLoadedFn, } from "../abstractions/LoaderRenderer"; import type { ComponentDef} from "../../abstractions/ComponentDefs"; import type { ContainerState } from "../rendering/ContainerWrapper"; import { removeNullProperties } from "../utils/misc"; import { extractParam } from "../utils/extractParam"; import { createLoaderRenderer } from "../renderers"; import { useAppContext } from "../AppContext"; import { Loader } from "./Loader"; import { createMetadata, d } from "../../components/metadata-helpers"; /** * Properties of the Data loader component */ type ExternalDataLoaderProps = { loader: ExternalDataLoaderDef; state: ContainerState; doNotRemoveNulls?: boolean; loaderInProgressChanged: LoaderInProgressChangedFn; loaderIsRefetchingChanged: LoaderInProgressChangedFn; loaderLoaded: LoaderLoadedFn; loaderError: LoaderErrorFn; structuralSharing?: boolean; }; /** * Represents a non-displayed React component, which handles the specified API loader */ function ExternalDataLoader({ loader, loaderInProgressChanged, loaderIsRefetchingChanged, loaderError, loaderLoaded, state, doNotRemoveNulls, structuralSharing = true, }: ExternalDataLoaderProps) { const appContext = useAppContext(); const method = extractParam(state, loader.props.method, appContext); const headers: Record<string, string> = extractParam(state, loader.props.headers, appContext); const data = extractParam(state, loader.props.data, appContext); const url = extractParam(state, loader.props.url, appContext); const urlLoadable = !!url; const doLoad = useCallback(async () => { if (!urlLoadable) { return; } const response = await fetch(url, { method: method || "POST", headers: { "Content-Type": "application/json", ...headers, }, body: JSON.stringify(data), }); const responseObj = await response.json(); if (!doNotRemoveNulls) { removeNullProperties(responseObj); } return responseObj; }, [urlLoadable, headers, data, url, method, doNotRemoveNulls]); return ( <Loader state={state} loader={loader} loaderInProgressChanged={loaderInProgressChanged} loaderIsRefetchingChanged={loaderIsRefetchingChanged} loaderLoaded={loaderLoaded} loaderError={loaderError} loaderFn={doLoad} structuralSharing={structuralSharing} /> ); } export const ExternalDataLoaderMd = createMetadata({ status: "stable", description: `Represents a loader that calls an API through an HTTP/HTTPS GET request`, props: { url: d("URL segment to use in the GET request"), method: d("The HTTP method to use"), headers: d("Headers to send with the request"), data: d("The body of the request to be sent as JSON"), }, }); type ExternalDataLoaderDef = ComponentDef<typeof ExternalDataLoaderMd>; export const externalDataLoaderRenderer = createLoaderRenderer( "ExternalDataLoader", ({ loader, state, loaderInProgressChanged, loaderIsRefetchingChanged, loaderError, loaderLoaded }) => { return ( <ExternalDataLoader loader={loader} state={state} loaderInProgressChanged={loaderInProgressChanged} loaderIsRefetchingChanged={loaderIsRefetchingChanged} loaderLoaded={loaderLoaded} loaderError={loaderError} /> ); }, ExternalDataLoaderMd, ); ```