This is page 21 of 140. Use http://codebase.md/xmlui-org/xmlui/mockApiDef.js?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 │ │ │ │ ├── make-navpanel-width-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/testing/themed-app-test-helpers.ts: -------------------------------------------------------------------------------- ```typescript import type { BrowserContext, Locator, Page } from "@playwright/test"; import { xmlUiMarkupToComponent } from "../components-core/xmlui-parser"; import type { ThemeDefinition } from "../abstractions/ThemingDefs"; import type { ComponentDef, CompoundComponentDef } from "../abstractions/ComponentDefs"; import type { StandaloneAppDescription } from "../components-core/abstractions/standalone"; type EntryPoint = string | Record<string, any>; type UnparsedAppDescription = Omit<Partial<StandaloneAppDescription>, "entryPoint"> & { entryPoint?: EntryPoint; }; export type ThemeTestDesc = { themeVar: string; themeVarAsCSS: string; expected: string; dependsOnVars?: Record<string, string>; }; function parseComponent(entryPoint: ComponentDef<any> | string) { if (typeof entryPoint === "string") { return xmlUiMarkupToComponent(entryPoint).component; } return entryPoint; } export async function initApp( page: Page, appDescription: UnparsedAppDescription, url: string = "/", resources = {}, ) { const { entryPoint, components } = appDescription; const _appDescription: StandaloneAppDescription = { ...appDescription, name: appDescription.name || "Test App", entryPoint: parseComponent(entryPoint as ComponentDef) as ComponentDef, resources, components: (!components ? undefined : Array.isArray(components) ? components.map((comp) => parseComponent(comp as unknown as ComponentDef)) : [parseComponent(components)]) as CompoundComponentDef[], }; await page.addInitScript((app) => { // @ts-ignore window.TEST_ENV = app; }, _appDescription); await page.goto(url); } export async function prepPage( co: BrowserContext, appDesc: StandaloneAppDescription, url: string = "/", ) { const context = co; const page = await context.newPage(); await initApp(page, appDesc, url); return page; } export async function initThemedApp( page: Page, entryPoint: EntryPoint, theme: Partial<ThemeDefinition>, ) { theme.id ??= "testTheme"; theme.name ??= "Custom Test theme"; await initApp(page, { entryPoint, defaultTheme: theme.id, themes: [theme as any] }); } /** * @param percentage a percenage value as a string, like "40%" * @param scalarOf100Percent The value to multiply the percentage by */ export function scalePercentBy(scalarOf100Percent: number, percentage: string) { if (!percentage.endsWith("%")) { throw new Error("argument doesn't end with % sign"); } const percentageNum = Number(percentage.slice(0, -1)); return (scalarOf100Percent / 100) * percentageNum; } export function getBoundingRect(locator: Locator) { return locator.evaluate((element) => element.getBoundingClientRect()); } export async function getFullRectangle(locator: Locator) { const boundingRect = await locator.evaluate((element) => element.getBoundingClientRect()); const margins = await getElementStyles(locator, [ "margin-left", "margin-right", "margin-top", "margin-bottom", ]); const marginLeft = parseFloat(margins["margin-left"]); const marginRight = parseFloat(margins["margin-right"]); const marginTop = parseFloat(margins["margin-top"]); const marginBottom = parseFloat(margins["margin-bottom"]); const width = boundingRect.width + marginLeft + marginRight; const height = boundingRect.height + marginTop + marginBottom; const left = boundingRect.left - marginLeft; const right = boundingRect.right + marginRight; const top = boundingRect.top - marginTop; const bottom = boundingRect.bottom + marginBottom; return { width, height, left, right, top, bottom }; } export async function isElementOverflown(locator: Locator, direction: "x" | "y" | "both" = "both") { const [width, height, scrollWidth, scrollHeight] = await locator.evaluate((element) => [ element.clientWidth, element.clientHeight, element.scrollWidth, element.scrollHeight, ]); if (direction === "x") return scrollWidth > width; if (direction === "y") return scrollHeight > height; return scrollWidth > width && scrollHeight > height; } export function pixelStrToNum(pixelStr: string) { return Number(pixelStr.replace("px", "")); } export function getElementStyle(locator: Locator, style: string) { return locator.evaluate( (element, style) => window.getComputedStyle(element).getPropertyValue(style), style, ); } /** * Retreives all the provided style properties from the locator * @returns an object with the keys being the elements of the styles argument */ export function getElementStyles(locator: Locator, styles: string[] = []) { return locator.evaluate( (element, styles) => Object.fromEntries( styles.map((styleName) => [ styleName, window.getComputedStyle(element).getPropertyValue(styleName), ]), ), styles, ); } export async function getStyle(page: Page, testId: string, style: string) { const locator = page.getByTestId(testId); return await getElementStyle(locator, style); } ``` -------------------------------------------------------------------------------- /xmlui/src/components/ExpandableItem/ExpandableItem.tsx: -------------------------------------------------------------------------------- ```typescript import styles from "./ExpandableItem.module.scss"; import { createComponentRenderer } from "../../components-core/renderers"; import { parseScssVar } from "../../components-core/theming/themeVars"; import { iconPositionMd } from "../abstractions"; import { createMetadata, d, dComponent } from "../../components/metadata-helpers"; import { defaultExpandableItemProps, ExpandableItem } from "./ExpandableItemNative"; const COMP = "ExpandableItem"; export const ExpandableItemMd = createMetadata({ status: "stable", description: "`ExpandableItem` creates expandable/collapsible section, similar to the HTML " + "details disclosure element. When the user clicks on the `summary` the content " + "expands or collapses.", props: { summary: dComponent("The summary content that is always visible and acts as the trigger."), initiallyExpanded: { description: "Determines if the component is initially expanded when rendered.", valueType: "boolean", defaultValue: defaultExpandableItemProps.initiallyExpanded, }, enabled: { description: "When true, the expandable item can be opened and closed. When false, it cannot be toggled.", valueType: "boolean", defaultValue: defaultExpandableItemProps.enabled, }, iconCollapsed: { description: "The icon to display when the item is collapsed.", valueType: "string", defaultValue: defaultExpandableItemProps.iconCollapsed, }, iconExpanded: { description: "The icon to display when the item is expanded.", valueType: "string", defaultValue: defaultExpandableItemProps.iconExpanded, }, iconPosition: { description: "Determines the position of the icon (start or end).", valueType: "string", availableValues: iconPositionMd, defaultValue: defaultExpandableItemProps.iconPosition, }, withSwitch: { description: "When true, a switch is used instead of an icon to toggle the expanded state.", valueType: "boolean", defaultValue: defaultExpandableItemProps.withSwitch, }, }, events: { expandedChange: d( `This event fires when the expandable item is expanded or collapsed. It provides a boolean value indicating the new state.`, ), }, apis: { expand: { description: `This method expands the item.`, signature: "expand(): void", }, collapse: { description: `This method collapses the item.`, signature: "collapse(): void", }, toggle: { description: `This method toggles the item's expanded state.`, signature: "toggle(): void", }, isExpanded: { description: `This method returns a boolean indicating whether the item is currently expanded.`, signature: "isExpanded(): boolean", }, }, themeVars: parseScssVar(styles.themeVars), limitThemeVarsToComponent: true, defaultThemeVars: { [`backgroundColor-${COMP}`]: "transparent", [`color-${COMP}`]: "$textColor-primary", [`color-${COMP}--disabled`]: "$textColor--disabled", [`fontFamily-${COMP}`]: "$fontFamily", [`borderColor-${COMP}`]: "$borderColor", [`borderWidth-${COMP}`]: "0", [`borderBottomWidth-${COMP}`]: "1px", [`borderStyle-${COMP}`]: "solid", [`borderRadius-${COMP}`]: "0", [`paddingTop-${COMP}`]: "$space-2", [`paddingBottom-${COMP}`]: "$space-2", [`paddingLeft-${COMP}`]: "$space-0", [`paddingRight-${COMP}`]: "$space-0", [`gap-${COMP}`]: "$space-2", [`paddingLeft-content-${COMP}`]: "$space-3", [`paddingRight-content-${COMP}`]: "$space-3", [`paddingVertical-content-${COMP}`]: "$space-2", [`transition-${COMP}`]: "0.2s ease", }, }); export const expandableItemComponentRenderer = createComponentRenderer( COMP, ExpandableItemMd, ({ node, renderChild, lookupEventHandler, registerComponentApi, extractValue, className }) => { return ( <ExpandableItem summary={extractValue(node.props?.summary)} initiallyExpanded={extractValue.asOptionalBoolean( node.props.initiallyExpanded, defaultExpandableItemProps.initiallyExpanded, )} enabled={extractValue.asOptionalBoolean( node.props.enabled, defaultExpandableItemProps.enabled, )} iconExpanded={ extractValue(node.props?.iconExpanded) ?? defaultExpandableItemProps.iconExpanded } iconCollapsed={ extractValue(node.props?.iconCollapsed) ?? defaultExpandableItemProps.iconCollapsed } iconPosition={ extractValue.asOptionalString(node.props.iconPosition) ?? defaultExpandableItemProps.iconPosition } withSwitch={extractValue.asOptionalBoolean( node.props.withSwitch, defaultExpandableItemProps.withSwitch, )} onExpandedChange={lookupEventHandler("expandedChange")} className={className} registerComponentApi={registerComponentApi} > {renderChild(node.children)} </ExpandableItem> ); }, ); ``` -------------------------------------------------------------------------------- /docs/content/components/Slot.md: -------------------------------------------------------------------------------- ```markdown # Slot [#slot] Placeholder in a reusable component. Signs the slot where the component's injected children should be rendered. ## Using Slot [#using-slot] You can add `Slot` to a user-defined component as a placeholder. When you refer to the particular component in the markup, the children are transposed to the `Slot`. ```xmlui-pg name="Using Slot" ---app copy display {3-5} <App name="XMLUI Hello World"> <ActionBar> <Button label="Create" onClick="window.alert('Create clicked')" /> <Button label="Edit" onClick="window.alert('Edit clicked')" /> <Button label="Delete" onClick="window.alert('Delete clicked')" /> </ActionBar> </App> ---desc The app flows down three buttons to the `ActionBar` to render. ---comp copy display {5} <Component name="ActionBar"> <Card> <H3>Use these actions</H3> <HStack> <Slot /> </HStack> </Card> </Component> ---desc `ActionBar` renders the passed children by replacing `Slot` with them. ``` ## Default Slot content [#default-slot-content] You can provide default content for the `Slot`. If the user-defined component does not have any children, XMLUI will render the default content. ```xmlui-pg ---app copy display name="Define default Slot content" <App> <ActionBar /> </App> ---comp copy display {6} <Component name="ActionBar"> <Card> <H3>Use these actions</H3> <HStack> <Slot> <Button label="Default" onClick="window.alert('Default clicked')" /> </Slot> </HStack> </Card> </Component> ``` ## Named Slots [#named-slots] You can add multiple slots to a user-defined component; you can have a default slot and several *named* slots. Slot names should end with `template`, and you can use the `<property>` markup syntax to declare their values. ```xmlui-pg ---app copy display name="Using named Slots" {4, 7, 9-11} <App> <ActionBar> <property name="headerTemplate"> <H2>Click one of these actions</H2> </property> <property name="footerTemplate"> <Text>Footer content goes here</Text> </property> <Button label="Create" onClick="window.alert('Create clicked')" /> <Button label="Edit" onClick="window.alert('Edit clicked')" /> <Button label="Delete" onClick="window.alert('Delete clicked')" /> </ActionBar> </App> ---desc This app passes a header template and a footer template slot to the `ActionBar` component and also declares buttons to render. ---comp copy display {3-5, 7-9, 11} <Component name="ActionBar"> <Card> <Slot name="headerTemplate"> <H3>Use these actions</H3> </Slot> <HStack> <Slot> <Button label="Default" onClick="window.alert('Default clicked')" /> </Slot> </HStack> <Slot name="footerTemplate" /> </Card> </Component> ---desc XMLUI finds the appropriate slots by their name and transposes their content received from the app. Just like the default slot, named slots can have default content. ``` > [!WARN] XMLUI will display an error message when the `Slot` name does not end with "Template". ## Template properties [#template-properties] The user-defined component can provide properties for the actual template. ```xmlui-pg ---app copy display name="Using template properties" /header/ /name="headerTemplate"/ /$processedHeader/ <App> <ActionBar header="Action Bar Example"> <property name="headerTemplate"> <Text variant="title">{$processedHeader}</Text> </property> <Button label="Create" onClick="window.alert('Create clicked')" /> <Button label="Edit" onClick="window.alert('Edit clicked')" /> <Button label="Delete" onClick="window.alert('Delete clicked')" /> </ActionBar> </App> ---desc The app passes a `header` property value to the `ActionBar` component. `Actionbar` utilizes this property, transforms it, and passes it back to the template in the `$processedHeader` context variable so that the app can use it. `$processHeader` is available only within the `headerTemplate` slot. ---comp copy display /transformedHeader/ /processedHeader="{transformedHeader}"/ <Component name="ActionBar"> <Card var.transformedHeader="*** {$props.header.toUpperCase()} ***"> <Slot name="headerTemplate" processedHeader="{transformedHeader}" > <H3>{transformedHeader}</H3> </Slot> <HStack> <Slot> <Button label="Default" onClick="window.alert('Default clicked')" /> </Slot> </HStack> </Card> </Component> ---desc `Actionbar` transforms the `header` property and stores it internally in the `transformedHeader` variable. It utilizes the value in the default header definition and also passes it back to the actual template definition with the `processedHeader` name. XMLUI creates the `$processedHeader` context variable from this name. ``` ## Properties [#properties] ### `name` [#name] This optional property defines the name of the slot. ## 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. ``` -------------------------------------------------------------------------------- /tools/create-app/templates/default/ts/public/resources/xmlui-logo.svg: -------------------------------------------------------------------------------- ``` <svg width="115" height="35" viewBox="0 0 46 14" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M19.8926 3.32031V9.57227H23.4199C23.7012 9.57227 23.916 9.64062 24.0645 9.77734C24.2168 9.91406 24.293 10.0859 24.293 10.293C24.293 10.5039 24.2188 10.6758 24.0703 10.8086C23.9219 10.9375 23.7051 11.002 23.4199 11.002H19.2188C18.8398 11.002 18.5664 10.918 18.3984 10.75C18.2344 10.582 18.1523 10.3105 18.1523 9.93555V3.32031C18.1523 2.96875 18.2305 2.70508 18.3867 2.5293C18.5469 2.35352 18.7559 2.26562 19.0137 2.26562C19.2754 2.26562 19.4863 2.35352 19.6465 2.5293C19.8105 2.70117 19.8926 2.96484 19.8926 3.32031Z" fill="#008EE5" /> <path d="M11.0918 9.70898L9.71484 4.23633V10.166C9.71484 10.4941 9.64062 10.7402 9.49219 10.9043C9.34766 11.0684 9.1543 11.1504 8.91211 11.1504C8.67773 11.1504 8.48633 11.0703 8.33789 10.9102C8.18945 10.7461 8.11523 10.498 8.11523 10.166V3.36914C8.11523 2.99414 8.21289 2.74219 8.4082 2.61328C8.60352 2.48047 8.86719 2.41406 9.19922 2.41406H9.73828C10.0625 2.41406 10.2969 2.44336 10.4414 2.50195C10.5898 2.56055 10.6992 2.66602 10.7695 2.81836C10.8398 2.9707 10.9199 3.21875 11.0098 3.5625L12.2578 8.26758L13.5059 3.5625C13.5957 3.21875 13.6758 2.9707 13.7461 2.81836C13.8164 2.66602 13.9238 2.56055 14.0684 2.50195C14.2168 2.44336 14.4531 2.41406 14.7773 2.41406H15.3164C15.6484 2.41406 15.9121 2.48047 16.1074 2.61328C16.3027 2.74219 16.4004 2.99414 16.4004 3.36914V10.166C16.4004 10.4941 16.3262 10.7402 16.1777 10.9043C16.0332 11.0684 15.8379 11.1504 15.5918 11.1504C15.3613 11.1504 15.1719 11.0684 15.0234 10.9043C14.875 10.7402 14.8008 10.4941 14.8008 10.166V4.23633L13.4238 9.70898C13.334 10.0645 13.2598 10.3262 13.2012 10.4941C13.1465 10.6582 13.043 10.8086 12.8906 10.9453C12.7383 11.082 12.5273 11.1504 12.2578 11.1504C12.0547 11.1504 11.8828 11.1055 11.7422 11.0156C11.6016 10.9297 11.4922 10.8184 11.4141 10.6816C11.3359 10.5449 11.2734 10.3945 11.2266 10.2305C11.1836 10.0625 11.1387 9.88867 11.0918 9.70898Z" fill="#008EE5" /> <path d="M0.621094 9.33203L2.54297 6.52539L0.925781 4.0293C0.773438 3.78711 0.658203 3.58008 0.580078 3.4082C0.505859 3.23242 0.46875 3.06445 0.46875 2.9043C0.46875 2.74023 0.541016 2.59375 0.685547 2.46484C0.833984 2.33203 1.01367 2.26562 1.22461 2.26562C1.4668 2.26562 1.6543 2.33789 1.78711 2.48242C1.92383 2.62305 2.11133 2.88672 2.34961 3.27344L3.63867 5.35938L5.01562 3.27344C5.12891 3.09766 5.22461 2.94727 5.30273 2.82227C5.38477 2.69727 5.46289 2.59375 5.53711 2.51172C5.61133 2.42969 5.69336 2.36914 5.7832 2.33008C5.87695 2.28711 5.98438 2.26562 6.10547 2.26562C6.32422 2.26562 6.50195 2.33203 6.63867 2.46484C6.7793 2.59375 6.84961 2.74805 6.84961 2.92773C6.84961 3.18945 6.69922 3.54492 6.39844 3.99414L4.70508 6.52539L6.52734 9.33203C6.69141 9.57812 6.81055 9.7832 6.88477 9.94727C6.95898 10.1074 6.99609 10.2598 6.99609 10.4043C6.99609 10.541 6.96289 10.666 6.89648 10.7793C6.83008 10.8926 6.73633 10.9824 6.61523 11.0488C6.49414 11.1152 6.35742 11.1484 6.20508 11.1484C6.04102 11.1484 5.90234 11.1133 5.78906 11.043C5.67578 10.9766 5.58398 10.8926 5.51367 10.791C5.44336 10.6895 5.3125 10.4922 5.12109 10.1992L3.60938 7.82031L2.00391 10.2695C1.87891 10.4648 1.78906 10.6016 1.73438 10.6797C1.68359 10.7578 1.62109 10.834 1.54688 10.9082C1.47266 10.9824 1.38477 11.041 1.2832 11.084C1.18164 11.127 1.0625 11.1484 0.925781 11.1484C0.714844 11.1484 0.539062 11.084 0.398438 10.9551C0.261719 10.8262 0.193359 10.6387 0.193359 10.3926C0.193359 10.1035 0.335938 9.75 0.621094 9.33203Z" fill="#008EE5" /> <rect x="27" width="19" height="14" rx="2" fill="#008EE5" /> <path d="M40.125 10.0879V3.32031C40.125 2.96875 40.2051 2.70508 40.3652 2.5293C40.5254 2.35352 40.7324 2.26562 40.9863 2.26562C41.248 2.26562 41.459 2.35352 41.6191 2.5293C41.7832 2.70117 41.8652 2.96484 41.8652 3.32031V10.0879C41.8652 10.4434 41.7832 10.709 41.6191 10.8848C41.459 11.0605 41.248 11.1484 40.9863 11.1484C40.7363 11.1484 40.5293 11.0605 40.3652 10.8848C40.2051 10.7051 40.125 10.4395 40.125 10.0879Z" fill="white" /> <path d="M30.9492 7.45117V3.32031C30.9492 2.96875 31.0273 2.70508 31.1836 2.5293C31.3438 2.35352 31.5527 2.26562 31.8105 2.26562C32.0801 2.26562 32.293 2.35352 32.4492 2.5293C32.6094 2.70508 32.6895 2.96875 32.6895 3.32031V7.54492C32.6895 8.02539 32.7422 8.42773 32.8477 8.75195C32.957 9.07227 33.1484 9.32227 33.4219 9.50195C33.6953 9.67773 34.0781 9.76562 34.5703 9.76562C35.25 9.76562 35.7305 9.58594 36.0117 9.22656C36.293 8.86328 36.4336 8.31445 36.4336 7.58008V3.32031C36.4336 2.96484 36.5117 2.70117 36.668 2.5293C36.8242 2.35352 37.0332 2.26562 37.2949 2.26562C37.5566 2.26562 37.7676 2.35352 37.9277 2.5293C38.0918 2.70117 38.1738 2.96484 38.1738 3.32031V7.45117C38.1738 8.12305 38.1074 8.68359 37.9746 9.13281C37.8457 9.58203 37.5996 9.97656 37.2363 10.3164C36.9238 10.6055 36.5605 10.8164 36.1465 10.9492C35.7324 11.082 35.248 11.1484 34.6934 11.1484C34.0332 11.1484 33.4648 11.0781 32.9883 10.9375C32.5117 10.793 32.123 10.5723 31.8223 10.2754C31.5215 9.97461 31.3008 9.5918 31.1602 9.12695C31.0195 8.6582 30.9492 8.09961 30.9492 7.45117Z" fill="white" /> </svg> ``` -------------------------------------------------------------------------------- /docs/public/pages/howto/update-ui-optimistically.md: -------------------------------------------------------------------------------- ```markdown # Update UI optimistically For immediate user feedback, use reactive variables like `localFavorited` and `localFavoritesCount` to update UI state instantly while API calls run in the background. ```xmlui-pg copy display name="Click the Like button - immediate feedback" ---app display /localFavorited/ /localFavoritesCount/ {37-52} <App> <APICall id="favoritePost" method="post" url="/api/posts/{$param}/favorite" inProgressNotificationMessage="Favoriting post..." completedNotificationMessage="Post favorited!" /> <APICall id="unfavoritePost" method="post" url="/api/posts/{$param}/unfavorite" inProgressNotificationMessage="Unfavoriting post..." completedNotificationMessage="Post unfavorited!" /> <DataSource id="timelineData" url="/api/timeline" method="GET" /> <VStack gap="$space-4" padding="$space-4"> <Items data="{timelineData}"> <Card var.localFavorited="{null}" var.localFavoritesCount="{null}"> <VStack> <Text>{$item.author}</Text> <Text>{$item.content}</Text> <HStack gap="$space-4" verticalAlignment="center"> <HStack gap="$space-1" verticalAlignment="center"> <SocialButton icon="reply" /> <Text variant="caption">{$item.replies_count}</Text> </HStack> <HStack gap="$space-1" verticalAlignment="center"> <SocialButton icon="trending-up" /> <Text variant="caption">{$item.reblogs_count}</Text> </HStack> <HStack gap="$space-1" verticalAlignment="center"> <SocialButton icon="like" themeColor="{(localFavorited !== null ? localFavorited : $item.favourited) ? 'attention' : 'secondary'}"> <event name="click"> // Get current state (local takes precedence) const currentFavorited = localFavorited !== null ? localFavorited : $item.favourited; const currentCount = localFavoritesCount !== null ? localFavoritesCount : ($item.favourites_count || 0); // Update UI optimistically localFavorited = !currentFavorited; localFavoritesCount = currentFavorited ? Math.max(0, currentCount - 1) : currentCount + 1; // Make API call if (currentFavorited) { unfavoritePost.execute($item.id).then(() => timelineData.refetch()); } else { favoritePost.execute($item.id).then(() => timelineData.refetch()); } </event> </SocialButton> <Text variant="caption"> {localFavoritesCount !== null ? localFavoritesCount : ($item.favourites_count || 0)} </Text> </HStack> </HStack> </VStack> </Card> </Items> </VStack> </App> ---comp display {8} <Component name="SocialButton"> <Button borderRadius="50%" icon="{$props.icon}" variant="outlined" themeColor="{$props.themeColor || 'secondary'}" size="xs" onClick="{emitEvent('click')}" /> </Component> ---api { "apiUrl": "/api", "initialize": "$state.posts = [ { id: '1', content: 'This is a great post about XMLUI!', author: 'John Developer', favourited: false, favourites_count: 5, replies_count: 2, reblogs_count: 1 }, { id: '2', content: 'Learning optimistic UI updates!', author: 'Jane Designer', favourited: true, favourites_count: 12, replies_count: 4, reblogs_count: 3 } ]", "operations": { "get-timeline": { "url": "/timeline", "method": "get", "handler": "return $state.posts" }, "favorite-post": { "url": "/posts/:id/favorite", "method": "post", "pathParamTypes": { "id": "string" }, "handler": " delay(2000); const post = $state.posts.find(p => p.id === $pathParams.id); if (post) { post.favourited = true; post.favourites_count += 1; } " }, "unfavorite-post": { "url": "/posts/:id/unfavorite", "method": "post", "pathParamTypes": { "id": "string" }, "handler": " delay(2000); const post = $state.posts.find(p => p.id === $pathParams.id); if (post) { post.favourited = false; post.favourites_count -= 1; } " } } } ``` The relationship between `onClick="{emitEvent('click')}"` in the `SocialButton` component and the `<event name="click">` handler in the main app demonstrates event propagation in XMLUI. The parent component catches the emitted click event and implements the optimistic UI update. This separation allows for: - Component reuse. `SocialButton` can be used anywhere without knowing what action the click should perform. - Flexible event handling. Different instances of `SocialButton` can handle clicks differently. ``` -------------------------------------------------------------------------------- /xmlui/src/components/ModalDialog/ModalDialog.tsx: -------------------------------------------------------------------------------- ```typescript import styles from "./ModalDialog.module.scss"; import { createComponentRenderer } from "../../components-core/renderers"; import { parseScssVar } from "../../components-core/theming/themeVars"; import { paddingSubject } from "../../components-core/theming/themes/base-utils"; import { MemoizedItem } from "../container-helpers"; import { ModalDialog, ModalDialogFrame, defaultProps } from "./ModalDialogNative"; import { createMetadata, d } from "../metadata-helpers"; const COMP = "ModalDialog"; export const ModalDialogMd = createMetadata({ status: "stable", description: "`ModalDialog` creates overlay dialogs that appear on top of the main interface, " + "ideal for forms, confirmations, detailed views, or any content that requires " + "focused user attention. Dialogs are programmatically opened using the `open()` " + "method and can receive parameters for dynamic content.", parts: { content: { description: "The main content area of the modal dialog.", }, title: { description: "The title area of the modal dialog.", }, }, props: { fullScreen: { description: `Toggles whether the dialog encompasses the whole UI (\`true\`) or not and has a minimum ` + `width and height (\`false\`).`, valueType: "boolean", defaultValue: defaultProps.fullScreen, }, title: d(`Provides a prestyled heading to display the intent of the dialog.`), closeButtonVisible: { description: `Shows (\`true\`) or hides (\`false\`) the visibility of the close button on the dialog.`, valueType: "boolean", defaultValue: defaultProps.closeButtonVisible, }, }, events: { open: d( `This event is fired when the \`${COMP}\` is opened either via a \`when\` or an ` + `imperative API call (\`open()\`).`, ), close: d( `This event is fired when the close button is pressed or the user clicks outside ` + `the \`${COMP}\`.`, ), }, apis: { close: { description: `This method is used to close the \`${COMP}\`. Invoke it using \`modalId.close()\` ` + `where \`modalId\` refers to a \`ModalDialog\` component.`, signature: "close(): void", }, open: { description: "This method imperatively opens the modal dialog. You can pass an arbitrary number " + "of parameters to the method. In the `ModalDialog` instance, you can access those " + "with the `\$param` and `\$params` context values.", signature: "open(...params: any[]): void", parameters: { params: "An arbitrary number of parameters that can be used to pass data to the dialog.", }, }, }, contextVars: { $param: d("First parameter passed to the `open()` method"), $params: d( "Array of all parameters passed to `open()` method (access with `$params[0]`, `$params[1]`, etc.)", ), }, themeVars: parseScssVar(styles.themeVars), defaultThemeVars: { ...paddingSubject(COMP, { all: "$space-7" }), [`backgroundColor-${COMP}`]: "$backgroundColor-primary", [`backgroundColor-overlay-${COMP}`]: "$backgroundColor-overlay", [`textColor-${COMP}`]: "$textColor-primary", [`borderRadius-${COMP}`]: "$borderRadius", [`fontFamily-${COMP}`]: "$fontFamily", [`maxWidth-${COMP}`]: "450px", [`marginBottom-title-${COMP}`]: "0", }, }); export const modalViewComponentRenderer = createComponentRenderer( COMP, ModalDialogMd, ({ node, extractValue, className, renderChild, lookupEventHandler, registerComponentApi, layoutContext, }) => { // gigantic hack: If the ModalDialog is not inside a ModalDialogFrame, wrap it in one // we do this through the layout context, render it through another render loop with the extra $param context var // (note the layoutContext and node on the MemoizedItem) // one solution would be to have a renderChild that can take a contextVars argument if (!layoutContext?._insideModalFrame) { return ( <ModalDialogFrame isInitiallyOpen={extractValue(node.when) !== undefined} registerComponentApi={registerComponentApi} onClose={lookupEventHandler("close")} onOpen={lookupEventHandler("open")} renderDialog={({ openParams, ref }) => { return ( <MemoizedItem node={node} renderChild={renderChild} layoutContext={{ _insideModalFrame: true }} contextVars={{ $param: openParams?.[0], $params: openParams }} /> ); }} /> ); } return ( <ModalDialog className={className} fullScreen={extractValue.asOptionalBoolean(node.props?.fullScreen)} title={extractValue(node.props?.title)} closeButtonVisible={extractValue.asOptionalBoolean(node.props.closeButtonVisible)} externalAnimation={extractValue.asOptionalBoolean(node.props.externalAnimation)} > {renderChild(node.children, { type: "Stack" })} </ModalDialog> ); }, ); ``` -------------------------------------------------------------------------------- /xmlui/src/parsers/scripting/TokenTrait.ts: -------------------------------------------------------------------------------- ```typescript import { TokenType } from "./TokenType"; // Describes the special traits of a token type TokenTrait = { expressionStart?: boolean; isAssignment?: boolean; canBeUnary?: boolean; keywordLike?: boolean; isPropLiteral?: boolean; }; // Individual traits of tokens export const tokenTraits: Record<TokenType, TokenTrait> = { [TokenType.Eof]: {}, [TokenType.Ws]: {}, [TokenType.DollarLBrace]: {}, [TokenType.Backtick]: {expressionStart: true}, [TokenType.BlockComment]: {}, [TokenType.EolComment]: {}, [TokenType.Unknown]: {}, [TokenType.LParent]: { expressionStart: true }, [TokenType.RParent]: {}, [TokenType.Identifier]: { expressionStart: true, keywordLike: true, isPropLiteral: true }, [TokenType.Exponent]: {}, [TokenType.Divide]: {}, [TokenType.Multiply]: {}, [TokenType.Remainder]: {}, [TokenType.Plus]: { expressionStart: true, canBeUnary: true }, [TokenType.Minus]: { expressionStart: true, canBeUnary: true }, [TokenType.BitwiseXor]: {}, [TokenType.BitwiseOr]: {}, [TokenType.LogicalOr]: {}, [TokenType.BitwiseAnd]: {}, [TokenType.LogicalAnd]: {}, [TokenType.Assignment]: { isAssignment: true }, [TokenType.AddAssignment]: { isAssignment: true }, [TokenType.SubtractAssignment]: { isAssignment: true }, [TokenType.ExponentAssignment]: { isAssignment: true }, [TokenType.MultiplyAssignment]: { isAssignment: true }, [TokenType.DivideAssignment]: { isAssignment: true }, [TokenType.RemainderAssignment]: { isAssignment: true }, [TokenType.ShiftLeftAssignment]: { isAssignment: true }, [TokenType.ShiftRightAssignment]: { isAssignment: true }, [TokenType.SignedShiftRightAssignment]: { isAssignment: true }, [TokenType.BitwiseAndAssignment]: { isAssignment: true }, [TokenType.BitwiseXorAssignment]: { isAssignment: true }, [TokenType.BitwiseOrAssignment]: { isAssignment: true }, [TokenType.LogicalAndAssignment]: { isAssignment: true }, [TokenType.LogicalOrAssignment]: { isAssignment: true }, [TokenType.NullCoalesceAssignment]: { isAssignment: true }, [TokenType.Semicolon]: {}, [TokenType.Comma]: {}, [TokenType.Colon]: {}, [TokenType.LSquare]: { expressionStart: true }, [TokenType.RSquare]: {}, [TokenType.QuestionMark]: {}, [TokenType.NullCoalesce]: {}, [TokenType.OptionalChaining]: {}, [TokenType.BinaryNot]: { expressionStart: true, canBeUnary: true }, [TokenType.LBrace]: { expressionStart: true }, [TokenType.RBrace]: {}, [TokenType.Equal]: {}, [TokenType.StrictEqual]: {}, [TokenType.LogicalNot]: { expressionStart: true, canBeUnary: true }, [TokenType.NotEqual]: {}, [TokenType.StrictNotEqual]: {}, [TokenType.LessThan]: {}, [TokenType.LessThanOrEqual]: {}, [TokenType.ShiftLeft]: {}, [TokenType.GreaterThan]: {}, [TokenType.GreaterThanOrEqual]: {}, [TokenType.ShiftRight]: {}, [TokenType.SignedShiftRight]: {}, [TokenType.Dot]: {}, [TokenType.Spread]: { expressionStart: true, isPropLiteral: true }, [TokenType.Global]: { expressionStart: true }, [TokenType.DecimalLiteral]: { expressionStart: true, isPropLiteral: true}, [TokenType.HexadecimalLiteral]: { expressionStart: true, isPropLiteral: true }, [TokenType.BinaryLiteral]: { expressionStart: true, isPropLiteral: true }, [TokenType.RealLiteral]: { expressionStart: true, isPropLiteral: true }, [TokenType.StringLiteral]: { expressionStart: true, isPropLiteral: true }, [TokenType.IncOp]: { expressionStart: true }, [TokenType.DecOp]: { expressionStart: true }, [TokenType.Infinity]: { expressionStart: true, keywordLike: true }, [TokenType.NaN]: { expressionStart: true, keywordLike: true }, [TokenType.True]: { expressionStart: true, keywordLike: true, isPropLiteral: true}, [TokenType.False]: { expressionStart: true, keywordLike: true, isPropLiteral: true}, [TokenType.Typeof]: { expressionStart: true, canBeUnary: true, keywordLike: true }, [TokenType.Null]: { expressionStart: true, keywordLike: true }, [TokenType.Undefined]: { expressionStart: true, keywordLike: true }, [TokenType.In]: { keywordLike: true }, [TokenType.Let]: { keywordLike: true }, [TokenType.Const]: { keywordLike: true }, [TokenType.Var]: { keywordLike: true }, [TokenType.If]: { keywordLike: true }, [TokenType.Else]: { keywordLike: true }, [TokenType.Arrow]: { keywordLike: true }, [TokenType.Return]: { keywordLike: true }, [TokenType.Break]: { keywordLike: true }, [TokenType.Continue]: { keywordLike: true }, [TokenType.Do]: { keywordLike: true }, [TokenType.While]: { keywordLike: true }, [TokenType.For]: { keywordLike: true }, [TokenType.Of]: { keywordLike: true }, [TokenType.Throw]: { keywordLike: true }, [TokenType.Try]: { keywordLike: true }, [TokenType.Catch]: { keywordLike: true }, [TokenType.Finally]: { keywordLike: true }, [TokenType.Switch]: { keywordLike: true }, [TokenType.Case]: { keywordLike: true }, [TokenType.Default]: { keywordLike: true }, [TokenType.Delete]: { expressionStart: true, canBeUnary: true, keywordLike: true }, [TokenType.Function]: { keywordLike: true, expressionStart: true }, [TokenType.As]: { keywordLike: true }, }; ``` -------------------------------------------------------------------------------- /xmlui/src/components/Tabs/Tabs.tsx: -------------------------------------------------------------------------------- ```typescript import styles from "./Tabs.module.scss"; import { parseScssVar } from "../../components-core/theming/themeVars"; import { createComponentRenderer } from "../../components-core/renderers"; import { MemoizedItem } from "../container-helpers"; import { Tabs, defaultProps } from "./TabsNative"; import { createMetadata, d, dComponent, dDidChange } from "../metadata-helpers"; const COMP = "Tabs"; export const TabsMd = createMetadata({ status: "experimental", description: "`Tabs` enables users to switch among content panels using clickable tab headers. " + "It provides an efficient way to present multiple related sections in a single " + "interface area, with each tab containing distinct content defined by " + "[TabItem](/components/TabItem) components.", props: { activeTab: d( `This property indicates the index of the active tab. The indexing starts from 0, ` + `representing the starting (leftmost) tab. If not set, the first tab is selected by default.`, ), orientation: { description: `This property indicates the orientation of the component. In horizontal orientation, ` + `the tab sections are laid out on the left side of the content panel, while in vertical ` + `orientation, the buttons are at the top. Note: This property is ignored when ` + `accordionView is set to true.`, availableValues: ["horizontal", "vertical"], defaultValue: defaultProps.orientation, valueType: "string", }, tabAlignment: { description: `This property controls how tabs are aligned within the tab header container in ` + `horizontal orientation. Use 'start' to align tabs to the left, 'end' to align to the ` + `right, 'center' to center the tabs, and 'stretch' to make tabs fill the full width ` + `of the header. Note: This property is ignored when orientation is set to 'vertical' ` + `or when accordionView is enabled.`, availableValues: ["start", "end", "center", "stretch"], defaultValue: defaultProps.tabAlignment, valueType: "string", }, accordionView: { description: `When enabled, displays tabs in an accordion-like view where tab headers are stacked ` + `vertically and only the active tab's content is visible. Each tab header remains visible ` + `and clicking a header opens its content while closing others. When enabled, the ` + `orientation property is ignored.`, defaultValue: defaultProps.accordionView, valueType: "boolean", }, headerTemplate: { ...dComponent(`This property declares the template for the clickable tab area.`), }, }, events: { didChange: dDidChange(COMP), }, apis: { next: { description: `This method selects the next tab. If the current tab is the last one, it wraps around to the first tab.`, signature: "next(): void", }, prev: { description: `This method selects the previous tab. If the current tab is the first one, it wraps around to the last tab.`, signature: "prev(): void", }, setActiveTabIndex: { description: `This method sets the active tab by index (0-based).`, signature: "setActiveTabIndex(index: number): void", }, setActiveTabById: { description: `This method sets the active tab by its ID.`, signature: "setActiveTabById(id: string): void", }, }, themeVars: parseScssVar(styles.themeVars), defaultThemeVars: { // [`backgroundColor-${COMP}`]: "transparent", [`borderStyle-${COMP}`]: "solid", [`borderColor-${COMP}`]: "$borderColor", [`borderColor-active-${COMP}`]: "$color-primary", [`borderWidth-${COMP}`]: "2px", // [`backgroundColor-trigger-${COMP}`]: "transparent", [`backgroundColor-trigger-${COMP}--hover`]: "$color-surface-100", [`padding-trigger-${COMP}`]: "$space-4", // [`backgroundColor-list-${COMP}`]: "$color-primary-50", // [`textColor-trigger-${COMP}`]: "$color-primary-100", }, }); export const tabsComponentRenderer = createComponentRenderer( COMP, TabsMd, ({ extractValue, node, renderChild, className, registerComponentApi, lookupEventHandler }) => { return ( <Tabs id={node?.uid} className={className} headerRenderer={ node?.props?.headerTemplate ? (item) => ( <MemoizedItem node={node.props.headerTemplate! as any} itemKey="$header" contextVars={{ $header: item, }} renderChild={renderChild} /> ) : undefined } activeTab={extractValue(node.props?.activeTab)} orientation={extractValue(node.props?.orientation)} tabAlignment={extractValue(node.props?.tabAlignment)} accordionView={extractValue(node.props?.accordionView)} onDidChange={lookupEventHandler("didChange")} registerComponentApi={registerComponentApi} > {renderChild(node.children)} </Tabs> ); }, ); ``` -------------------------------------------------------------------------------- /xmlui/dev-docs/release-method.md: -------------------------------------------------------------------------------- ```markdown **Overall Philosophy:** * **`main` branch:** The source of truth for all development. * **Changesets:** Used for managing versioning and changelogs. * Can be added manually (`npx changeset add`). * Or, ideally, auto-generated from Conventional Commits on PRs (see `add-changeset.yml` below). * **Beta Releases:** Fully automated on every merge to `main` that includes new changesets. Published to npm with a `beta` tag. * **Stable Releases:** A manually triggered process that: 1. Creates a "Version Packages" Pull Request for review. 2. Upon merging this PR, publishes packages to npm (with the `latest` tag) and creates GitHub Releases. * **Handling Race Conditions for Stable Release:** If `main` changes significantly after the "Version Packages" PR is created, the safest approach is to close that PR and re-trigger the stable release process to generate a new, up-to-date "Version Packages" PR. --- **The Process Steps:** 1. **Development:** * Developers create feature branches from `main`. * They make code changes and write **Conventional Commit messages**. * **(Automation Option):** When a PR is opened/updated, the `add-changeset.yml` workflow runs, analyzes conventional commits, and automatically adds/updates `.changeset/*.md` files to the PR branch. * **(Manual Option):** Developer runs `npx changeset add` before committing changes that require a version bump, and commits the generated changeset file. * Developer opens/updates a Pull Request to `main`. 2. **Pull Request Merged to `main`:** * The `beta-release.yml` workflow triggers. * It consumes any `.changeset` files merged from the PR. * It versions affected packages with a beta suffix (e.g., `1.0.1-beta.shortsha`). * It publishes these beta versions to npm under the `beta` dist-tag. * It commits and pushes these snapshot version changes back to `main`. 3. **Preparing for a Stable Release (Manual Trigger):** * A designated person (release manager) decides it's time for a stable release. * **Pre-check:** Briefly check if `main` is relatively stable or communicate a short "quiet period" for merges to `main` to minimize conflicts with the version PR. * Go to GitHub Actions -> "Stable Release Process" workflow -> Run workflow. * The `stable-release.yml` workflow's `create_version_pr` job runs: * It uses `changesets/action` to consume all current `.changeset` files on `main`. * It generates version bumps, updates `CHANGELOG.md` files. * It commits these changes to a new branch (e.g., `changeset-release/main`). * It opens a "Version Packages for Release" Pull Request to `main`. 4. **Reviewing and Merging the "Version Packages" PR:** * The team reviews this PR. It shows exactly which packages will be versioned and what their changelogs will contain for the stable release. * **Critical Check:** Before merging, verify if `main` has received significant new changes (especially new changesets that have triggered new beta releases) *since this "Version Packages" PR was created*. * **If `main` has changed significantly:** Close the current "Version Packages" PR and its branch. Go back to Step 3 and re-trigger the "Stable Release Process" workflow to generate a fresh "Version Packages" PR based on the latest `main`. * **If `main` is stable or changes are minor/unrelated:** Proceed to merge the "Version Packages" PR. 5. **"Version Packages" PR is Merged to `main`:** * The `stable-release.yml` workflow's `publish_and_release` job triggers. * It uses `changesets/action` to: * Publish the versioned packages (now merged into `main`) to npm, this time to the `latest` dist-tag. * Create Git tags for each published package version. * Create corresponding GitHub Releases, populating them with content from the `CHANGELOG.md` files. --- **1. `.github/workflows/add-changeset.yml`** *Automates creation of changeset files from Conventional Commits in PRs.* ```yaml name: Add Changeset from Conventional Commits (NPM) on: pull_request: types: [opened, synchronize, reopened, ready_for_review] permissions: contents: write pull-requests: read jobs: add_changeset: if: github.event.pull_request.draft == false && github.actor != 'dependabot[bot]' && github.actor != 'renovate[bot]' runs-on: ubuntu-latest steps: - name: Checkout Repo uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '18' # Or your Node.js version cache: 'npm' # Use npm cache - name: Install Dependencies run: npm ci # Uses package-lock.json - name: Create Changeset File uses: tripsit/conventional-changesets-action@v4 with: github-token: ${{ secrets.GITHUB_TOKEN }} commit-message: "chore: add generated changeset(s) [skip ci]" skip-ci: "true" # Configure type mappings if needed ``` ``` -------------------------------------------------------------------------------- /xmlui/src/components-core/rendering/renderChild.tsx: -------------------------------------------------------------------------------- ```typescript import type { ReactNode } from "react"; import type { ComponentDef } from "../../abstractions/ComponentDefs"; import type { InnerRendererContext } from "../abstractions/ComponentRenderer"; import type { ComponentCleanupFn } from "../rendering/ContainerWrapper"; import { shouldKeep, extractParam } from "../utils/extractParam"; import { ComponentWrapper } from "./ComponentWrapper"; import type { StatePartChangedFn } from "./ContainerWrapper"; /** * This type represents the context in which the React component belonging to a * particular component definition is rendered with the `renderChild()` function. */ export interface ChildRendererContext extends InnerRendererContext { statePartChanged: StatePartChangedFn; cleanup: ComponentCleanupFn; } /** * This function is the jolly-joker of the rendering process. It renders a child component * based on the specified context, which contains the component's definition, the current state, * and other necessary information. * * The function checks a few special cases: * - <Slot> with a single text node child: it renders the text in the context of the parent component. * - CDATA text nodes: it renders the text as is without parsing it. * - TextNode: it extracts the text from the node and renders it. * * In other cases, it extracts the component's ID and renders the component as a <ComponentNode>. * * As this function passes itself as a renderChild function to the <ComponentNode>, it can render * nested components recursively. */ export function renderChild({ node, state, dispatch, appContext, lookupAction, lookupSyncCallback, registerComponentApi, renderChild, statePartChanged, layoutContext, parentRenderContext, memoedVarsRef, cleanup, uidInfoRef, }: ChildRendererContext): ReactNode { // --- Render only visible components if (!shouldKeep(node.when, state, appContext)) { return null; } // --- We do not parse text nodes specified with CDATA to avoid whitespace collapsing const nodeValue = (node.props as any)?.value; if (node.type === "TextNodeCData") { return nodeValue ?? ""; } // --- A TextNode value may contain nexted expressions, so we extract it. if (node.type === "TextNode") { const extractedValue = extractParam(state, nodeValue, appContext, true); return typeof extractedValue === "boolean" ? extractedValue.toString() : extractedValue; } // --- Rendering a Slot requires some preparations, as TextNode and // --- TextNodeCData are virtual nodes. Also, slots may have default templates // --- to render when no slot children are specified. The following section // --- handles these cases. if (node.type === "Slot") { // --- Check for special Slot cases let slotChildren: ComponentDef | ComponentDef[]; const templateName = node.props?.name; // console.log("templateName", templateName); if (templateName) { // --- Let's check the validity of the slot name if (!templateName.endsWith("Template")) { throw new Error( `Slot name '${templateName}' is not valid. ` + "A named slot should use a name ending with 'Template'.", ); } // --- Named slot: use a template property from the parent component slotChildren = parentRenderContext?.props?.[templateName]; } else { // --- Children slot: use the children of the parent component slotChildren = parentRenderContext?.children; } if (!slotChildren) { // --- No children to render, let's try the default slot template (if there is any) slotChildren = node.children; } if (slotChildren) { const toRender = Array.isArray(slotChildren) ? slotChildren : [slotChildren]; // --- Check for the virtual nodes. At this point, parentRendererContext is // --- undefined when the parent does not provide slot children. In this case, // --- the ComponentBed component will render the default slot template. if (toRender.length === 1 && parentRenderContext) { if (toRender[0].type === "TextNodeCData" || toRender[0].type === "TextNode") { // --- Preserve the text and render it in the parent context return parentRenderContext.renderChild(toRender); } } } } // --- In other cases, we extract the component ID, and then render the component. // --- A component's ID is generally a string with identifier syntax. However, some // --- internal components have IDs with expressions, so we evaluate them. const key = extractParam(state, node.uid, appContext, true); return ( <ComponentWrapper key={key} resolvedKey={key} node={node} cleanup={cleanup} statePartChanged={statePartChanged} memoedVarsRef={memoedVarsRef} state={state} dispatch={dispatch} appContext={appContext} lookupAction={lookupAction} lookupSyncCallback={lookupSyncCallback} registerComponentApi={registerComponentApi} renderChild={renderChild} layoutContext={layoutContext} parentRenderContext={parentRenderContext} uidInfoRef={uidInfoRef} /> ); } ``` -------------------------------------------------------------------------------- /xmlui/src/components-core/utils/LruCache.ts: -------------------------------------------------------------------------------- ```typescript // ==================================================================================================================== // Types to implement an LRU cache we use to provide stable // Implementation source: https://www.nickang.com/2021-11-28-how-to-implement-an-lru-cache-in-javascript/ /** * A single node of the LRU cache */ class DoublyLinkedNode { prev: DoublyLinkedNode | undefined; next: DoublyLinkedNode | undefined; constructor(public readonly value: any, public readonly key: string) { this.next = undefined; this.prev = undefined; } } /** * We keep values in the LRU cache in a doubly linked list */ class DoublyLinkedList { head: DoublyLinkedNode | undefined; tail: DoublyLinkedNode | undefined; size = 0; constructor() { this.head = undefined; this.tail = undefined; this.size = 0; } /** * Adds a new node to the head of the list * @param node The node to add to head of list */ unshift(node: DoublyLinkedNode) { // case 1: there is only a root node in the list // point node.prev to root node // point node.next to undefined // point DoublyLinkedList head to node // point DoublyLinkedList tail to node // increment DoublyLinkedList size by 1 // case 2: there are data nodes in the list // point head node.prev to node // point node.next to head node // point node.prev to root node // point DoublyLinkedList head to node // increment DoublyLinkedList size by 1 if (this.size === 0) { // case 1 this.head = node; this.tail = node; this.size++; } else { // case 2 this.head!.prev = node; node.next = this.head; node.prev = undefined; this.head = node; this.size++; } } /** * Remove least recently used node from tail */ pop() { const node = this.tail; if (!node) { return undefined; } else if (this.head === this.tail) { this.head = undefined; this.tail = undefined; } else { this.tail!.prev!.next = undefined; } this.tail = node.prev; this.size--; return node; } /** * Moves the specified node to the head */ moveToHead(node: DoublyLinkedNode) { if (node === this.head) { return; } if (node === this.head && node === this.tail) { return; } if (node === this.tail) { // set tail to tail node.prev this.tail = this.tail.prev; // set new tail node.next to undefined this.tail!.next = undefined; // set node.next to current head node.next = this.head; // set current head node.prev to node this.head!.prev = node; // set head to node this.head = node; // set node.prev to undefined node.prev = undefined; } else { // set node.prev.next to node.next node.prev!.next = node.next; // set node.next.prev to node.prev node.next!.prev = node.prev; // set node.next to current head node.next = this.head; // set current head node.prev to node this.head!.prev = node; // set node as new head this.head = node; // set node.prev to undefined node.prev = undefined; } } } /** * This class implements the LRU cache */ export class LRUCache { private store: Record<string, DoublyLinkedNode> = {}; list = new DoublyLinkedList(); constructor(public readonly maxSize: number) {} /** * Gets the number of items stored in the cache */ get length(): number { return this.list.size; } /** * Gets the value with the specified key * @param key */ get(key: string): any { // case 1: node with this key found // update position of node to head of DoublyLinkedList // return existing node // case 2: node with this key not found (i.e. doesn't exist) // return undefined const existingNode = this.store[key]; if (existingNode) { this.list.moveToHead(existingNode); } return existingNode?.value; } /** * Sets the value for a particular key within the cache * @param key Cache item key * @param value Cache item value */ set(key: string, value: any): void { // case 1: search and found existing node with this key // use get() to obtain node // if not exist, go to case 2 // if exist, let get() handle re-ordering in DoublyLinkedList // set node to hold new value const existingNode = this.get(key); if (existingNode) { existingNode.value = value; } // case 2: search and couldn't find existing node with this key // create new node // insert key-value pair into store // insert as new head of DoublyLinkedList const newNode = new DoublyLinkedNode(value, key); this.store[key] = newNode; this.list.unshift(newNode); if (this.hasReachedMaxSize()) { this.evictLeastRecentlyUsed(); } } hasReachedMaxSize() { return this.list.size === this.maxSize + 1; } evictLeastRecentlyUsed() { const evictedNode = this.list.pop(); delete this.store[evictedNode!.key]; } } ``` -------------------------------------------------------------------------------- /xmlui/conventions/mermaid.md: -------------------------------------------------------------------------------- ```markdown # Mermaid Diagram Conventions This document captures best practices and lessons learned for creating effective Mermaid diagrams in XMLUI documentation. ## Block Diagrams vs Flowcharts ### When to Use Block Diagrams - **Container representations**: Block diagrams excel at showing rectangular containers with structured content - **Clean layouts**: Better for side-by-side arrangements without connecting lines - **Text-heavy content**: Superior handling of multi-line text and tree structures - **Consistent styling**: More predictable alignment and spacing ### When to Use Flowcharts - **Process flows**: When you need arrows and connections between elements - **Decision trees**: For branching logic and conditional paths - **Simple node relationships**: Basic parent-child relationships with connections ## Block Diagram Best Practices ### Basic Structure ```mermaid %%{init: {"block": {"padding": 8}}}%% block-beta columns 3 block:GroupName:3 ITEM["Content goes here"] end ITEM1["Item 1"] ITEM2["Item 2"] ITEM3["Item 3"] ``` ### Configuration - Always include `%%{init: {"block": {"padding": 8}}}%%` for consistent padding - Use `columns N` to control horizontal layout (1 for single column, 3 for three-column grid) - Use `block:Name:N` syntax to span multiple columns ### Content Formatting #### Left-Aligned Text ```mermaid CONTAINER["<div style='text-align: left; width: 100%'>Title<br/>Content line 1<br/>Content line 2</div>"] ``` #### Tree Structures Use ` ` entities for precise indentation: ```mermaid ITEM["<div style='text-align: left; width: 100%'>🏠 Container<br/>💡 State:<br/> ├─ item1: value<br/> └─ item2: value<br/>🌳 Tree:<br/> Root:<br/> ├─ Child 1<br/> └─ Child 2</div>"] ``` #### Spacing Guidelines - **Root level items**: 5 ` ` entities to align with section headers - **Tree children**: 7-8 ` ` entities for proper indentation under parents - **Section headers**: Align with icon text, not the icon itself ## Styling ### CSS Classes ```mermaid classDef rootContainer fill:#f0f0f0,stroke:#888,stroke-width:2px,color:#333 classDef innerContainer fill:#f8f8f8,stroke:#aaa,stroke-width:1px,color:#333 class ITEM1,ITEM2 rootContainer class ITEM3 innerContainer ``` ### Background Colors - **Light containers**: `fill:#f0f0f0` with `stroke:#888` - **Inner elements**: `fill:#f8f8f8` with `stroke:#aaa` - **Text color**: Always use `color:#333` for readability ### Padding - Apply padding directly in HTML: `<div style='padding: 0 15px;'>` for horizontal-only padding - Avoid CSS class padding - it may not work consistently in all Mermaid versions ## Layout Patterns ### Single Container ```mermaid %%{init: {"block": {"padding": 8}}}%% block-beta columns 1 CONTAINER["<div style='text-align: left; width: 100%'>Content</div>"] classDef rootContainer fill:#f0f0f0,stroke:#888,stroke-width:2px,color:#333 class CONTAINER rootContainer ``` ### Multi-Container Grid ```mermaid %%{init: {"block": {"padding": 8}}}%% block-beta columns 3 block:Header:3 HEADER["Header spanning full width"] end ITEM1["Item 1"] ITEM2["Item 2"] ITEM3["Item 3"] ``` ## Common Pitfalls ### Text Alignment Issues - **Problem**: Content appears centered even with left-align styling - **Solution**: Always use `<div style='text-align: left; width: 100%'>` wrapper ### Inconsistent Spacing - **Problem**: Tree items don't align properly - **Solution**: Count ` ` entities carefully - use consistent patterns (5 for root, 7-8 for children) ### Styling Not Applied - **Problem**: CSS classes don't affect appearance - **Solution**: Apply styles directly in HTML divs rather than relying solely on CSS classes ### Layout Breaks - **Problem**: Items don't arrange as expected - **Solution**: Use `columns N` consistently and test with different content lengths ## Icons and Emojis ### Standard Icons - 🏠 - Containers - 💡 - State/Data - 🌳 - Trees/Hierarchies - 📦 - Components - ⚙️ - Configuration ### Tree Symbols - `├─` - Branch item - `└─` - Final branch item - Use these consistently for hierarchical structures ## Testing Tips 1. **Always preview**: Mermaid rendering can vary between environments 2. **Check alignment**: Verify tree structures align properly at different zoom levels 3. **Test content length**: Ensure layout works with both short and long text 4. **Validate styling**: Confirm background colors and padding appear correctly 5. **Cross-platform**: Test on different operating systems if possible ## Migration from Flowcharts When converting existing flowcharts to block diagrams: 1. Replace `graph TB/LR` with `block-beta` 2. Remove connection arrows (`---`, `-->`) 3. Convert subgraphs to block groups 4. Add HTML div wrappers for text alignment 5. Apply consistent CSS styling 6. Test tree structure indentation This approach provides cleaner, more maintainable diagrams with better text formatting capabilities. ``` -------------------------------------------------------------------------------- /xmlui/src/components/Charts/LineChart/LineChart.tsx: -------------------------------------------------------------------------------- ```typescript import { defaultProps, LineChart } from "./LineChartNative"; import { createComponentRenderer } from "../../../components-core/renderers"; import { createMetadata, d } from "../../metadata-helpers"; import { parseScssVar } from "../../../components-core/theming/themeVars"; import styles from "./LineChart.module.scss"; import { MemoizedItem } from "../../container-helpers"; const COMP = "LineChart"; export const LineChartMd = createMetadata({ status: "experimental", description: "`LineChart` displays data as connected points over a continuous axis, ideal " + "for showing trends, changes over time, or relationships between variables. " + "Use it time series data, progress tracking, and comparing multiple data " + "series on the same scale.", docFolder: "Charts/LineChart", props: { data: { description: "The data to be displayed in the line chart." + "It needs to be an array of objects, where each object represents a data point.", }, xKey: { description: "The key in the data objects used for labeling different data series.", valueType: "string", }, yKeys: { description: "This property specifies the keys in the data objects that should be used for rendering the lines.", valueType: "string", }, hideX: { description: "Determines whether the X-axis should be hidden. If set to (`true`), the axis will not be displayed.", valueType: "boolean", defaultValue: defaultProps.hideX, }, hideY: { description: "Determines whether the Y-axis should be hidden. If set to (`true`), the axis will not be displayed.", valueType: "boolean", defaultValue: defaultProps.hideY, }, hideTickX: { description: "Determines whether the X-axis ticks should be hidden. If set to (`true`), the ticks will not be displayed.", valueType: "boolean", defaultValue: defaultProps.hideTickX, }, hideTickY: { description: "Determines whether the Y-axis ticks should be hidden. If set to (`true`), the ticks will not be displayed.", valueType: "boolean", defaultValue: defaultProps.hideTickY, }, hideTooltip: { description: "Determines whether the tooltip should be hidden." + "If set to (`true`), no tooltip will be shown when hovering over data points.", valueType: "boolean", defaultValue: defaultProps.hideTooltip, }, tickFormatterX: { description: "A function that formats the X-axis tick labels. It receives a tick value and returns a formatted string.", }, tickFormatterY: { description: "A function that formats the Y-axis tick labels. It receives a tick value and returns a formatted string.", }, showLegend: { description: "Determines whether the legend should be displayed.", valueType: "boolean", defaultValue: defaultProps.showLegend, }, tooltipTemplate: { description: "This property allows replacing the default template to display a tooltip.", }, marginTop: d("The top margin of the chart"), marginRight: d("The right margin of the chart"), marginBottom: d("The bottom margin of the chart"), marginLeft: d("The left margin of the chart"), }, themeVars: parseScssVar(styles.themeVars), defaultThemeVars: { [`width-line-LineChart`]: "1px", }, }); export const lineChartComponentRenderer = createComponentRenderer( COMP, LineChartMd, ({ extractValue, node, className, lookupSyncCallback, renderChild }: any) => { return ( <LineChart tickFormatterX={lookupSyncCallback(node.props?.tickFormatterX)} tickFormatterY={lookupSyncCallback(node.props?.tickFormatterY)} hideTickX={extractValue(node.props?.hideTickX)} hideTickY={extractValue(node.props?.hideTickY)} data={extractValue(node.props?.data)} className={className} dataKeys={extractValue(node.props?.yKeys)} nameKey={extractValue(node.props?.xKey)} hideX={extractValue(node.props?.hideX)} hideY={extractValue(node.props?.hideY)} hideTooltip={extractValue(node.props?.hideTooltip)} showLegend={extractValue.asOptionalBoolean(node.props?.showLegend)} marginTop={extractValue.asOptionalNumber(node.props?.marginTop)} marginRight={extractValue.asOptionalNumber(node.props?.marginRight)} marginBottom={extractValue.asOptionalNumber(node.props?.marginBottom)} marginLeft={extractValue.asOptionalNumber(node.props?.marginLeft)} tooltipRenderer={ node.props.tooltipTemplate ? (tooltipData) => { return ( <MemoizedItem node={node.props.tooltipTemplate} item={tooltipData} contextVars={{ $tooltip: tooltipData, }} renderChild={renderChild} /> ); } : undefined } > {renderChild(node.children)} </LineChart> ); }, ); ``` -------------------------------------------------------------------------------- /xmlui/src/components/TimeInput/TimeInput.md: -------------------------------------------------------------------------------- ```markdown %-DESC-START **Key features:** - **Time format support**: 12-hour and 24-hour formats with customizable display - **Precision control**: Configure precision for hours, minutes, and seconds - **Input validation**: Real-time validation with visual feedback for invalid times - **Accessibility**: Full keyboard navigation and screen reader support - **Localization**: Automatic AM/PM labels based on user locale %-DESC-END %-API-START setValue ```xmlui-pg copy {3, 9, 12} display name="Example: setValue" <App> <HStack> <Button label="Set Time to 14:30" onClick="picker.setValue('14:30')" /> <Button label="Remove Time" onClick="picker.setValue('')" /> </HStack> <TimeInput id="picker" /> </App> ``` %-API-END %-PROP-START initialValue ```xmlui-pg copy display name="Example: initialValue" height="120px" <App> <TimeInput initialValue="14:30:15" /> </App> ``` %-PROP-END %-PROP-START placeholder ```xmlui-pg copy display name="Example: placeholder" height="120px" <App> <TimeInput placeholder="Select a time" /> </App> ``` %-PROP-END %-PROP-START enabled ```xmlui-pg copy display name="Example: enabled" height="120px" <App> <TimeInput enabled="false" initialValue="14:30" /> </App> ``` %-PROP-END %-PROP-START validationStatus | Value | Description | | :-------- | :---------------------------------------------------- | | `valid` | Visual indicator for an input that is accepted | | `warning` | Visual indicator for an input that produced a warning | | `error` | Visual indicator for an input that produced an error | ```xmlui-pg copy display name="Example: validationStatus" <App> <TimeInput validationStatus="valid" initialValue="11:30" /> <TimeInput validationStatus="warning" initialValue="11:30" /> <TimeInput validationStatus="error" initialValue="11:30" /> </App> ``` %-PROP-END %-PROP-START format The `format` prop controls how time is displayed and which parts are editable. Based on Unicode Technical Standard #35. | Format | Description | Example | | :----- | :---------- | :------ | | `H:mm` | 24-hour format with hours and minutes | 14:30 | | `HH:mm:ss` | 24-hour format with hours, minutes, seconds | 14:30:15 | | `h:mm a` | 12-hour format with AM/PM | 2:30 PM | | `hh:mm:ss a` | 12-hour format with seconds and AM/PM | 02:30:15 PM | ```xmlui-pg copy display name="Example: format" <App> <TimeInput format="H:mm" initialValue="14:30" /> <TimeInput format="h:mm a" initialValue="14:30" /> <TimeInput format="HH:mm:ss" initialValue="14:30:15" /> <TimeInput format="HH:mm:ss a" initialValue="14:30:15" /> </App> ``` %-PROP-END %-PROP-START clearable When enabled, it displays a clear button that allows users to reset the time picker back to its initial value. Change the time value in this app and then click the clear button: ```xmlui-pg copy display name="Example: clearable" /clearable/ <App> <TimeInput initialValue="11:30" /> <TimeInput clearable="true" initialValue="10:20" /> </App> ``` %-PROP-END %-PROP-START clearIcon ```xmlui-pg copy display name="Example: clearIcon" /clearIcon/ <App> <TimeInput initialValue="11:30" clearIcon="trash" /> </App> ``` %-PROP-END %-PROP-START required Marks the time input as required for form validation. ```xmlui-pg copy display name="Example: required" height="120px" <App> <TimeInput required="true" /> </App> ``` %-PROP-END %-PROP-START mute When `true`, prevents audible beeps but still fires the `beep` event for programmatic handling. %-PROP-END %-PROP-START emptyCharacter Character to use as placeholder for empty time values. If longer than 1 character, uses the first character. Defaults to '-'. ```xmlui-pg copy display name="Example: emptyCharacter" <App> <TimeInput emptyCharacter="." /> <TimeInput emptyCharacter="*" /> <TimeInput emptyCharacter="abc" /> </App> ``` %-PROP-END %-EVENT-START didChange Fired when the time value changes. Receives the new time value as a parameter. > [!INFO] The time value changes when the edited input part (hour, minute, second) loses focus or the AM/PM selectro changes. ```xmlui-pg copy {2} display name="Example: didChange" height="180px" <App var.selectedTime="No time selected"> <Text value="{selectedTime}" /> <TimeInput format="h:m:s a" initialValue="07:30:05" onDidChange="(time) => selectedTime = time" /> </App> ``` %-EVENT-END %-EVENT-START gotFocus Fired when the time picker receives focus. ```xmlui-pg copy {4-5} display name="Example: gotFocus/lostFocus" <App var.isFocused="{false}"> <Text value="{isFocused ? 'TimeInput focused' : 'TimeInput lost focus'}" /> <TimeInput format="HH:mm:ss a" onGotFocus="isFocused = true" onLostFocus="isFocused = false" initialValue="14:30" /> </App> ``` %-EVENT-END %-EVENT-START invalidTime Fired when the user enters an invalid time value. ```xmlui-pg copy {2} display name="Example: invalidTime" <App var.errorMessage=""> <Text value="{errorMessage}" /> <TimeInput onInvalidTime="(error) => errorMessage = 'Invalid time entered'" onDidChange="errorMessage = ''" /> </App> ``` %-EVENT-END ``` -------------------------------------------------------------------------------- /xmlui/src/components/Checkbox/Checkbox.md: -------------------------------------------------------------------------------- ```markdown %-DESC-START **Key features:** - **Flexible labeling**: Position labels on any side and support custom label templates - **Validation support**: Built-in validation states for form error handling - **Indeterminate state**: Special visual state for mixed selections (useful for "select all" scenarios) To bind data to a `Checkbox`, use the XMLUI [Forms infrastructure](/forms). ## Checkbox Values The `initialValue` and `value` properties of the checkbox are transformed to a Boolean value to display the checked (`true`) or unchecked (`false`) state with this logic: - `null` and `undefined` go to `false`. - If the property is Boolean, the property value is used as is. - If it is a number, `NaN` and `0` result in `false`; other values represent `true`. - If the property is a string, the empty string and the literal "false" string result in `false`; others result in `true`. - The empty array value goes to `false`; other array values result in `true`. - Object values with no properties result in `false`; other values represent `true`. %-DESC-END %-PROP-START enabled ```xmlui-pg copy display {4-5, 9-10} name="Example: enabled" <App> Enabled checkboxes: <HStack> <Checkbox initialValue="true" enabled="true" /> <Checkbox initialValue="false" enabled="true" /> </HStack> Disabled checkboxes: <HStack> <Checkbox initialValue="true" enabled="false" /> <Checkbox initilaValue="false" enabled="false" /> </HStack> </App> ``` %-PROP-END %-PROP-START indeterminate This prop is commonly used if there are several other checkboxes linked to one checkbox and some items in that group of checkboxes are in a mixed state: at least one item has a different value compared to the rest. The following sample binds the state of two checkboxes to one and updates the state of the top checkbox accordingly. When the states of the bound checkboxes are different, the top checkbox is set to indeterminate: ```xmlui-pg copy display name="Example: indeterminate" ---app copy display {4} <App var.indeterminate="{false}"> <Checkbox label="Indeterminate Checkbox" indeterminate="{indeterminate}" initialValue="{cb1.value}" readOnly="true" /> <ChangeListener listenTo="{ { v1: cb1.value, v2: cb2.value } }" onDidChange="indeterminate = cb1.value !== cb2.value" /> Group of checkboxes: <HStack> <Checkbox label="Checkbox #1" id="cb1" initialValue="true" /> <Checkbox label="Checkbox #2" id="cb2" initialValue="false" /> </HStack> </App> ---desc Try this sample by clicking the bottom group of checkboxes. ``` %-PROP-END %-PROP-START label ```xmlui-pg copy display name="Example: label" <App> <Checkbox label="Example label" initialValue="true" /> <Checkbox label="Another label" intialValue="false" /> </App> ``` %-PROP-END %-PROP-START labelPosition ```xmlui-pg copy display name="Example: labelPosition" <App> <Checkbox label="Top label" labelPosition="top" initialValue="true" /> <Checkbox label="End label" labelPosition="end" initialValue="true" /> <Checkbox label="Bottom label" labelPosition="bottom" initialValue="true" /> <Checkbox label="Start label" labelPosition="start" initialValue="true" /> </App> ``` %-PROP-END %-PROP-START readOnly ```xmlui-pg copy {3} display name="Example: readOnly" <App> <Checkbox readOnly="true" label="Checked" initialValue="true" /> <Checkbox readOnly="true" label="Unchecked" intialValue="false" /> </App> ``` %-PROP-END %-API-START value You can query this read-only API property to query the checkbox's current value (`true`: checked, `false`: unchecked). See an example in the `setValue` API method. %-API-END %-API-START setValue You can use this method to set the checkbox's current value programmatically (`true`: checked, `false`: unchecked). ```xmlui-pg copy {10,13,15} display name="Example: value and setValue" <App var.changes=""> <Checkbox id="checkbox" readOnly="true" label="This checkbox can be set only programmatically" onDidChange="changes += '+'" /> <HStack> <Button label="Check" onClick="checkbox.setValue(true)" /> <Button label="Uncheck" onClick="checkbox.setValue(false)" /> </HStack> <Text>The checkbox is {checkbox.value ? "checked" : "unchecked"}</Text> <Text value="Changes: {changes}" /> </App> ``` %-API-END %-EVENT-START didChange ```xmlui-pg copy display name="Example: didChange" <App verticalAlignment="center" var.changes=""> <Checkbox label="Changeable" onDidChange="changes += '+'" /> <Checkbox label="Readonly" readOnly="true" onDidChange="changes += '-'" /> <Text value="Changes: {changes}" /> </App> ``` %-EVENT-END %-EVENT-START gotFocus Click the `Checkbox` in the example demo to change the label text. Note how clicking elsewhere resets the text to the original. ```xmlui-pg copy display name="Example: gotFocus/lostFocus" <App var.focused="{false}" verticalAlignment="center"> <Checkbox value="true" onGotFocus="focused = true" onLostFocus="focused = false" /> <Text value="{focused === true ? 'I am focused!' : 'I have lost the focus!'}" /> </App> ``` %-EVENT-END %-EVENT-START lostFocus (See the example above) %-EVENT-END ``` -------------------------------------------------------------------------------- /xmlui/src/language-server/services/common/metadata-utils.ts: -------------------------------------------------------------------------------- ```typescript import { RiEmpathizeFill } from "react-icons/ri"; import type { ComponentMetadata, ComponentPropertyMetadata } from "../../../abstractions/ComponentDefs" import { layoutOptionKeys } from "../../../components-core/descriptorHelper"; import { onPrefixRegex, stripOnPrefix } from "../../../parsers/xmlui-parser"; import { viewportSizeMd } from "../../../components/abstractions"; type RestrictedComponentMetadata = Pick<ComponentMetadata, "description" | "status" | "props" | "events" | "apis" | "contextVars" | "allowArbitraryProps" | "shortDescription"> export type ComponentMetadataCollection = Record<string, RestrictedComponentMetadata> export class MetadataProvider { constructor(private readonly metadataCollection: ComponentMetadataCollection) {} componentNames(): string[] { return Object.keys(this.metadataCollection); } getComponent(componentName: string): ComponentMetadataProvider | null { const providerData = this.metadataCollection[componentName]; if (!providerData) { return null; } return new ComponentMetadataProvider(providerData); } } export type AttributeKind = "prop" | "event" | "api" | "implicit" | "layout" export type TaggedAttribute = { name: string, kind: AttributeKind }; export class ComponentMetadataProvider { constructor(private readonly metadata: RestrictedComponentMetadata) {} /** * Retrieves the metadata for a given property, explicit or implicit. * @param name The name of the property. * @returns The metadata for the property, or `undefined` if not found. */ getProp(name: string) { return this.metadata.props[name] ?? implicitPropsMetadata[name]; } getAttr(name: string) { if (onPrefixRegex.test(name)){ const eventName = stripOnPrefix(name) const event = this.metadata.events?.[eventName]; if (event) { return event; } } const explicitProp = this.metadata.props?.[name]; if (explicitProp) { return explicitProp; } const api = this.metadata.apis?.[name]; if (api) { return api; } const layout = layoutMdForKey(name); if (layout) { return layout; } return implicitPropsMetadata[name]; } getAttrForKind({ name, kind}: TaggedAttribute){ switch (kind){ case "api": return this.metadata.apis[name]; case "event": return this.metadata.events[name]; case "prop": return this.metadata.props[name]; case "implicit": return implicitPropsMetadata[name]; case "layout": return layoutMdForKey(name) } } getAllAttributes() { const attrNames: TaggedAttribute[] = []; for (const key of Object.keys(this.metadata.props ?? {})) { attrNames.push({ name: key, kind: "prop" }); } for (const key of Object.keys(this.metadata.events ?? {})) { attrNames.push({ name: key, kind: "event" }); } for (const key of Object.keys(this.metadata.apis ?? {})) { attrNames.push({ name: key, kind: "api" }); } for (const layoutKey of layoutOptionKeys){ attrNames.push({name: layoutKey, kind: "layout"}) } for (const implicitPropKey of Object.keys(implicitPropsMetadata)){ attrNames.push({name: implicitPropKey, kind: "implicit"}) } return attrNames; } getEvent(name: string){ return this.metadata.events?.[name]; } getApi(name: string){ return this.metadata.apis?.[name]; } get events(): Record<string, string> { return this.metadata.events; } get apis(): Record<string, string> { return this.metadata.apis; } get contextVars(): Record<string, string> { return this.metadata.contextVars; } get allowArbitraryProps(): boolean { return this.metadata.allowArbitraryProps; } get shortDescription(): string { return this.metadata.shortDescription; } getMetadata(): RestrictedComponentMetadata { return this.metadata; } } function layoutMdForKey(name: string): ComponentPropertyMetadata { const metadata = { description: "Layout property. Not yet documented", }; if (layoutOptionKeys.includes(name)){ return metadata; } for(const size of viewportSizeMd){ const suffix = "-" + ((size as { value: string | number; description: string; }).value); if(name.endsWith(suffix)){ const nameWithoutSize = name.slice(0, -suffix.length); if(layoutOptionKeys.includes(nameWithoutSize)){ return metadata; } } } return null; } const implicitPropsMetadata: Record<string, ComponentPropertyMetadata> = { inspect: { description: "Determines whether the component can be inspected or not", defaultValue: false, valueType: "boolean", }, data: { description: "Specifies the data source for a component. Can be a URL string (fetched automatically), a DataSource or an expression to evaluate. Changes to this property trigger UI updates once data is loaded.", }, when: { description: "Specifies a condition that must be met for the component to be displayed", defaultValue: true, valueType: "boolean", } }; export function addOnPrefix(name: string) { return "on" + name[0].toUpperCase() + name.substring(1); } ``` -------------------------------------------------------------------------------- /docs/content/components/NoResult.md: -------------------------------------------------------------------------------- ```markdown # NoResult [#noresult] `NoResult` displays a visual indication that a query or search returned nothing. ## Properties [#properties] ### `hideIcon` (default: false) [#hideicon-default-false] This boolean property indicates if the icon should be hidden. ```xmlui-pg copy display name="Example: hideIcon" <App> <FlowLayout> <NoResult hideIcon="true" width="50%" /> <NoResult hideIcon="false" width="50%" /> </FlowLayout> </App> ``` ### `icon` (default: "noresult") [#icon-default-noresult] This property defines the icon to display with the component. This property defines the icon to display with the component. For a list of of available icons consult [`Icon` documentation](/components/Icon). ```xmlui-pg copy display name="Example: icon" <App> <NoResult icon="error" height="100%" /> </App> ``` ### `label` [#label] This property sets the label of the component. If not set, the component will not display a label. Customize the displayed text using this property. Leave empty to omit it. ```xmlui-pg copy display name="Example: label" <App> <NoResult label="Sorry, found nothing!" height="100%" /> </App> ``` ## Events [#events] This component does not have any events. ## Exposed Methods [#exposed-methods] This component does not expose any methods. ## Styling [#styling] ### Theme Variables [#theme-variables] | Variable | Default Value (Light) | Default Value (Dark) | | --- | --- | --- | | [border](../styles-and-themes/common-units/#border)-NoResult | 0px solid $borderColor | 0px solid $borderColor | | [borderBottom](../styles-and-themes/common-units/#border)-NoResult | *none* | *none* | | [borderBottomColor](../styles-and-themes/common-units/#color)-NoResult | *none* | *none* | | [borderBottomStyle](../styles-and-themes/common-units/#border-style)-NoResult | *none* | *none* | | [borderBottomWidth](../styles-and-themes/common-units/#size)-NoResult | *none* | *none* | | [borderColor](../styles-and-themes/common-units/#color)-NoResult | *none* | *none* | | [borderEndEndRadius](../styles-and-themes/common-units/#border-rounding)-NoResult | *none* | *none* | | [borderEndStartRadius](../styles-and-themes/common-units/#border-rounding)-NoResult | *none* | *none* | | [borderHorizontal](../styles-and-themes/common-units/#border)-NoResult | *none* | *none* | | [borderHorizontalColor](../styles-and-themes/common-units/#color)-NoResult | *none* | *none* | | [borderHorizontalStyle](../styles-and-themes/common-units/#border-style)-NoResult | *none* | *none* | | [borderHorizontalWidth](../styles-and-themes/common-units/#size)-NoResult | *none* | *none* | | [borderLeft](../styles-and-themes/common-units/#border)-NoResult | *none* | *none* | | [color](../styles-and-themes/common-units/#color)-NoResult | *none* | *none* | | [borderLeftStyle](../styles-and-themes/common-units/#border-style)-NoResult | *none* | *none* | | [borderLeftWidth](../styles-and-themes/common-units/#size)-NoResult | *none* | *none* | | [borderRight](../styles-and-themes/common-units/#border)-NoResult | *none* | *none* | | [color](../styles-and-themes/common-units/#color)-NoResult | *none* | *none* | | [borderRightStyle](../styles-and-themes/common-units/#border-style)-NoResult | *none* | *none* | | [borderRightWidth](../styles-and-themes/common-units/#size)-NoResult | *none* | *none* | | [borderStartEndRadius](../styles-and-themes/common-units/#border-rounding)-NoResult | *none* | *none* | | [borderStartStartRadius](../styles-and-themes/common-units/#border-rounding)-NoResult | *none* | *none* | | [borderStyle](../styles-and-themes/common-units/#border-style)-NoResult | *none* | *none* | | [borderTop](../styles-and-themes/common-units/#border)-NoResult | *none* | *none* | | [borderTopColor](../styles-and-themes/common-units/#color)-NoResult | *none* | *none* | | [borderTopStyle](../styles-and-themes/common-units/#border-style)-NoResult | *none* | *none* | | [borderTopWidth](../styles-and-themes/common-units/#size)-NoResult | *none* | *none* | | [borderHorizontal](../styles-and-themes/common-units/#border)-NoResult | *none* | *none* | | [borderVerticalColor](../styles-and-themes/common-units/#color)-NoResult | *none* | *none* | | [borderVerticalStyle](../styles-and-themes/common-units/#border-style)-NoResult | *none* | *none* | | [borderVerticalWidth](../styles-and-themes/common-units/#size)-NoResult | *none* | *none* | | [borderWidth](../styles-and-themes/common-units/#size)-NoResult | *none* | *none* | | [gap](../styles-and-themes/common-units/#size)-icon-NoResult | $space-2 | $space-2 | | [padding](../styles-and-themes/common-units/#size)-NoResult | *none* | *none* | | [paddingBottom](../styles-and-themes/common-units/#size)-NoResult | *none* | *none* | | [paddingHorizontal](../styles-and-themes/common-units/#size)-NoResult | *none* | *none* | | [paddingLeft](../styles-and-themes/common-units/#size)-NoResult | *none* | *none* | | [paddingRight](../styles-and-themes/common-units/#size)-NoResult | *none* | *none* | | [paddingTop](../styles-and-themes/common-units/#size)-NoResult | *none* | *none* | | [paddingVertical](../styles-and-themes/common-units/#size)-NoResult | $space-2 | $space-2 | | [size](../styles-and-themes/common-units/#size)-icon-NoResult | $space-8 | $space-8 | ```