This is page 20 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/components/Button/Button.md: -------------------------------------------------------------------------------- ```markdown %-DESC-START **Key features:** - **Visual hierarchy**: Choose from `solid`, `outlined`, or `ghost` variants to indicate importance - **Theme colors**: Use `primary`, `secondary`, or `attention` colors for different action types - **Icon support**: Add icons before or after text, or create icon-only buttons - **Form integration**: Automatically handles form submission when used in forms %-DESC-END %-PROP-START autoFocus %-PROP-END %-PROP-START contentPosition ```xmlui-pg copy display name="Example: content position" <App> <Button width="200px" icon="drive" label="Button" contentPosition="center" /> <Button width="200px" icon="drive" label="Button" contentPosition="start" /> <Button width="200px" icon="drive" label="Button" contentPosition="end" /> <Button width="200px" contentPosition="end"> This is a nested text </Button> </App> ``` %-PROP-END %-PROP-START icon ```xmlui-pg copy display name="Example: icon" <App> <HStack> <Button icon="drive" label="Let there be drive" /> <Button icon="drive" /> </HStack> </App> ``` %-PROP-END %-PROP-START iconPosition ```xmlui-pg copy display name="Example: icon position" <App> <HStack> <Button icon="drive" label="Left" /> <Button icon="drive" label="Right" iconPosition="right" /> </HStack> <HStack> <Button icon="drive" label="Start" iconPosition="start" /> <Button icon="drive" label="End" iconPosition="end" /> </HStack> <HStack> <Button icon="drive" label="Start (right-to-left)" iconPosition="start" direction="rtl" /> <Button icon="drive" label="End (right-to-left)" iconPosition="end" direction="rtl" /> </HStack> </App> ``` %-PROP-END %-PROP-START label ```xmlui-pg copy display name="Example: label" <App> <Button label="I am the button label" /> <Button /> <Button label="I am the button label"> <Icon name="trash" /> I am a text nested into Button </Button> </App> ``` %-PROP-END %-PROP-START variant ```xmlui-pg copy display name="Example: variant" <App> <HStack> <Button label="default (solid)" /> <Button label="solid" variant="solid" /> <Button label="outlined" variant="outlined" /> <Button label="ghost" variant="ghost" /> </HStack> </App> ``` %-PROP-END %-PROP-START themeColor ```xmlui-pg copy display name="Example: theme colors" <App> <HStack> <Button label="Button" themeColor="primary" /> <Button label="Button" themeColor="secondary" /> <Button label="Button" themeColor="attention" /> </HStack> </App> ``` %-PROP-END %-PROP-START enabled ```xmlui-pg copy display name="Example: enabled" <App> <HStack> <Button label="I am enabled (by default)" /> <Button label="I am enabled explicitly" enabled="true" /> <Button label="I am not enabled" enabled="false" /> </HStack> </App> ``` %-PROP-END %-PROP-START size ```xmlui-pg copy display name="Example: size" <App> <HStack> <Button icon="drive" label="default" /> <Button icon="drive" label="extra-small" size="xs" /> <Button icon="drive" label="small" size="sm" /> <Button icon="drive" label="medium" size="md" /> <Button icon="drive" label="large" size="lg" /> </HStack> <HStack> <Button label="default" /> <Button label="extra-small" size="xs" /> <Button label="small" size="sm" /> <Button label="medium" size="md" /> <Button label="large" size="lg" /> </HStack> </App> ``` %-PROP-END %-EVENT-START click ```xmlui-pg copy display name="Example: click" <App> <Button label="Click me!" onClick="toast('Button clicked')" /> </App> ``` %-EVENT-END %-EVENT-START gotFocus ```xmlui-pg copy display name="Example: gotFocus" <App var.text="No event" > <HStack verticalAlignment="center" > <Button label="First, click me!" onGotFocus="text = 'Focus received'" onLostFocus="text = 'Focus lost'" /> <Text value="Then, me!"/> </HStack> <Text value="{text}" /> </App> ``` %-EVENT-END %-EVENT-START lostFocus (See the example above) %-EVENT-END %-STYLE-START ### Fixed width and height Using a set of buttons with a fixed width or height is often helpful. So `Button` supports these theme variables: - `width-Button` - `height-Button` Avoid setting the `width-Button` and `height-Button` styles in the theme definition. Instead, wrap the affected button group into a `Theme` component as in the following example: ```xmlui-pg copy name="Example: Buttons with fixed width" <App> <HStack> <Theme width-Button="120px"> <Button label="Short" /> <Button label="Longer" /> <Button label="Longest" /> <Button label="Disabled" enabled="false" /> <Button label="Outlined" variant="outlined" /> </Theme> </HStack> </App> ``` %-STYLE-END ``` -------------------------------------------------------------------------------- /xmlui/src/components/Accordion/Accordion.tsx: -------------------------------------------------------------------------------- ```typescript import styles from "./Accordion.module.scss"; import { createComponentRenderer } from "../../components-core/renderers"; import { parseScssVar } from "../../components-core/theming/themeVars"; import { createMetadata, dCollapse, dDidChange, dExpand, dExpanded, dFocus, } from "../../components/metadata-helpers"; import { triggerPositionNames } from "../../components/abstractions"; import { AccordionComponent, defaultProps } from "./AccordionNative"; const COMP = "Accordion"; // See reference implementation here: https://getbootstrap.com/docs/5.3/components/accordion/ // Make the header focusable, handle ARIA attributes, and manage the state of the accordion. export const AccordionMd = createMetadata({ status: "in progress", description: `(**NOT IMPLEMENTED YET**) The \`${COMP}\` component is a collapsible container that toggles ` + `the display of content sections. It helps organize information by expanding or collapsing it ` + `based on user interaction.`, props: { triggerPosition: { description: `This property indicates the position where the trigger icon should be displayed. The \`start\` ` + `value signs the trigger is before the header text (template), and \`end\` indicates that it ` + `follows the header.`, defaultValue: defaultProps.triggerPosition, valueType: "string", availableValues: triggerPositionNames, }, collapsedIcon: { description: "This property is the name of the icon that is displayed when the accordion is " + "collapsed. This property is the name of the icon that is displayed when the accordion is expanded. If not provided, a chevron-down icon is used.", valueType: "string", defaultValue: defaultProps.collapsedIcon, }, expandedIcon: { description: "This property is the name of the icon that is displayed when the accordion is " + "expanded. If not provided, a chevron-up icon is used.", valueType: "string", }, hideIcon: { description: `This property indicates that the trigger icon is not displayed (\`true\`).`, defaultValue: defaultProps.hideIcon, valueType: "boolean", }, rotateExpanded: { description: `This optional property defines the rotation angle of the expanded icon (relative to the collapsed icon).`, valueType: "string", defaultValue: defaultProps.rotateExpanded, }, }, events: { displayDidChange: dDidChange(COMP), }, apis: { expanded: { description: `This method returns \`true\` if the accordion is expanded, and \`false\` if it is collapsed.`, signature: "get expanded(): boolean", }, expand: { description: `This method expands the accordion, making its content visible.`, signature: "expand()", }, collapse: { description: `This method collapses the accordion, hiding its content.`, signature: "collapse()", }, toggle: { description: `This method toggles the state of the ${COMP} between expanded and collapsed.`, signature: "toggle()", }, focus: dFocus(COMP), }, themeVars: parseScssVar(styles.themeVars), defaultThemeVars: { [`paddingHorizontal-header-${COMP}`]: "$space-3", [`paddingVertical-header-${COMP}`]: "$space-3", [`verticalAlignment-header-${COMP}`]: "center", [`fontSize-header-${COMP}`]: "$fontSize-base", [`fontWeight-header-${COMP}`]: "$fontWeight-normal", [`fontFamily-header-${COMP}`]: "$fontFamily", [`border-${COMP}`]: "0px solid $borderColor", [`width-icon-${COMP}`]: "", [`height-icon-${COMP}`]: "", [`backgroundColor-header-${COMP}`]: "$color-primary-500", [`backgroundColor-header-${COMP}-hover`]: "$color-primary-400", [`color-header-${COMP}`]: "$color-surface-50", [`color-content-${COMP}`]: "$textColor-primary", [`backgroundColor-content-${COMP}`]: "transparent", [`color-icon-${COMP}`]: "$color-surface-50", }, }); export const accordionComponentRenderer = createComponentRenderer( COMP, AccordionMd, ({ node, renderChild, extractValue, lookupEventHandler, registerComponentApi, className }) => { return ( <AccordionComponent className={className} triggerPosition={extractValue(node.props?.triggerPosition)} collapsedIcon={extractValue(node.props.collapsedIcon)} expandedIcon={extractValue(node.props.expandedIcon)} hideIcon={extractValue.asOptionalBoolean(node.props.hideIcon)} rotateExpanded={extractValue(node.props.rotateExpanded)} onDisplayDidChange={lookupEventHandler("displayDidChange")} registerComponentApi={registerComponentApi} > {renderChild(node.children)} </AccordionComponent> ); }, ); ``` -------------------------------------------------------------------------------- /xmlui/src/components/Checkbox/Checkbox.tsx: -------------------------------------------------------------------------------- ```typescript import styles from "../Toggle/Toggle.module.scss"; import { createComponentRenderer } from "../../components-core/renderers"; import { parseScssVar } from "../../components-core/theming/themeVars"; import { createMetadata, dAutoFocus, dClick, dComponent, dDidChange, dEnabled, dGotFocus, dIndeterminate, dInitialValue, dInternal, dLostFocus, dReadonly, dRequired, dValidationStatus, } from "../../components/metadata-helpers"; import { defaultProps as toggleDefaultProps, Toggle } from "../Toggle/Toggle"; import { MemoizedItem } from "../container-helpers"; export const defaultProps = { ...toggleDefaultProps, labelPosition: "end", }; const COMP = "Checkbox"; export const CheckboxMd = createMetadata({ status: "stable", description: "`Checkbox` allows users to make binary choices with a clickable box that shows " + "checked/unchecked states. It's essential for settings, preferences, multi-select " + "lists, and accepting terms or conditions.", parts: { label: { description: "The label displayed for the checkbox.", }, input: { description: "The checkbox input area.", }, }, props: { indeterminate: dIndeterminate(toggleDefaultProps.indeterminate), required: dRequired(), initialValue: dInitialValue(toggleDefaultProps.initialValue), autoFocus: dAutoFocus(), readOnly: dReadonly(), enabled: dEnabled(), validationStatus: dValidationStatus(toggleDefaultProps.validationStatus), description: dInternal( `(*** NOT IMPLEMENTED YET ***) This optional property displays an alternate description ` + `of the ${COMP} besides its label.`, ), inputTemplate: dComponent("This property is used to define a custom checkbox input template"), }, childrenAsTemplate: "inputTemplate", events: { click: dClick(COMP), gotFocus: dGotFocus(COMP), lostFocus: dLostFocus(COMP), didChange: dDidChange(COMP), }, apis: { value: { description: `This method returns the current value of the ${COMP}.`, signature: "get value(): boolean", }, setValue: { description: `This method sets the current value of the ${COMP}.`, signature: "set value(value: boolean): void", parameters: { value: "The new value to set for the checkbox.", }, }, }, themeVars: parseScssVar(styles.themeVars), limitThemeVarsToComponent: true, defaultThemeVars: { [`borderColor-checked-${COMP}-error`]: `$borderColor-${COMP}-error`, [`backgroundColor-checked-${COMP}-error`]: `$borderColor-${COMP}-error`, [`borderColor-checked-${COMP}-warning`]: `$borderColor-${COMP}-warning`, [`backgroundColor-checked-${COMP}-warning`]: `$borderColor-${COMP}-warning`, [`borderColor-checked-${COMP}-success`]: `$borderColor-${COMP}-success`, [`backgroundColor-checked-${COMP}-success`]: `$borderColor-${COMP}-success`, [`backgroundColor-indicator-${COMP}`]: "$backgroundColor-primary", [`borderColor-checked-${COMP}`]: "$color-primary-500", [`backgroundColor-checked-${COMP}`]: "$color-primary-500", [`backgroundColor-${COMP}--disabled`]: "$color-surface-200", }, }); export const checkboxComponentRenderer = createComponentRenderer( COMP, CheckboxMd, ({ node, extractValue, className, updateState, lookupEventHandler, state, registerComponentApi, renderChild, layoutContext, }) => { const inputTemplate = node.props.inputTemplate; return ( <Toggle inputRenderer={ inputTemplate ? (contextVars) => ( <MemoizedItem contextVars={contextVars} node={inputTemplate} renderChild={renderChild} layoutContext={layoutContext} /> ) : undefined } enabled={extractValue.asOptionalBoolean(node.props.enabled)} className={className} initialValue={extractValue.asOptionalBoolean( node.props.initialValue, defaultProps.initialValue, )} value={state?.value} readOnly={extractValue.asOptionalBoolean(node.props.readOnly)} validationStatus={extractValue(node.props.validationStatus)} updateState={updateState} onClick={lookupEventHandler("click")} onDidChange={lookupEventHandler("didChange")} onFocus={lookupEventHandler("gotFocus")} onBlur={lookupEventHandler("lostFocus")} required={extractValue.asOptionalBoolean(node.props.required)} indeterminate={extractValue.asOptionalBoolean(node.props.indeterminate)} registerComponentApi={registerComponentApi} autoFocus={extractValue.asOptionalBoolean(node.props.autoFocus)} /> ); }, ); ``` -------------------------------------------------------------------------------- /xmlui/src/components/Charts/RadarChart/RadarChart.tsx: -------------------------------------------------------------------------------- ```typescript import { RadarChart, defaultProps } from "./RadarChartNative"; import { createComponentRenderer } from "../../../components-core/renderers"; import { createMetadata } from "../../metadata-helpers"; import { MemoizedItem } from "../../container-helpers"; const COMP = "RadarChart"; export const RadarChartMd = createMetadata({ status: "experimental", description: "Interactive radar chart for displaying multivariate data in a two-dimensional chart of three or more quantitative variables", docFolder: "Charts/RadarChart", props: { data: { description: "This property is used to provide the component with data to display. " + "The data needs to be an array of objects.", }, dataKeys: { description: "This property specifies the keys in the data objects that should be used for rendering the chart elements. " + "E.g. 'value' or 'amount'.", valueType: "string", }, nameKey: { description: "Specifies the key in the data objects that will be used to label the different data series.", valueType: "string", }, hideGrid: { description: "Determines whether the polar grid should be hidden. If set to `true`, the grid will not be rendered.", valueType: "boolean", defaultValue: defaultProps.hideGrid, }, hideAngleAxis: { description: "Determines whether the angle axis should be hidden. If set to `true`, the angle axis will not be rendered.", valueType: "boolean", defaultValue: defaultProps.hideAngleAxis, }, hideRadiusAxis: { description: "Determines whether the radius axis should be hidden. If set to `true`, the radius axis will not be rendered.", valueType: "boolean", defaultValue: defaultProps.hideRadiusAxis, }, hideTooltip: { description: "Determines whether the tooltip should be hidden. If set to `true`, the tooltip will not be rendered.", valueType: "boolean", defaultValue: defaultProps.hideTooltip, }, showLegend: { description: "Determines whether the legend should be shown. If set to `true`, the legend will be rendered.", valueType: "boolean", defaultValue: defaultProps.showLegend, }, filled: { description: "Determines whether the radar areas should be filled. If set to `true`, areas will be filled with color.", valueType: "boolean", defaultValue: defaultProps.filled, }, strokeWidth: { description: "Sets the stroke width for the radar lines. Higher values create thicker lines.", valueType: "number", defaultValue: defaultProps.strokeWidth, }, fillOpacity: { description: "Sets the fill opacity for the radar areas when filled is true. Value between 0 and 1.", valueType: "number", defaultValue: defaultProps.fillOpacity, }, tooltipTemplate: { description: "This property allows replacing the default template to display a tooltip.", }, }, events: { // Standard chart events - customize based on chart type }, apis: { // Chart-specific APIs if needed }, contextVars: { // Add context variables if needed }, }); // Component renderer export const radarChartComponentRenderer = createComponentRenderer( COMP, RadarChartMd, ({ extractValue, node, className, renderChild }: any) => { return ( <RadarChart className={className} data={extractValue(node.props?.data)} nameKey={extractValue(node.props?.nameKey)} dataKeys={extractValue(node.props?.dataKeys)} hideGrid={extractValue.asOptionalBoolean(node.props?.hideGrid)} hideAngleAxis={extractValue.asOptionalBoolean(node.props?.hideAngleAxis)} hideRadiusAxis={extractValue.asOptionalBoolean(node.props?.hideRadiusAxis)} hideTooltip={extractValue.asOptionalBoolean(node.props?.hideTooltip)} showLegend={extractValue.asOptionalBoolean(node.props?.showLegend)} filled={extractValue.asOptionalBoolean(node.props?.filled)} strokeWidth={extractValue.asOptionalNumber(node.props?.strokeWidth)} fillOpacity={extractValue.asOptionalNumber(node.props?.fillOpacity)} tooltipRenderer={ node.props.tooltipTemplate ? (tooltipData) => { return ( <MemoizedItem node={node.props.tooltipTemplate} item={tooltipData} contextVars={{ $tooltip: tooltipData, }} renderChild={renderChild} /> ); } : undefined } > {renderChild(node.children)} </RadarChart> ); }, ); ``` -------------------------------------------------------------------------------- /xmlui/src/components/TextBox/TextBox.module.scss: -------------------------------------------------------------------------------- ```scss @use "../../components-core/theming/themes" as t; // --- This code snippet is required to collect the theme variables used in this module $themeVars: ( ); @function createThemeVar($componentVariable) { $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global; @return t.getThemeVar($themeVars, $componentVariable); } $componentName: "TextBox"; $themeVars: t.composePaddingVars($themeVars, $componentName); // --- CSS properties of a particular TextBox variant @mixin variant($variantName) { border-radius: createThemeVar("Input:borderRadius-#{$componentName}-#{$variantName}"); border-color: createThemeVar("Input:borderColor-#{$componentName}-#{$variantName}"); border-width: createThemeVar("Input:borderWidth-#{$componentName}-#{$variantName}"); border-style: createThemeVar("Input:borderStyle-#{$componentName}-#{$variantName}"); font-size: createThemeVar("Input:fontSize-#{$componentName}-#{$variantName}"); background-color: createThemeVar("Input:backgroundColor-#{$componentName}-#{$variantName}"); box-shadow: createThemeVar("Input:boxShadow-#{$componentName}-#{$variantName}"); color: createThemeVar("Input:textColor-#{$componentName}-#{$variantName}"); &:hover { border-color: createThemeVar("Input:borderColor-#{$componentName}-#{$variantName}--hover"); background-color: createThemeVar("Input:backgroundColor-#{$componentName}-#{$variantName}--hover" ); box-shadow: createThemeVar("Input:boxShadow-#{$componentName}-#{$variantName}--hover"); color: createThemeVar("Input:textColor-#{$componentName}-#{$variantName}--hover"); } &:focus-within { border-color: createThemeVar("Input:borderColor-#{$componentName}-#{$variantName}--focus"); background-color: createThemeVar("Input:backgroundColor-#{$componentName}-#{$variantName}--focus" ); box-shadow: createThemeVar("Input:boxShadow-#{$componentName}-#{$variantName}--focus"); color: createThemeVar("Input:textColor-#{$componentName}-#{$variantName}--focus"); } &:has(.input:focus-visible) { outline-width: createThemeVar("Input:outlineWidth-#{$componentName}-#{$variantName}--focus"); outline-color: createThemeVar("Input:outlineColor-#{$componentName}-#{$variantName}--focus"); outline-style: createThemeVar("Input:outlineStyle-#{$componentName}-#{$variantName}--focus"); outline-offset: createThemeVar("Input:outlineOffset-#{$componentName}-#{$variantName}--focus"); } .input { &::placeholder { color: createThemeVar("Input:textColor-placeholder-#{$componentName}-#{$variantName}"); font-size: createThemeVar("Input:fontSize-placeholder-#{$componentName}-#{$variantName}"); } } .adornment { color: createThemeVar("Input:color-adornment-#{$componentName}-#{$variantName}"); } .passwordToggle { cursor: pointer; color: createThemeVar("Input:color-passwordToggle-#{$componentName}"); padding-left: createThemeVar("Input:paddingLeft-passwordToggle-#{$componentName}"); padding-right: createThemeVar("Input:paddingRight-passwordToggle-#{$componentName}"); &:hover { color: createThemeVar("Input:color-passwordToggle-#{$componentName}--hover"); } &:focus { color: createThemeVar("Input:color-passwordToggle-#{$componentName}--focus"); } } } @layer components { .inputRoot { display: flex; align-items: center; gap: createThemeVar("Input:gap-adornment-#{$componentName}"); width: 100%; border-style: solid; border-width: 1px; transition: background-color ease-in 0.1s; overflow: hidden; @include t.paddingVars($themeVars, $componentName); @include variant("default"); &.error { @include variant("error"); } &.warning { @include variant("warning"); } &.valid { @include variant("success"); } &:has(.input:is(:disabled)) { cursor: not-allowed; background-color: createThemeVar("Input:backgroundColor-#{$componentName}--disabled"); color: createThemeVar("Input:textColor-#{$componentName}--disabled"); border-color: createThemeVar("Input:borderColor-#{$componentName}--disabled"); } .input { font-size: inherit; color: inherit; border: 0; outline: none !important; background-color: transparent; width: 100%; padding: 0; cursor: inherit; &::-ms-reveal { display: none; } // Remove default search input styles if type="search" &[type="search"]::-webkit-search-cancel-button, &[type="search"]::-webkit-search-decoration { -webkit-appearance: none; appearance: none; } } } .readOnly { cursor: default; } } // --- We export the theme variables to add them to the component renderer :export { themeVars: t.json-stringify($themeVars); } ``` -------------------------------------------------------------------------------- /xmlui/src/components-core/devtools/InspectorDialog.tsx: -------------------------------------------------------------------------------- ```typescript import React, { type CSSProperties, type ReactNode, useEffect, useRef, useState } from "react"; import { composeRefs } from "@radix-ui/react-compose-refs"; import classnames from "classnames"; import * as Dialog from "@radix-ui/react-dialog"; import styles from "./InspectorDialog.module.scss"; import { useTheme } from "xmlui"; import { motion, AnimatePresence } from "framer-motion"; // ===================================================================================================================== // React component definition const MotionContent = motion.create(Dialog.Content); type ModalProps = { style?: CSSProperties; children?: ReactNode; isOpen: boolean; setIsOpen: (isOpen: boolean) => void; clickPosition: { x: number; y: number }; }; const overlayVariants = { visible: { opacity: 1 }, hidden: { opacity: 0 }, }; const contentVariants = { initial: (custom: { x: number; y: number }) => ({ opacity: 0, scale: 0.2, x: custom.x - window.innerWidth / 2, y: custom.y - window.innerHeight / 2, }), animate: { opacity: 1, scale: 1, x: 0, y: 0, }, exit: { opacity: 0, scale: 0.2, transition: { duration: 0.2 }, }, }; function durationToSeconds(durationString?: string) { if (!durationString) { return undefined; } const trimmedString = durationString.trim(); if (trimmedString.endsWith("ms")) { const milliseconds = parseFloat(trimmedString); return milliseconds / 1000; } else if (trimmedString.endsWith("s")) { return parseFloat(trimmedString); } else { return parseFloat(trimmedString); } } export const InspectorDialog = React.forwardRef( ( { children, style, isOpen, setIsOpen, clickPosition }: ModalProps, ref: React.Ref<HTMLDivElement>, ) => { const { root, getThemeVar } = useTheme(); const modalRef = useRef<HTMLDivElement>(null); const composedRef = ref ? composeRefs(ref, modalRef) : modalRef; const [rendered, setRendered] = useState(true); useEffect(() => { if (isOpen) { modalRef.current?.focus(); } }, [isOpen]); // https://github.com/radix-ui/primitives/issues/2122#issuecomment-2140827998 useEffect(() => { if (isOpen) { // Pushing the change to the end of the call stack const timer = setTimeout(() => { document.body.style.pointerEvents = ""; }, 0); return () => clearTimeout(timer); } else { document.body.style.pointerEvents = "auto"; } }, [isOpen]); if (!root) { return null; } const onExitComplete = () => { setIsOpen(false); }; return ( <Dialog.Root defaultOpen={false} open={isOpen} onOpenChange={setRendered}> <Dialog.Portal container={root}> <AnimatePresence onExitComplete={onExitComplete}> {rendered && ( <Dialog.Overlay className={styles.overlay} forceMount> <motion.div key="overlay" className={styles.overlayBg} variants={overlayVariants} initial="hidden" animate="visible" exit="hidden" transition={{ duration: 0.2, ease: [0.16, 1, 0.3, 1], }} /> <MotionContent forceMount onPointerDownOutside={(event) => { if ( event.target instanceof Element && event.target.closest("._debug-inspect-button") !== null ) { //we prevent the auto modal close on clicking the inspect button event.preventDefault(); } }} > <motion.div ref={composedRef} className={classnames(styles.content, styles.contentWrapper)} variants={contentVariants} custom={{ x: clickPosition.x, y: clickPosition.y }} initial="initial" animate="animate" exit="exit" transition={{ duration: durationToSeconds(getThemeVar("duration-startAnimation-ModalDialog")) || 0.8, ease: [0.16, 1, 0.3, 1], }} style={{ ...style, gap: undefined }} > {children} </motion.div> </MotionContent> </Dialog.Overlay> )} </AnimatePresence> </Dialog.Portal> </Dialog.Root> ); }, ); InspectorDialog.displayName = "InspectorDialog"; ``` -------------------------------------------------------------------------------- /xmlui/dev-docs/next/tiptap-design-considerations.md: -------------------------------------------------------------------------------- ```markdown # Tiptap Design Considerations for XMLUI Markdown Interop ## Context XMLUI uses Markdown as the source of truth for all documentation and rich text content. The Markdown component supports a wide range of features, including GFM (GitHub Flavored Markdown) extensions, and maps Markdown/HTML tags to XMLUI's own React components for consistent theming and behavior. ## Interop Challenges - **Tiptap (rich editor) natively produces HTML, not Markdown.** - **Markdown is less expressive than HTML.** Some HTML features cannot be round-tripped to Markdown. - **XMLUI Markdown only supports a subset of HTML tags,** mapped via the HTMLTags component. Arbitrary HTML is not guaranteed to render. ## Design Options ### 1. Plain Markdown Editor - Simple `<textarea>` for raw Markdown editing. - No conversion needed; what the user sees is what is stored. - Power users can use all Markdown features. ### 2. Rich Editor Producing Markdown - Use Tiptap for WYSIWYG editing, but restrict features to those that map cleanly to Markdown and XMLUI's Markdown component. - On save, convert Tiptap's HTML output to Markdown (using a library like turndown). - On load, convert Markdown to HTML for editing (using marked or markdown-it). - Warn users about possible formatting loss if switching between modes. ## Supported Features | Feature/Tag | Markdown | HTML | XMLUI Markdown Support | Notes | |------------------|----------|-----------------------------|-----------------------|------------------------------| | Headings | Yes | `<h1>`-`<h6>` | Yes | Standard | | Bold/Italic | Yes | `<b>`, `<i>`, `<strong>`, `<em>` | Yes | Mapped to Text variants | | Lists | Yes | `<ul>`, `<ol>`, `<li>` | Yes | Standard | | Links | Yes | `<a>` | Yes | Mapped to LinkNative | | Code/Pre | Yes | `<code>`, `<pre>` | Yes | Mapped to Text/PreTag | | Tables | GFM | `<table>` | Yes (GFM) | Supported via remark-gfm | | Images | Yes | `<img>` | Yes | Standard | | Blockquote | Yes | `<blockquote>` | Yes | Standard | | Details/Section | No (MD) | `<details>`, `<section>` | Yes (custom) | Special handling | | Custom HTML | No | Most | No/Partial | Only mapped tags allowed | ## Best Practices - **Keep Markdown as the canonical format.** - **Restrict Tiptap features to those that map to XMLUI Markdown/HTMLTags.** - **Warn users about possible formatting loss when switching between rich/plain modes.** - **Test round-tripping** (Markdown → HTML → Markdown) for fidelity. - **Document any limitations or unsupported features.** ## Open Questions - Should the conversion logic live in the editor component or in global handlers? - How should we handle features/extensions that are not supported by XMLUI Markdown? - What is the best UX for switching between plain and rich modes? ## Focused Scope for Tiptap/TableEditor Exercise ### Motivation The primary motivation for the Tiptap exercise is to demonstrate, in developer documentation, how to wrap a real, useful component in XMLUI. The chosen example is a TableEditor, which addresses a common pain point: creating and editing Markdown tables for documentation. ### Scope and Workflow - The TableEditor provides a spreadsheet-like UI for editing tables. - Users can switch to a code view to see the generated Markdown table. - The workflow is: edit your table visually, copy the Markdown, and paste it into your documentation (e.g., in VSCode). - There is no need to solve file persistence, in-situ editing, or live site integration for this exercise. - This mirrors the current workflow where developers use external tools to generate Markdown tables, but brings the experience into the XMLUI/React context. ### Rationale - This approach is practical and developer-focused, providing immediate utility without overcomplicating the implementation. - It showcases XMLUI's extensibility and ability to integrate rich, interactive components. - The TableEditor can be demonstrated as a standalone tool or as a Tiptap node/component, but the main value is in Markdown table generation. ### Next Step The next step is to rename the Editor component to TableEditor to reflect this focused scope. --- *This document is a living record of design considerations for Tiptap/Markdown interop in XMLUI. Update as the implementation evolves.* ``` -------------------------------------------------------------------------------- /xmlui/src/components/Charts/AreaChart/AreaChart.md: -------------------------------------------------------------------------------- ```markdown %-DESC-START Interactive area chart for showing data trends over time with filled areas under the curve. **Key features:** - **Time series visualization**: Perfect for showing data trends over time with filled areas under the curve - **Multiple data series**: Display several metrics on the same chart with different colored areas - **Stacked vs overlapping**: Stack areas on top of each other or display them overlapping - **Curved lines**: Use smooth curves for more visually appealing continuous data - **Custom formatting**: Use `tickFormatter` to format axis labels %-DESC-END %-PROP-START data ```xml <AreaChart nameKey="name" data="{[ { name: 'Jan', value: 100 }, { name: 'Feb', value: 150 }, { name: 'Mar', value: 120 } ]}" dataKeys="{['value']}" /> ``` %-PROP-END %-PROP-START nameKey ```xml <AreaChart nameKey="month" data="{[ { month: 'Jan', sales: 1200, profit: 400 }, { month: 'Feb', sales: 1900, profit: 600 }, { month: 'Mar', sales: 1500, profit: 500 } ]}" dataKeys="{['sales', 'profit']}" /> ``` %-PROP-END %-PROP-START dataKeys ```xml <AreaChart nameKey="category" data="{[ { category: 'A', value1: 100, value2: 200 }, { category: 'B', value1: 150, value2: 250 }, { category: 'C', value1: 120, value2: 180 } ]}" dataKeys="{['value1', 'value2']}" /> ``` %-PROP-END %-PROP-START hideX ```xml <AreaChart nameKey="name" data="{[ { name: 'A', value: 100 }, { name: 'B', value: 200 }, { name: 'C', value: 150 } ]}" dataKeys="{['value']}" hideX="true" /> ``` %-PROP-END %-PROP-START hideY ```xml <AreaChart nameKey="name" data="{[ { name: 'A', value: 100 }, { name: 'B', value: 200 }, { name: 'C', value: 150 } ]}" dataKeys="{['value']}" hideY="true" /> ``` %-PROP-END %-PROP-START hideTickX ```xml <AreaChart nameKey="name" data="{[ { name: 'A', value: 100 }, { name: 'B', value: 200 }, { name: 'C', value: 150 } ]}" dataKeys="{['value']}" hideTickX="true" /> ``` %-PROP-END %-PROP-START hideTickY ```xml <AreaChart nameKey="name" data="{[ { name: 'A', value: 100 }, { name: 'B', value: 200 }, { name: 'C', value: 150 } ]}" dataKeys="{['value']}" hideTickY="true" /> ``` %-PROP-END %-PROP-START hideTooltip ```xml <AreaChart nameKey="name" data="{[ { name: 'A', value: 100 }, { name: 'B', value: 200 }, { name: 'C', value: 150 } ]}" dataKeys="{['value']}" hideTooltip="true" /> ``` %-PROP-END %-PROP-START showLegend ```xml <AreaChart nameKey="quarter" data="{[ { quarter: 'Q1', revenue: 1000, expenses: 800, profit: 200 }, { quarter: 'Q2', revenue: 1200, expenses: 900, profit: 300 }, { quarter: 'Q3', revenue: 1100, expenses: 850, profit: 250 }, { quarter: 'Q4', revenue: 1400, expenses: 1000, profit: 400 } ]}" dataKeys="{['revenue', 'expenses', 'profit']}" showLegend="true" /> ``` %-PROP-END %-PROP-START stacked ```xml <AreaChart nameKey="category" data="{[ { category: 'A', value1: 100, value2: 200 }, { category: 'B', value1: 150, value2: 250 }, { category: 'C', value1: 120, value2: 180 } ]}" dataKeys="{['value1', 'value2']}" stacked="true" /> ``` %-PROP-END %-PROP-START curved ```xml <AreaChart nameKey="time" data="{[ { time: '00:00', temperature: 18 }, { time: '06:00', temperature: 15 }, { time: '12:00', temperature: 25 }, { time: '18:00', temperature: 22 }, { time: '24:00', temperature: 19 } ]}" dataKeys="{['temperature']}" curved="true" /> ``` %-PROP-END %-PROP-START tooltipTemplate ```xmlui-pg copy display height="320px" name="Example: tooltipTemplate" /tooltipTemplate/ <App> <AreaChart height="240px" data="{[ { 'month': 'Jan', 'sales': 1200, 'profit': 400 }, { 'month': 'Feb', 'sales': 1900, 'profit': 600 }, { 'month': 'Mar', 'sales': 1500, 'profit': 500 }, { 'month': 'Apr', 'sales': 1800, 'profit': 700 } ]}" dataKeys="{['sales', 'profit']}" nameKey="month" > <property name="tooltipTemplate"> <VStack backgroundColor='white' padding="$space-2"> <Text fontWeight='bold'>{$tooltip.label}</Text> <HStack> <Text color='blue'>Sales: {$tooltip.payload.sales}</Text> <Text color='green'>Profit: {$tooltip.payload.profit}</Text> </HStack> </VStack> </property> </AreaChart> </App> ``` The `tooltipTemplate` prop allows you to customize the appearance and content of chart tooltips. The template receives a `$tooltip` context variable containing: - `$tooltip.label`: The label for the data point (typically the nameKey value) - `$tooltip.payload`: An object containing all data values for the hovered point - `$tooltip.active`: Boolean indicating if the tooltip is currently active %-PROP-END ``` -------------------------------------------------------------------------------- /xmlui/src/components/Heading/HeadingNative.tsx: -------------------------------------------------------------------------------- ```typescript import React, { type CSSProperties, type ForwardedRef, forwardRef, type ReactNode, useCallback, useContext, useEffect, useRef, useState, } from "react"; import { composeRefs } from "@radix-ui/react-compose-refs"; import classnames from "classnames"; import styles from "./Heading.module.scss"; import { getMaxLinesStyle } from "../../components-core/utils/css-utils"; import { TableOfContentsContext } from "../../components-core/TableOfContentsContext"; import { useIsomorphicLayoutEffect } from "../../components-core/utils/hooks"; import type { HeadingLevel } from "./abstractions"; import { Link } from "@remix-run/react"; import { useAppContext } from "../../components-core/AppContext"; import type { RegisterComponentApiFn } from "../../abstractions/RendererDefs"; export type HeadingProps = { uid?: string; level?: HeadingLevel; children: ReactNode; sx?: CSSProperties; style?: CSSProperties; maxLines?: number; preserveLinebreaks?: boolean; ellipses?: boolean; title?: string; className?: string; showAnchor?: boolean; registerComponentApi?: RegisterComponentApiFn; [furtherProps: string]: any; }; export const defaultProps: Pick< HeadingProps, "level" | "ellipses" | "omitFromToc" | "maxLines" | "preserveLinebreaks" | "showAnchor" > = { level: "h1", ellipses: true, omitFromToc: false, maxLines: 0, preserveLinebreaks: false, showAnchor: false, }; export const Heading = forwardRef(function Heading( { uid, level = defaultProps.level, children, sx, style, title, maxLines = defaultProps.maxLines, preserveLinebreaks, ellipses = defaultProps.ellipses, className, omitFromToc = defaultProps.omitFromToc, showAnchor, registerComponentApi, ...furtherProps }: HeadingProps, forwardedRef: ForwardedRef<HTMLHeadingElement>, ) { const Element = level?.toLowerCase() as HeadingLevel; const elementRef = useRef<HTMLHeadingElement>(null); const [anchorId, setAnchorId] = useState<string | null>(null); const anchorRef = useRef<HTMLAnchorElement>(null); const tableOfContentsContext = useContext(TableOfContentsContext); const registerHeading = tableOfContentsContext?.registerHeading; const appContext = useAppContext(); if (showAnchor === undefined) { showAnchor = appContext?.appGlobals?.showHeadingAnchors ?? false; } const ref = forwardedRef ? composeRefs(elementRef, forwardedRef) : elementRef; const scrollIntoView = useCallback((options?: ScrollIntoViewOptions) => { if (elementRef.current) { elementRef.current.scrollIntoView({ behavior: 'smooth', block: 'start', ...options, }); } }, []); const hasOverflow = useCallback(() => { if (elementRef.current) { const element = elementRef.current; return element.scrollWidth > element.clientWidth || element.scrollHeight > element.clientHeight; } return false; }, []); useEffect(() => { registerComponentApi?.({ scrollIntoView, hasOverflow, }); }, [registerComponentApi, scrollIntoView, hasOverflow]); useEffect(() => { if (elementRef.current) { const newAnchorId = elementRef.current.textContent ?.trim() ?.replace(/[^\w\s-]/g, "") ?.replace(/\s+/g, "-") ?.toLowerCase(); setAnchorId(newAnchorId || null); } }, []); useIsomorphicLayoutEffect(() => { if (elementRef.current && anchorId && !omitFromToc) { return registerHeading?.({ id: anchorId, level: parseInt(level.replace("h", "")), text: elementRef.current.textContent!.trim().replace(/#$/, ""), // Remove trailing # anchor: anchorRef.current, }); } }, [anchorId, registerHeading, level, omitFromToc]); return ( <Element {...furtherProps} ref={ref} id={uid} title={title} style={{ ...sx, ...style, ...getMaxLinesStyle(maxLines) }} className={classnames(styles.heading, styles[Element], className, { [styles.truncateOverflow]: maxLines > 0, [styles.preserveLinebreaks]: preserveLinebreaks, [styles.noEllipsis]: !ellipses, })} > {anchorId && ( <span ref={anchorRef} id={anchorId} className={styles.anchorRef} data-anchor={true} /> )} {children} {showAnchor && anchorId && ( <Link to={`#${anchorId}`} aria-hidden="true" onClick={(event) => { // cmd/ctrl + click - open in new tab, don't prevent that if (tableOfContentsContext) { if (!event.ctrlKey && !event.metaKey && !event.metaKey) { event.preventDefault(); } tableOfContentsContext.scrollToAnchor(anchorId, true); } }} > # </Link> )} </Element> ); }); ``` -------------------------------------------------------------------------------- /xmlui/src/components/Splitter/VSplitter.spec.ts: -------------------------------------------------------------------------------- ```typescript import { getBounds } from "../../testing/component-test-helpers"; import { expect, test } from "../../testing/fixtures"; // ============================================================================= // BASIC FUNCTIONALITY TESTS // ============================================================================= test.describe("Basic Functionality", () => { test("renders with basic setup", async ({ initTestBed, page }) => { await initTestBed(` <VSplitter height="200px" width="400px" testId="vsplitter"> <Stack backgroundColor="lightblue" height="100%" testId="primary"/> <Stack backgroundColor="darksalmon" height="100%" testId="secondary"/> </VSplitter> `); await expect(page.getByTestId("vsplitter")).toBeVisible(); await expect(page.getByTestId("primary")).toBeVisible(); await expect(page.getByTestId("secondary")).toBeVisible(); }); test("defaults to vertical orientation", async ({ initTestBed, page }) => { await initTestBed(` <VSplitter height="200px" width="400px" testId="vsplitter"> <Stack backgroundColor="lightblue" height="100%" testId="primary"/> <Stack backgroundColor="darksalmon" height="100%" testId="secondary"/> </VSplitter> `); const primary = page.getByTestId("primary"); const secondary = page.getByTestId("secondary"); const primaryBounds = await getBounds(primary); const secondaryBounds = await getBounds(secondary); // In vertical orientation, primary should be above secondary expect(primaryBounds.bottom).toBeLessThanOrEqual(secondaryBounds.top + 10); // Allow for small overlap due to resizer }); test("ignores orientation property when explicitly set", async ({ initTestBed, page }) => { await initTestBed(` <VSplitter height="200px" width="400px" orientation="horizontal" testId="vsplitter"> <Stack backgroundColor="lightblue" height="100%" testId="primary"/> <Stack backgroundColor="darksalmon" height="100%" testId="secondary"/> </VSplitter> `); const primary = page.getByTestId("primary"); const secondary = page.getByTestId("secondary"); const primaryBounds = await getBounds(primary); const secondaryBounds = await getBounds(secondary); // Even with orientation="horizontal", VSplitter should still be vertical // Primary should be above secondary, NOT to the left of it expect(primaryBounds.bottom).toBeLessThanOrEqual(secondaryBounds.top + 10); }); test("works with swapped property", async ({ initTestBed, page }) => { await initTestBed(` <VSplitter height="200px" width="400px" swapped="true" testId="vsplitter"> <Stack backgroundColor="lightblue" height="100%" testId="primary"/> <Stack backgroundColor="darksalmon" height="100%" testId="secondary"/> </VSplitter> `); const primary = page.getByTestId("primary"); const secondary = page.getByTestId("secondary"); const primaryBounds = await getBounds(primary); const secondaryBounds = await getBounds(secondary); // With swapped=true, secondary should be above primary expect(secondaryBounds.bottom).toBeLessThanOrEqual(primaryBounds.top + 10); }); test("maintains vertical orientation even with invalid orientation values", async ({ initTestBed, page }) => { await initTestBed(` <VSplitter height="200px" width="400px" orientation="invalid-value" testId="vsplitter"> <Stack backgroundColor="lightblue" height="100%" testId="primary"/> <Stack backgroundColor="darksalmon" height="100%" testId="secondary"/> </VSplitter> `); const primary = page.getByTestId("primary"); const secondary = page.getByTestId("secondary"); const primaryBounds = await getBounds(primary); const secondaryBounds = await getBounds(secondary); // Should still be vertical regardless of invalid orientation value expect(primaryBounds.bottom).toBeLessThanOrEqual(secondaryBounds.top + 10); }); }); // ============================================================================= // ACCESSIBILITY TESTS // ============================================================================= test.describe("Accessibility", () => { test("resizer has vertical cursor style", async ({ initTestBed, page, createSplitterDriver }) => { await initTestBed(` <VSplitter height="200px" width="400px" testId="vsplitter"> <Stack backgroundColor="lightblue" height="100%"/> <Stack backgroundColor="darksalmon" height="100%"/> </VSplitter> `); const vsplitter = page.getByTestId("vsplitter"); const driver = await createSplitterDriver(vsplitter); const resizer = await driver.getResizer(); // VSplitter should always use vertical cursor (ns-resize) await expect(resizer).toHaveCSS("cursor", "ns-resize"); }); }); ``` -------------------------------------------------------------------------------- /docs/content/components/Footer.md: -------------------------------------------------------------------------------- ```markdown # Footer [#footer] `Footer` provides a designated area at the bottom of your application for footer content such as branding, copyright notices, or utility controls like theme toggles. ## Properties [#properties] This component does not have any properties. ## 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) | | --- | --- | --- | | [backgroundColor](../styles-and-themes/common-units/#color)-Footer | $backgroundColor-AppHeader | $backgroundColor-AppHeader | | [border](../styles-and-themes/common-units/#border)-Footer | *none* | *none* | | [borderBottom](../styles-and-themes/common-units/#border)-Footer | *none* | *none* | | [borderBottomColor](../styles-and-themes/common-units/#color)-Footer | *none* | *none* | | [borderBottomStyle](../styles-and-themes/common-units/#border-style)-Footer | *none* | *none* | | [borderBottomWidth](../styles-and-themes/common-units/#size)-Footer | *none* | *none* | | [borderColor](../styles-and-themes/common-units/#color)-Footer | *none* | *none* | | [borderEndEndRadius](../styles-and-themes/common-units/#border-rounding)-Footer | *none* | *none* | | [borderEndStartRadius](../styles-and-themes/common-units/#border-rounding)-Footer | *none* | *none* | | [borderHorizontal](../styles-and-themes/common-units/#border)-Footer | *none* | *none* | | [borderHorizontalColor](../styles-and-themes/common-units/#color)-Footer | *none* | *none* | | [borderHorizontalStyle](../styles-and-themes/common-units/#border-style)-Footer | *none* | *none* | | [borderHorizontalWidth](../styles-and-themes/common-units/#size)-Footer | *none* | *none* | | [borderLeft](../styles-and-themes/common-units/#border)-Footer | *none* | *none* | | [color](../styles-and-themes/common-units/#color)-Footer | *none* | *none* | | [borderLeftStyle](../styles-and-themes/common-units/#border-style)-Footer | *none* | *none* | | [borderLeftWidth](../styles-and-themes/common-units/#size)-Footer | *none* | *none* | | [borderRight](../styles-and-themes/common-units/#border)-Footer | *none* | *none* | | [color](../styles-and-themes/common-units/#color)-Footer | *none* | *none* | | [borderRightStyle](../styles-and-themes/common-units/#border-style)-Footer | *none* | *none* | | [borderRightWidth](../styles-and-themes/common-units/#size)-Footer | *none* | *none* | | [borderStartEndRadius](../styles-and-themes/common-units/#border-rounding)-Footer | *none* | *none* | | [borderStartStartRadius](../styles-and-themes/common-units/#border-rounding)-Footer | *none* | *none* | | [borderStyle](../styles-and-themes/common-units/#border-style)-Footer | *none* | *none* | | [borderTop](../styles-and-themes/common-units/#border)-Footer | 1px solid $borderColor | 1px solid $borderColor | | [borderTopColor](../styles-and-themes/common-units/#color)-Footer | *none* | *none* | | [borderTopStyle](../styles-and-themes/common-units/#border-style)-Footer | *none* | *none* | | [borderTopWidth](../styles-and-themes/common-units/#size)-Footer | *none* | *none* | | [borderHorizontal](../styles-and-themes/common-units/#border)-Footer | *none* | *none* | | [borderVerticalColor](../styles-and-themes/common-units/#color)-Footer | *none* | *none* | | [borderVerticalStyle](../styles-and-themes/common-units/#border-style)-Footer | *none* | *none* | | [borderVerticalWidth](../styles-and-themes/common-units/#size)-Footer | *none* | *none* | | [borderWidth](../styles-and-themes/common-units/#size)-Footer | *none* | *none* | | [fontSize](../styles-and-themes/common-units/#size)-Footer | $fontSize-sm | $fontSize-sm | | [gap](../styles-and-themes/common-units/#size)-Footer | $space-normal | $space-normal | | [height](../styles-and-themes/common-units/#size)-Footer | *none* | *none* | | [margin](../styles-and-themes/common-units/#size)-Footer | 0 auto | 0 auto | | [maxWidth-content](../styles-and-themes/common-units/#size)-Footer | $maxWidth-content | $maxWidth-content | | [padding](../styles-and-themes/common-units/#size)-Footer | $space-2 $space-4 | $space-2 $space-4 | | [paddingBottom](../styles-and-themes/common-units/#size)-Footer | *none* | *none* | | [paddingHorizontal](../styles-and-themes/common-units/#size)-Footer | *none* | *none* | | [paddingLeft](../styles-and-themes/common-units/#size)-Footer | *none* | *none* | | [paddingRight](../styles-and-themes/common-units/#size)-Footer | *none* | *none* | | [paddingTop](../styles-and-themes/common-units/#size)-Footer | *none* | *none* | | [paddingVertical](../styles-and-themes/common-units/#size)-Footer | *none* | *none* | | [textColor](../styles-and-themes/common-units/#color)-Footer | $textColor-secondary | $textColor-secondary | | [verticalAlignment](../styles-and-themes/common-units/#alignment)-Footer | center | center | ``` -------------------------------------------------------------------------------- /xmlui/src/components-core/theming/parse-layout-props.ts: -------------------------------------------------------------------------------- ```typescript import type { MediaBreakpointType} from "../../abstractions/AppContextDefs"; import { MediaBreakpointKeys } from "../../abstractions/AppContextDefs"; export type ParsedLayout = { property: string; part?: string; component?: string; screenSizes?: MediaBreakpointType[]; states?: string[]; } /** * Mapping exceptions for camelCase property names to CSS property names. * These properties don't follow the standard camelCase-to-kebab-case conversion. */ export const CSS_PROPERTY_EXCEPTIONS: Record<string, string> = { textColor: "color", paddingVertical: "", paddingHorizontal: "", marginVertical: "", marginHorizontal: "", borderVertical: "", borderHorizontal: "", }; export function parseLayoutProperty(prop: string, parseComponent: boolean = false): ParsedLayout | string { if (!prop || typeof prop !== 'string') { return "Property string cannot be empty"; } // Split by '--' to separate states from the rest const parts = prop.split('--'); const mainPart = parts[0]; const stateParts = parts.slice(1); // Validate state names const states: string[] = []; for (const statePart of stateParts) { if (!statePart) { return "State name cannot be empty"; } if (!isValidName(statePart)) { return `Invalid state name: ${statePart}`; } states.push(statePart); } // Split main part by '-' to get segments const segments = mainPart.split('-').filter(segment => segment.length > 0); if (segments.length === 0) { return "CSS property name is required"; } // The first segment is always the CSS property name (camelCase, no dashes) const property = segments[0]; // Validate CSS property name (camelCase) if (!isValidPropertyName(property)) { return `Invalid CSS property name: ${property}`; } const result: ParsedLayout = { property, states: states.length > 0 ? states : undefined }; let segmentIndex = 1; const screenSizes: MediaBreakpointType[] = []; // Process remaining segments while (segmentIndex < segments.length) { const segment = segments[segmentIndex]; // Check if it's a screen size if (isMediaBreakpoint(segment)) { screenSizes.push(segment as MediaBreakpointType); segmentIndex++; continue; } // Check if it's a component name (starts with uppercase) if (isComponentName(segment)) { if (!parseComponent) { return `Component names are not allowed when parseComponent is false: ${segment}`; } if (result.component) { return "Multiple component names found"; } result.component = segment; segmentIndex++; continue; } // Check if it's a part name (starts with lowercase) if (isValidPartName(segment)) { if (result.part) { return "Multiple part names found"; } result.part = segment; segmentIndex++; continue; } // If we reach here, the segment is invalid return `Invalid segment: ${segment}`; } // Set screen sizes if any were found if (screenSizes.length > 0) { result.screenSizes = screenSizes; } return result; } /** * Transforms a camelCase property name (as used in ParsedLayout.property) * to its corresponding CSS style property name. * * Handles special cases defined in CSS_PROPERTY_EXCEPTIONS, otherwise * converts camelCase to kebab-case (e.g., "fontSize" -> "font-size"). * * @param property - The camelCase property name from ParsedLayout * @returns The CSS property name in kebab-case or the mapped exception * * @example * toCssPropertyName('fontSize') // returns 'font-size' * toCssPropertyName('textColor') // returns 'color' (exception) * toCssPropertyName('backgroundColor') // returns 'background-color' */ export function toCssPropertyName(property: string): string { // Check if there's a mapping exception if (property in CSS_PROPERTY_EXCEPTIONS) { return CSS_PROPERTY_EXCEPTIONS[property]; } // Convert camelCase to kebab-case return property.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`); } function isValidPropertyName(name: string): boolean { // CSS property names in camelCase - start with lowercase letter, can contain letters and numbers return /^[a-z][a-zA-Z0-9]*$/.test(name); } function isValidName(name: string): boolean { // Names start with a letter and can contain letters, numbers, or underscores return /^[a-zA-Z][a-zA-Z0-9_]*$/.test(name); } function isValidPartName(name: string): boolean { // Part names start with lowercase letter and can contain letters, numbers, or underscores return /^[a-z][a-zA-Z0-9_]*$/.test(name); } function isComponentName(name: string): boolean { // Component names start with uppercase letter return /^[A-Z][a-zA-Z0-9_]*$/.test(name); } function isMediaBreakpoint(value: string): boolean { return MediaBreakpointKeys.includes(value as MediaBreakpointType); } ``` -------------------------------------------------------------------------------- /xmlui/src/components/Charts/AreaChart/AreaChart.tsx: -------------------------------------------------------------------------------- ```typescript import { AreaChart, defaultProps } from "./AreaChartNative"; import { createComponentRenderer } from "../../../components-core/renderers"; import { createMetadata } from "../../metadata-helpers"; import { MemoizedItem } from "../../container-helpers"; const COMP = "AreaChart"; export const AreaChartMd = createMetadata({ status: "experimental", description: "Interactive area chart for showing data trends over time with filled areas under the curve", docFolder: "Charts/AreaChart", props: { data: { description: "This property is used to provide the component with data to display. " + "The data needs to be an array of objects.", }, dataKeys: { description: "This property specifies the keys in the data objects that should be used for rendering the chart elements. " + "E.g. 'value' or 'amount'.", valueType: "string", }, nameKey: { description: "Specifies the key in the data objects that will be used to label the different data series.", valueType: "string", }, hideX: { description: "Determines whether the X-axis should be hidden. If set to `true`, the axis will not be rendered.", valueType: "boolean", defaultValue: defaultProps.hideX, }, hideY: { description: "Determines whether the Y-axis should be hidden. If set to `true`, the axis will not be rendered.", valueType: "boolean", defaultValue: defaultProps.hideY, }, hideTickX: { description: "Determines whether the X-axis tick labels should be hidden. If set to `true`, the tick labels will not be rendered.", valueType: "boolean", defaultValue: defaultProps.hideTickX, }, hideTickY: { description: "Determines whether the Y-axis tick labels should be hidden. If set to `true`, the tick labels will not be rendered.", valueType: "boolean", defaultValue: defaultProps.hideTickY, }, hideTooltip: { description: "Determines whether the tooltip should be hidden. If set to `true`, the tooltip will not be rendered.", valueType: "boolean", defaultValue: defaultProps.hideTooltip, }, showLegend: { description: "Determines whether the legend should be shown. If set to `true`, the legend will be rendered.", valueType: "boolean", defaultValue: defaultProps.showLegend, }, stacked: { description: "Determines whether multiple areas should be stacked on top of each other. If set to `true`, areas will be stacked.", valueType: "boolean", defaultValue: defaultProps.stacked, }, curved: { description: "Determines whether the area lines should be curved (smooth) or straight. If set to `true`, lines will be curved.", valueType: "boolean", defaultValue: defaultProps.curved, }, tooltipTemplate: { description: "This property allows replacing the default template to display a tooltip.", }, }, events: { // Standard chart events - customize based on chart type }, apis: { // Chart-specific APIs if needed }, contextVars: { // Add context variables if needed }, }); // Component renderer export const areaChartComponentRenderer = createComponentRenderer( COMP, AreaChartMd, ({ extractValue, node, className, lookupSyncCallback, renderChild }: any) => { return ( <AreaChart className={className} tickFormatterX={lookupSyncCallback(node.props?.tickFormatterX)} tickFormatterY={lookupSyncCallback(node.props?.tickFormatterY)} data={extractValue(node.props?.data)} nameKey={extractValue(node.props?.nameKey)} dataKeys={extractValue(node.props?.dataKeys)} hideX={extractValue.asOptionalBoolean(node.props?.hideX)} hideY={extractValue.asOptionalBoolean(node.props?.hideY)} hideTickX={extractValue.asOptionalBoolean(node.props?.hideTickX)} hideTickY={extractValue.asOptionalBoolean(node.props?.hideTickY)} hideTooltip={extractValue.asOptionalBoolean(node.props?.hideTooltip)} showLegend={extractValue.asOptionalBoolean(node.props?.showLegend)} stacked={extractValue.asOptionalBoolean(node.props?.stacked)} curved={extractValue.asOptionalBoolean(node.props?.curved)} tooltipRenderer={ node.props.tooltipTemplate ? (tooltipData) => { return ( <MemoizedItem node={node.props.tooltipTemplate} item={tooltipData} contextVars={{ $tooltip: tooltipData, }} renderChild={renderChild} /> ); } : undefined } > {renderChild(node.children)} </AreaChart> ); }, ); ``` -------------------------------------------------------------------------------- /xmlui/src/abstractions/ThemingDefs.ts: -------------------------------------------------------------------------------- ```typescript import type { Dispatch, SetStateAction } from "react"; // This type describes one the available theme tones. export type ThemeTone = "light" | "dark"; // When rendering any part of an XMLUI app, the styles to apply are enclosed in a // theme scope. Most apps use a single theme scope that includes all nodes within // the root app node. However, any app can use multiple theme scopes. // // The scope determines how the app applies styles to the particular section. This // type defines the properties of such a theme scope. export type ThemeScope = { // Gets the id of the scope's theme activeThemeId: string; // Gets the current tone of the scope's theme activeThemeTone: ThemeTone; // The HTML element that works as the root of the theme's scope. root: HTMLElement | undefined; // Sets the root HTML element the theme is assigned to setRoot: Dispatch<SetStateAction<HTMLElement | undefined>>; // The active theme in the current scope activeTheme: ThemeDefinition; // This hash object stores the CSS theme variable names with their CSS values // definition, like "--xmlui-verticalAlignment-Text-sub": "sub"; // "--xmlui-backgroundColor": "var(--xmlui-color-surface-50)" themeStyles: Record<string, string>; // This hash object stores the theme variable names with their CSS values definition, // like "verticalAlignment-Text-sub": "sub"; // "backgroundColor": "var(--xmlui-color-surface-50)". themeVars: Record<string, string>; // This function retrieves the physical path of the provided resource. The path can // be used as a URL in HTML tags, like in the `src` attribute of `<img>`. getResourceUrl: (resourceString?: string) => string | undefined; // This function gets the value of the specified theme variable. getThemeVar: (themeVar: string) => string | undefined; }; // This type represents the object managing app themes. When an app runs, styles are // applied according to the current (active) application theme and tone. export type AppThemes = { // Sets the active theme id setActiveThemeId: (newThemeId: string) => void; // Sets the active theme tone setActiveThemeTone: (newTone: ThemeTone) => void; // Toggles the current theme tone toggleThemeTone: () => void; // Gets the id of the active theme activeThemeId: string; // Gets the tone of the active theme activeThemeTone: ThemeTone; // This array holds all theme definitions available in the app. themes: Array<ThemeDefinition>; // This array holds all resource definitions available in the app. resources: Record<string, string>; // During the build process, resources may be renamed (resource folder and file // hierarchy flattened). This map resolves the original resource URLs to their // corresponding names created during the build. resourceMap: Record<string, string>; // This array lists the ids of available themes in the app. availableThemeIds: Array<string>; // This property holds the current theme definition used in the app. activeTheme: ThemeDefinition; }; // This type describes a font definition resource. export type FontDef = | { // Specifies a name that will be used as the font face value for font properties fontFamily: string; // A fontStyle value. Accepts two values to specify a range that is supported by // a font-face, for example `fontStyle: oblique 20deg 50deg` fontStyle?: string; // A font-weight value. Accepts two values to specify a range that is supported by // a font-face, for example `font-weight: 100 900` fontWeight?: string; // This property determines how a font face is displayed based on whether and // when it is downloaded and ready to use. fontDisplay?: string; // The mime type of the font file format?: string; // Specifies references to font resources. src: string; } | string; export interface ThemeDefinitionDetails { themeVars?: Record<string, string>; resources?: Record<string, string | FontDef>; } // This type represents a theme definition object. Theme files can use this object's // JSON-serialized format to define an app theme; an app can have multiple theme files. export interface ThemeDefinition extends ThemeDefinitionDetails { // Theme id id: string; // Optional theme name name?: string; // A theme can extend existing themes. The extension means that the theme variable // values defined in the new theme override the base theme's variable values. extends?: string | Array<string>; // This property defines the tone-dependent theme variable values. When a theme // variable value is resolved, the common theme variable values are overridden with // their theme-specific values. tones?: Record<string | ThemeTone, ThemeDefinitionDetails>; } export type DefaultThemeVars = Record<string | ThemeTone, string | Record<string, string>>; ``` -------------------------------------------------------------------------------- /xmlui/src/components/IFrame/IFrame.md: -------------------------------------------------------------------------------- ```markdown %-DESC-START **Key features:** - **External content embedding**: Load web pages, documents, or media from external URLs - **Security controls**: Built-in sandbox and permission policies for safe content isolation - **HTML content support**: Display inline HTML content without external sources - **Event handling**: Track loading states with load events %-DESC-END %-PROP-START src ```xmlui-pg copy display name="Example: src" <App> <IFrame src="https://example.com" width="100%" height="300px" /> </App> ``` %-PROP-END %-PROP-START srcdoc ```xmlui-pg copy display name="Example: srcdoc" <App> <IFrame srcdoc=" <h1>Hello World</h1> <p>This is embedded HTML content with <strong>formatting</strong>.</p> " width="100%" height="200px" /> </App> ``` %-PROP-END %-PROP-START allow The `allow` property controls which browser features the embedded content can access. Common values include camera, microphone, geolocation, and fullscreen permissions. ```xmlui-pg copy display name="Example: allow" <App> <IFrame src="https://www.youtube.com/embed/dQw4w9WgXcQ" allow="camera; microphone; geolocation" width="560px" height="315px" title="Rick Astley - Never Gonna Give You Up (Official Video)" /> </App> ``` %-PROP-END %-PROP-START name ```xmlui-pg copy display name="Example: name" /name="myFrame"/ <App> <VStack gap="$space-2"> <Button label="Open 'Kraftwerk: The Model' in IFrame" onClick="window.open('https://www.youtube.com/embed/-s4zRw16tMA', 'myFrame')" /> <IFrame src="https://example.com" name="myFrame" width="100%" height="300px" /> </VStack> </App> ``` %-PROP-END %-EVENT-START load ```xmlui-pg copy display name="Example: load" <App var.loadStatus="Loading..."> <VStack gap="$space-2"> <Text value="Status: {loadStatus}" /> <IFrame src="https://example.com" onLoad="loadStatus = 'Content loaded successfully!'" width="100%" height="200px" /> </VStack> </App> ``` %-EVENT-END %-STYLE-START ### Size Control IFrame supports these theme variables for consistent sizing: - `width-IFrame` - `height-IFrame` - `borderRadius-IFrame` - `border-IFrame` ```xmlui-pg display copy name="Example: IFrame with custom styling" <App> <Theme width-IFrame="400px" height-IFrame="250px" borderRadius-IFrame="8px" border-IFrame="2px solid $primaryColor"> <IFrame src="https://example.com" /> </Theme> </App> ``` %-STYLE-END %-API-START postMessage ```xmlui-pg copy display name="Example: postMessage" /postMessage/ <App> <VStack var.messageStatus="Ready to send" gap="$space-2"> <Button label="Send Message to IFrame" onClick=" myIframe.postMessage({type: 'greeting', text: 'Hello from parent!'}, '*'); messageStatus = 'Message sent!'; " /> <Card title="Status: {messageStatus}" /> <IFrame id="myIframe" srcdoc=" <script> window.addEventListener('message', (event) => \{ console.log('Received message:', event.data); document.body.innerHTML = '<h1>Message: ' + JSON.stringify(event.data) + '</h1>'; }); </script> <h1>Waiting for message...</h1> " width="100%" height="200px" /> </VStack> </App> ``` %-API-END %-API-START getContentWindow Get access to the iframe's content window object. Returns null if the content window is not accessible. ```xmlui-pg copy display name="Example: getContentWindow" /getContentWindow/ <App> <VStack var.windowStatus="Not checked yet" gap="$space-2"> <Button label="Check Content Window" onClick=" const contentWindow = myIframe.getContentWindow(); windowStatus = contentWindow ? 'Content window is accessible' : 'Content window is not accessible'; " /> <Card title="Status: {windowStatus}" /> <IFrame id="myIframe" src="https://example.com" width="100%" height="200px" /> </VStack> </App> ``` %-API-END %-API-START getContentDocument Get access to the iframe's content document object. Returns null if the content document is not accessible. ```xmlui-pg copy display name="Example: getContentDocument" /getContentDocument/ <App> <VStack var.iFrameTitle="<not queried yet>" > <Button label="Get Document Title" onClick=" const contentDoc = myIframe.getContentDocument(); iFrameTitle = contentDoc ? contentDoc.title : 'Content document not accessible'; " /> <Card title="IFrame title: {iFrameTitle}" /> <IFrame id="myIframe" srcdoc=" <html> <head><title>My Awesome Document</title></head> <body><h1>Awesome Content</h1></body> </html>" width="100%" height="200px" /> </VStack> </App> ``` %-API-END ``` -------------------------------------------------------------------------------- /xmlui/src/components-core/interception/Backend.ts: -------------------------------------------------------------------------------- ```typescript import { isArray, isObject, mapValues } from "lodash-es"; import type { BindingTreeEvaluationContext } from "../script-runner/BindingTreeEvaluationContext"; import { delay } from "../utils/misc"; import { runEventHandlerCode } from "../utils/statementUtils"; import { dateFunctions } from "../appContext/date-functions"; import { miscellaneousUtils } from "../appContext/misc-utils"; import { getDate } from "../utils/date-utils"; import Errors from "../interception/Errors"; import type { AuthService } from "../interception/ApiInterceptor"; import type { BackendDefinition, BackendEnvironment, IDatabase, RequestParams, } from "./abstractions"; // Use this backend environment as the default export const defaultBackendEnvironment: BackendEnvironment = { getDate: (date?: string | number | Date) => (date ? new Date(date) : new Date()), }; const mapValuesDeep = (obj: any, cb: (o: any) => any): any => { if (isArray(obj)) { return obj.map((innerObj) => mapValuesDeep(innerObj, cb)); } else if (isObject(obj)) { return mapValues(obj, (val) => mapValuesDeep(val, cb)); } else { return cb(obj); } }; export class CookieService { private cookies: Record<string, string | Array<string>> = {}; public setCookie(key: string, value: string | Array<string>) { this.cookies[key] = value; } public getCookieHeader() { const cookieArrays: Array<[string, string]> = []; Object.entries(this.cookies).forEach(([key, value]) => { if (Array.isArray(value)) { value.forEach((val) => cookieArrays.push(["Set-Cookie", `${key}=${val}`])); } else { cookieArrays.push(["Set-Cookie", `${key}=${value}`]); } }); return new Headers(cookieArrays); } } export class HeaderService { private headers: Record<string, string> = {}; public setHeader(key: string, value: string) { this.headers[key] = value; } public getHeaders() { const headersArray: Array<[string, string]> = []; Object.entries(this.headers).forEach(([key, value]) => { headersArray.push([key, value]); }); return new Headers(headersArray); } } export class Backend { private readonly resolvedHelpers?: Record<string, any>; private readonly apiStateHash: Record<string, any> = {}; constructor( public readonly definition: BackendDefinition, public readonly db: IDatabase, public readonly authService: AuthService, ) { this.resolvedHelpers = mapValuesDeep(definition.helpers, (helper) => { if (typeof helper === "string") { return (...params: any[]) => this.runFn(helper, ...params); } return helper; }); if (definition.initialize) { void this.runFn(definition.initialize); } } async executeOperation( operationId: string, requestParams: RequestParams, cookieService: CookieService, headerService: HeaderService, ) { const handler = this.definition.operations?.[operationId]; if (!handler) { throw new Error(`Unknown backend operation: ${operationId}`); } return await this.runFn(handler, requestParams, cookieService, headerService); } private async runFn(src: string, ...args: any[]) { let localContext = { ...this.resolvedHelpers, $db: this.db, $state: this.apiStateHash, $authService: this.authService, $env: defaultBackendEnvironment, $loggedInUser: this.authService.getLoggedInUser(), $pathParams: args[0]?.pathParams, $queryParams: args[0]?.queryParams, $requestBody: args[0]?.requestBody, $cookies: args[0]?.cookies, $requestHeaders: args[0]?.requestHeaders, $cookieService: args[1], //TODO really ugly, temporary solution $headerService: args[2], //TODO really ugly, temporary solution }; const evalContext = createEvalContext({ localContext: localContext, eventArgs: args, appContext: { ...dateFunctions, ...miscellaneousUtils, delay, Errors, createFile: (...args: any[]) => { return new File(args[0], args[1], args[2]); }, appendBlob: (blob1: Blob | undefined | null, blob2: Blob) => { if (blob1 && blob2) { return new Blob([blob1, blob2], { type: blob1.type || blob2.type }); } if (blob1) { return blob1; } if (blob2) { return blob2; } return null; }, getDate, }, }); await runEventHandlerCode(src, evalContext); return evalContext.mainThread?.blocks?.length ? evalContext.mainThread.blocks[evalContext.mainThread.blocks.length - 1].returnValue : undefined; } } function createEvalContext( parts: Partial<BindingTreeEvaluationContext>, ): BindingTreeEvaluationContext { return { ...{ mainThread: { childThreads: [], blocks: [{ vars: {} }], loops: [], breakLabelValue: -1, }, }, ...parts, }; } ``` -------------------------------------------------------------------------------- /xmlui/src/components/TextArea/TextArea.md: -------------------------------------------------------------------------------- ```markdown %-DESC-START It is often used in forms, see [this guide](/forms) for details. %-DESC-END %-PROP-START autoSize > **Note**: If either `autoSize`, `maxRows` or `minRows` is set, the `rows` prop has no effect. Write multiple lines in the `TextArea` in the demo below to see how it resizes automatically. ```xmlui-pg copy display name="Example: autoSize" height="240px" <App> <TextArea autoSize="true" /> </App> ``` %-PROP-END %-PROP-START enabled ```xmlui-pg copy display name="Example: enabled" <App> <TextArea enabled="false" /> </App> ``` %-PROP-END %-PROP-START enterSubmits Press `Enter` after writing something in the `TextArea` in the demo below. See [Using Forms](/forms) for details. ```xmlui-pg copy display name="Example: enterSubmits" <App> <Form onSubmit="toast.success(JSON.stringify(address.value))"> <TextArea id="address" enterSubmits="true" initialValue="Suzy Queue, 4455 Landing Lange, APT 4, Louisville, KY 40018-1234" /> </Form> </App> ``` %-PROP-END %-PROP-START initialValue The initial value displayed in the input field. ```xmlui-pg copy display name="Example: initialValue" <App> <TextArea initialValue="Example text" /> </App> ``` %-PROP-END %-PROP-START maxRows > **Note**: If either `autoSize`, `maxRows` or `minRows` is set, the `rows` prop has no effect. ```xmlui-pg copy {3} display name="Example: maxRows" height="160px" <App> <TextArea maxRows="3" initialValue="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." /> </App> ``` %-PROP-END %-PROP-START minRows > **Note**: If either `autoSize`, `maxRows` or `minRows` is set, the `rows` prop has no effect. ```xmlui-pg copy display name="Example: minRows" height="200px" <App> <TextArea minRows="3" initialValue="Lorem ipsum dolor sit amet..." /> </App> ``` %-PROP-END %-PROP-START placeholder ```xmlui-pg copy display name="Example: placeholder" <App> <TextArea placeholder="This is a placeholder" /> </App> ``` %-PROP-END %-PROP-START readOnly ```xmlui-pg copy display name="Example: readOnly" <App> <TextArea initialValue="Example text" readOnly="{true}" /> </App> ``` %-PROP-END %-PROP-START resize If you allow resizing, the `TextArea` turns off automatic sizing. When you allow vertical resizing, you can limit the sizable range according to `minRows` and `maxRows`. Drag the small resize indicators at the bottom right on each of the controls in the demo. ```xmlui-pg copy display name="Example: resize" height="300px" <App> <TextArea resize="vertical" minRows="1" maxRows="8" /> <TextArea resize="both" /> </App> ``` %-PROP-END %-PROP-START rows > **Note**: If either `autoSize`, `maxRows` or `minRows` is set, the `rows` prop has no effect. ```xmlui-pg copy display name="Example: rows" <App> <TextArea rows="10" /> </App> ``` %-PROP-END %-PROP-START validationStatus This prop is used to visually indicate status changes reacting to form field validation. ```xmlui-pg copy display name="Example: validationStatus" <App> <TextArea /> <TextArea validationStatus="valid" /> <TextArea validationStatus="warning" /> <TextArea validationStatus="error" /> </App> ``` %-PROP-END %-EVENT-START didChange Write in the input field and see how the `Text` underneath it is updated in parallel. ```xmlui-pg copy display name="Example: didChange" <App var.field=""> <TextArea initialValue="{field}" onDidChange="(val) => field = val" /> <Text value="{field}" /> </App> ``` %-EVENT-END %-EVENT-START gotFocus Clicking on the `TextArea` in the example demo changes the label text. Note how clicking elsewhere resets the text to the original. ```xmlui-pg copy display name="Example: gotFocus/lostFocus" <App> <TextArea initialValue="{focused === true ? 'I got focused!' : 'I lost focus...'}" onGotFocus="focused = true" onLostFocus="focused = false" var.focused="{false}" /> </App> ``` %-EVENT-END %-API-START focus ```xmlui-pg copy display name="Example: focus" <App> <Button label="Trigger Focus" onClick="inputComponent.focus()" /> <TextArea id="inputComponent" /> </App> ``` %-API-END %-API-START value In the example below, typing in the `TextArea` will also display the length of the text typed into it above the field: ```xmlui-pg copy display name="Example: value" <App> <Text value="TextArea content length: {inputComponent.value.length}" /> <TextArea id="inputComponent" /> </App> ``` %-API-END %-API-START setValue ```xmlui-pg copy display name="Example: setValue" <App var.changes=""> <TextArea id="inputField" readOnly="true" onDidChange="changes++" /> <HStack> <Button label="Check" onClick="inputField.setValue('example ')" /> <Text value="Number of changes: {changes}" /> </HStack> </App> ``` %-API-END ``` -------------------------------------------------------------------------------- /xmlui/scripts/generate-docs/generate-summary-files.mjs: -------------------------------------------------------------------------------- ``` import * as fs from "fs"; import * as path from "path"; import { FOLDERS } from "./folders.mjs"; import { createTable, getSectionBeforeAndAfter } from "./utils.mjs"; import { logger, LOGGER_LEVELS, processError } from "./logger.mjs"; import { COMPONENT_STATUS_CONFIG, SUMMARY_FILE_CONFIG, OUTPUT_FILES } from "./constants.mjs"; const ACCEPTED_STATUSES = COMPONENT_STATUS_CONFIG.ACCEPTED_STATUSES; const DEFAULT_STATUS = COMPONENT_STATUS_CONFIG.DEFAULT_STATUS; logger.setLevels(LOGGER_LEVELS.warning, LOGGER_LEVELS.error); logger.info("Generating summary files..."); // components logger.info("Components summary"); const componentsMetaFolder = path.join(FOLDERS.script, "metadata", "components"); const componentsOutFolder = path.join(FOLDERS.docsRoot, "content", "components"); generateComponentsSummary( path.join(componentsMetaFolder, OUTPUT_FILES.METADATA_JSON), "", componentsOutFolder, "_overview.md", SUMMARY_FILE_CONFIG.COMPONENTS_OVERVIEW_HEADER, ); logger.info("Components summary DONE"); // NOTE: Unused // extensions // logger.info("Extensions summary"); // const extensionsMetaFolder = path.join(FOLDERS.script, "metadata", "extensions"); // fs.readdirSync(extensionsMetaFolder).forEach((file) => { // if (path.extname(file) === ".json") { // let extensionName = path.basename(file, ".json"); // extensionName = extensionName.replace("-metadata", ""); // const extensionOutFolder = path.join(FOLDERS.docsRoot, "content", "extensions", extensionName); // generateComponentsSummary( // path.join(extensionsMetaFolder, file), // `extensions/${extensionName}`, // extensionOutFolder, // `_overview.md`, // SUMMARY_FILE_CONFIG.PACKAGE_COMPONENTS_HEADER, // ); // } // }); // logger.info("Extensions summary DONE"); // --- /** * Generates the component summary table and adds it to the provided file. * The function looks for the summary section in the provided file and regenerates it if present, * otherwise it appends one to the end of the file. * @param {string} metadataFile The path & name of the file containing the component metadata * @param {string} urlPath the path that is used to point to the generated component article in the docs * @param {string} outFolder The folder to add the summary to * @param {string} summaryFile The name of the file (with extesion) to add the summary to * @param {string} summarySectionName The section to look for and add the summary to * @param {boolean?} hasRowNums Whether to add row numbers to the summary table */ function generateComponentsSummary( metadataFile, urlPath, outFolder, summaryFile, summarySectionName, hasRowNums, ) { try { if (!fs.existsSync(metadataFile)) { throw new Error( `Metadata file does not exist: ${metadataFile}. Please run generate-docs first.`, ); } const outFile = path.join(outFolder, summaryFile ?? `_overview.md`); if (!fs.existsSync(outFile)) { fs.writeFileSync(outFile, ""); } const metadata = JSON.parse(fs.readFileSync(metadataFile, "utf8")); const table = createSummaryTable(metadata, urlPath, outFolder, hasRowNums); const fileContents = fs.readFileSync(outFile, "utf8"); let { beforeSection, afterSection } = getSectionBeforeAndAfter( fileContents, summarySectionName, ); beforeSection = beforeSection.length > 0 ? beforeSection + "\n\n" : beforeSection; const summary = beforeSection + `${summarySectionName}\n\n` + table + "\n" + afterSection; fs.writeFileSync(outFile, summary); } catch (error) { processError(error); } } /** * Translates a metadata object to a summary table in markdown. * @param {Record<string, any>} metadata object with components and their metadata * @param {string} urlPath the path that is used to point to the generated component article in the docs * @param {string} filePath the path that contains the generated component article file itself * @param {boolean?} hasRowNums should the table have numbered rows * @returns stringified markdown table */ function createSummaryTable(metadata, urlPath, filePath, hasRowNums = false) { const headers = [{ value: "Component", style: "center" }, "Description"]; const rows = metadata .sort((a, b) => a.displayName.localeCompare(b.displayName)) .filter((component) => { const componentStatus = component.status ?? DEFAULT_STATUS; return !ACCEPTED_STATUSES.includes(componentStatus) ? false : true; }) .map((component) => { const componentFilePath = path.join(filePath, `${component.displayName}.md`); const componentUrl = urlPath ? `./${urlPath}/${component.displayName}` : `./${component.displayName}`; return [ fs.existsSync(componentFilePath) ? `[${component.displayName}](${componentUrl})` : component.displayName, component.description, ]; }); return createTable({ headers, rows, rowNums: hasRowNums }); } ``` -------------------------------------------------------------------------------- /xmlui/src/components/Tooltip/Tooltip.tsx: -------------------------------------------------------------------------------- ```typescript import { createComponentRenderer } from "../../components-core/renderers"; import { createMetadata } from "../metadata-helpers"; import { parseScssVar } from "../../components-core/theming/themeVars"; import { Tooltip } from "./TooltipNative"; import type { TooltipProps } from "./TooltipNative"; import styles from "./Tooltip.module.scss"; const COMP = "Tooltip"; export const TooltipMd = createMetadata({ status: "stable", description: "A tooltip component that displays text when hovering over trigger content.", props: { text: { description: "The text content to display in the tooltip", type: "string", isRequired: false, }, markdown: { description: "The markdown content to display in the tooltip", type: "string", isRequired: false, }, tooltipTemplate: { description: "The template for the tooltip content", type: "Component", isRequired: false, }, delayDuration: { description: "The duration from when the mouse enters a tooltip trigger until the tooltip opens (in ms)", type: "number", defaultValue: 700, }, skipDelayDuration: { description: "How much time a user has to enter another trigger without incurring a delay again (in ms)", type: "number", defaultValue: 300, }, defaultOpen: { description: "The open state of the tooltip when it is initially rendered", type: "boolean", defaultValue: false, }, showArrow: { description: "Whether to show the arrow pointing to the trigger element", type: "boolean", defaultValue: false, }, side: { description: "The preferred side of the trigger to render against when open", type: "string", availableValues: ["top", "right", "bottom", "left"], defaultValue: "top", }, align: { description: "The preferred alignment against the trigger", type: "string", availableValues: ["start", "center", "end"], defaultValue: "center", }, sideOffset: { description: "The distance in pixels from the trigger", type: "number", defaultValue: 4, }, alignOffset: { description: "An offset in pixels from the 'start' or 'end' alignment options", type: "number", defaultValue: 0, }, avoidCollisions: { description: "When true, overrides the side and align preferences to prevent collisions with boundary edges", type: "boolean", defaultValue: true, }, }, events: {}, apis: {}, contextVars: {}, themeVars: parseScssVar(styles.themeVars), limitThemeVarsToComponent: true, defaultThemeVars: { [`backgroundColor-${COMP}`]: "$color-surface-0", [`border-${COMP}`]: "none", [`textColor-${COMP}`]: "$textcolor-primary", [`borderRadius-${COMP}`]: "4px", [`fontSize-${COMP}`]: "15px", [`lineHeight-${COMP}`]: "1", [`paddingTop-${COMP}`]: "10px", [`paddingBottom-${COMP}`]: "10px", [`paddingLeft-${COMP}`]: "15px", [`paddingRight-${COMP}`]: "15px", [`boxShadow-${COMP}`]: "hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px", [`fill-arrow-${COMP}`]: "$color-surface-200", [`stroke-arrow-${COMP}`]: "$color-surface-200", [`strokeWidth-arrow-${COMP}`]: "0", [`animationDuration-${COMP}`]: "400ms", [`animation-${COMP}`]: "cubic-bezier(0.16, 1, 0.3, 1)", dark: { [`backgroundColor-${COMP}`]: "$color-surface-200", }, }, }); export const tooltipComponentRenderer = createComponentRenderer( "Tooltip", TooltipMd, ({ node, extractValue, renderChild, layoutContext }) => { // If there are no children, do not render anything if (!node.children || node.children.length === 0) { return null; } // If text is not provided, do not render anything const text = extractValue.asOptionalString(node.props.text); const markdown = extractValue.asOptionalString(node.props.markdown); const tooltipTemplate = node.props.tooltipTemplate; return ( <Tooltip text={text} markdown={markdown} tooltipTemplate={renderChild(tooltipTemplate)} delayDuration={extractValue.asOptionalNumber(node.props.delayDuration)} skipDelayDuration={extractValue.asOptionalNumber(node.props.skipDelayDuration)} defaultOpen={extractValue.asOptionalBoolean(node.props.defaultOpen)} showArrow={extractValue.asOptionalBoolean(node.props.showArrow)} side={extractValue.asOptionalString(node.props.side) as TooltipProps["side"]} align={extractValue.asOptionalString(node.props.align) as TooltipProps["align"]} sideOffset={extractValue.asOptionalNumber(node.props.sideOffset)} alignOffset={extractValue.asOptionalNumber(node.props.alignOffset)} avoidCollisions={extractValue.asOptionalBoolean(node.props.avoidCollisions)} > {renderChild(node.children, layoutContext)} </Tooltip> ); }, ); ```