This is page 145 of 182. Use http://codebase.md/xmlui-org/xmlui/cantFindIt.jpg?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .changeset │ ├── config.json │ ├── cool-queens-look.md │ └── twelve-guests-care.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 │ ├── ud-components.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/dev-docs/ud-components.md: -------------------------------------------------------------------------------- ```markdown 1 | # User-Defined Component Architecture 2 | 3 | This document explains XMLUI's user-defined component infrastructure - the rendering mechanisms that enable developers to create reusable components in `.xmlui` files with template composition through slots and children transposition. It covers the architectural foundations, the slot transposition mechanism, the role of ComponentAdapter and supporting components, and implementation details for framework developers working on the XMLUI core. 4 | 5 | ## What Are User-Defined Components? 6 | 7 | **User-defined components** are reusable component definitions created by application developers using XMLUI markup in `.xmlui` files. They encapsulate visual structure, behavior, and state management patterns into named components that can be used throughout an application just like framework-provided components. 8 | 9 | A user-defined component is declared with the `<Component>` tag and must have a unique name starting with an uppercase letter. The component name must match the filename (e.g., `ActionBar.xmlui` contains `<Component name="ActionBar">`). Components support properties, events, methods, variables, and slots for template composition. 10 | 11 | **Key Characteristics:** 12 | 13 | - **Declarative Definition** - Components defined entirely in XMLUI markup without requiring JavaScript/TypeScript code 14 | - **Property Passing** - Parent components pass data through properties accessed via `$props` context variable 15 | - **Event Emission** - Components notify parents of state changes through custom events using `emitEvent()` 16 | - **Template Composition** - Slots enable parent components to inject custom markup into predefined component regions 17 | - **Encapsulation** - Components maintain their own state, methods, and internal structure 18 | - **Reusability** - Once defined, components can be used throughout the application like any framework component 19 | 20 | ## Architectural Overview 21 | 22 | ### Component Rendering Flow 23 | 24 | User-defined components follow a distinct rendering path compared to framework-provided components. The key architectural difference is that user-defined components are **compound components** - they have both a component definition (what the parent sees) and an internal implementation (what renders). 25 | 26 | **High-Level Flow:** 27 | 28 | ```mermaid 29 | %%{init: {"block": {"padding": 8}}}%% 30 | block-beta 31 | columns 3 32 | 33 | A["Parent Component Uses <MyComponent>"] 34 | B["ComponentAdapter receives<br/>component definition"] 35 | C["ComponentRegistry identifies<br/>as compound component"] 36 | 37 | D["CompoundComponent wraps<br/>internal implementation"] 38 | E["Container provides scope<br/>for $props, vars, methods"] 39 | F["Slot components transpose<br/>parent content"] 40 | 41 | block:Final:3 42 | G["SlotItem renders content<br/>with context variables"] 43 | end 44 | ``` 45 | 46 | This architecture enables template composition - parent components provide markup fragments that render within the compound component's layout, with access to data pushed from the compound component through slot properties. 47 | 48 | ### The Compound Component Pattern 49 | 50 | User-defined components are implemented as **compound components** - they consist of two interconnected parts: 51 | 52 | 1. **Component Interface** - The properties, events, and children the parent component provides 53 | 2. **Component Implementation** - The internal markup, state, and logic defined in the `.xmlui` file 54 | 55 | **Example Component Definition:** 56 | 57 | ```xmlui 58 | <Component name="ActionBar"> 59 | <variable name="isExpanded" value="{false}" /> 60 | 61 | <Card> 62 | <HStack> 63 | <H3>{$props.header}</H3> 64 | <Button 65 | label="{isExpanded ? 'Collapse' : 'Expand'}" 66 | onClick="isExpanded = !isExpanded" /> 67 | </HStack> 68 | <If condition="{isExpanded}"> 69 | <VStack> 70 | <Slot /> 71 | </VStack> 72 | </If> 73 | </Card> 74 | </Component> 75 | ``` 76 | 77 | **Parent Usage:** 78 | 79 | ```xmlui 80 | <ActionBar header="Actions Menu"> 81 | <Button label="Create" onClick="handleCreate()" /> 82 | <Button label="Edit" onClick="handleEdit()" /> 83 | <Button label="Delete" onClick="handleDelete()" /> 84 | </ActionBar> 85 | ``` 86 | 87 | The component interface receives `header` property and button children from the parent. The component implementation defines internal state (`isExpanded`), accesses the property via `$props.header`, and uses `<Slot />` to transpose the children into its layout. 88 | 89 | ### Slot-Based Template Composition 90 | 91 | Slots are placeholder components within user-defined components that mark where parent-provided content should render. XMLUI supports two types of slots: 92 | 93 | **Default (Unnamed) Slots** receive all children passed to the component: 94 | 95 | ```xmlui 96 | <Component name="Panel"> 97 | <Card> 98 | <Slot /> 99 | </Card> 100 | </Component> 101 | 102 | <!-- Usage --> 103 | <Panel> 104 | <Text>This content goes into the default slot</Text> 105 | </Panel> 106 | ``` 107 | 108 | **Named Slots** receive specific template fragments from the parent using the `<property>` syntax: 109 | 110 | ```xmlui 111 | <Component name="Dialog"> 112 | <Card> 113 | <Slot name="headerTemplate"> 114 | <H3>Default Header</H3> 115 | </Slot> 116 | <Slot /> 117 | <Slot name="actionsTemplate"> 118 | <Button label="OK" /> 119 | </Slot> 120 | </Card> 121 | </Component> 122 | 123 | <!-- Usage --> 124 | <Dialog> 125 | <property name="headerTemplate"> 126 | <H2>Custom Header</H2> 127 | </property> 128 | <Text>Main dialog content</Text> 129 | <property name="actionsTemplate"> 130 | <Button label="Cancel" /> 131 | <Button label="Confirm" /> 132 | </property> 133 | </Dialog> 134 | ``` 135 | 136 | Named slots must end with `"Template"` suffix - this is a framework requirement enforced in `slotRenderer()` and `renderChild()`. 137 | 138 | ### Slot Properties: Data Flow from Component to Parent 139 | 140 | Slots enable bidirectional data flow - not only can parents inject content into components, but components can push data back to the parent's slot content through **slot properties**. 141 | 142 | **How Slot Properties Work:** 143 | 144 | 1. The compound component declares properties on the `<Slot>` element 145 | 2. These properties are evaluated in the component's scope with access to component state 146 | 3. The properties are transformed into context variables prefixed with `$` in the slot content 147 | 4. The parent's slot content can access these context variables 148 | 149 | **Example - List Component Passing Item Data:** 150 | 151 | ```xmlui 152 | <Component name="ItemList"> 153 | <variable name="items" value="{['Apple', 'Banana', 'Cherry']}" /> 154 | 155 | <VStack> 156 | <For items="{items}"> 157 | <Slot 158 | name="itemTemplate" 159 | item="{$item}" 160 | index="{$index}" 161 | isLast="{$index === items.length - 1}"> 162 | <!-- Default template if parent doesn't provide one --> 163 | <Text>{$item}</Text> 164 | </Slot> 165 | </For> 166 | </VStack> 167 | </Component> 168 | 169 | <!-- Parent Usage --> 170 | <ItemList> 171 | <property name="itemTemplate"> 172 | <Card padding="sm"> 173 | <Text variant="strong">#{$index + 1}: {$item}</Text> 174 | <If condition="{!$isLast}"> 175 | <Divider /> 176 | </If> 177 | </Card> 178 | </property> 179 | </ItemList> 180 | ``` 181 | 182 | In this example: 183 | - The `ItemList` component iterates over `items` array 184 | - For each item, it renders a `<Slot>` with properties: `item`, `index`, and `isLast` 185 | - These properties become context variables `$item`, `$index`, and `$isLast` in the parent's template 186 | - The parent's `itemTemplate` can use these variables to render custom markup for each item 187 | 188 | **Scope Behavior:** 189 | 190 | Slot properties create a unique scoping pattern: 191 | - Slot properties are evaluated in the **component's scope** (with access to component variables and state) 192 | - The extracted values are passed as context variables to the slot content 193 | - The slot content renders in the **parent's scope** (with access to parent variables and IDs) 194 | - Context variables from slot properties are available only within that specific slot content 195 | 196 | This pattern enables powerful composition scenarios like data tables with custom cell renderers, lists with custom item templates, and dialogs with custom header/footer templates. 197 | 198 | ## Implementation Details 199 | 200 | ### ComponentAdapter: The Routing Layer 201 | 202 | `ComponentAdapter` is the central component in XMLUI's rendering pipeline that transforms component definitions into React elements. For user-defined components, ComponentAdapter handles special routing and slot transposition. 203 | 204 | **Key Responsibilities:** 205 | 206 | 1. **Component Registry Lookup** - Queries `ComponentRegistry` to determine if a component is compound (user-defined) vs primitive (framework) 207 | 2. **Slot Detection and Routing** - Detects `type === "Slot"` and routes to `slotRenderer()` function 208 | 3. **Renderer Context Assembly** - Prepares full renderer context with state, value extraction, event handlers, and layout information 209 | 4. **Behavior Application** - Applies component behaviors (tooltip, animation, label) but skips them for compound components 210 | 5. **Test Decoration** - Adds test IDs and inspection attributes for development tooling 211 | 212 | **Compound Component Detection:** 213 | 214 | ```typescript 215 | const { renderer, descriptor, isCompoundComponent } = 216 | componentRegistry.lookupComponentRenderer(safeNode.type) || {}; 217 | ``` 218 | 219 | The `lookupComponentRenderer()` method returns: 220 | - `renderer` - The rendering function (for compound components, this is `CompoundComponentHolder`) 221 | - `descriptor` - Metadata about the component (props, events, visual/nonvisual, etc.) 222 | - `isCompoundComponent` - Boolean flag indicating whether this is a user-defined component 223 | 224 | **Critical Behavioral Difference:** 225 | 226 | ComponentAdapter skips behavior application for compound components: 227 | 228 | ```typescript 229 | const behaviors = componentRegistry.getBehaviors(); 230 | if (!isCompoundComponent) { 231 | for (const behavior of behaviors) { 232 | if (behavior.canAttach(rendererContext.node, descriptor)) { 233 | renderedNode = behavior.attach(rendererContext, renderedNode, descriptor); 234 | } 235 | } 236 | } 237 | ``` 238 | 239 | This prevents behaviors (tooltip, animation, label) from wrapping the compound component's internal structure. Behaviors should only attach to the outermost visual element, which the compound component's internal implementation controls. 240 | 241 | ### slotRenderer(): The Transposition Function 242 | 243 | The `slotRenderer()` function is a specialized renderer within ComponentAdapter that handles all Slot component rendering logic. It implements the core transposition mechanism - taking content from the parent component and rendering it in the compound component's location. 244 | 245 | **Function Signature:** 246 | 247 | ```typescript 248 | function slotRenderer( 249 | { node, extractValue, renderChild, lookupAction, layoutContext }: RendererContext<any>, 250 | parentRenderContext?: ParentRenderContext, 251 | ): ReactNode 252 | ``` 253 | 254 | **Parameters:** 255 | 256 | - `node` - The Slot component definition containing the slot name and properties 257 | - `extractValue` - Function to evaluate property expressions (for slot properties) 258 | - `renderChild` - Function to render child components (for default slot content) 259 | - `lookupAction` - Function to resolve action expressions (for arrow function slot props) 260 | - `layoutContext` - Current layout context (horizontal/vertical stack, etc.) 261 | - `parentRenderContext` - Context from the parent component containing children and templates 262 | 263 | **Processing Logic:** 264 | 265 | ```typescript 266 | // 1. Extract slot name (if named slot) 267 | const templateName = extractValue.asOptionalString(node.props.name); 268 | 269 | // 2. Validate slot name ends with "Template" 270 | if (templateName && !templateName.endsWith("Template")) { 271 | return <InvalidComponent errors={[...]} />; 272 | } 273 | 274 | // 3. Extract slot properties (all props except "name") 275 | let slotProps: any = null; 276 | if (!isEmpty(node.props)) { 277 | slotProps = {}; 278 | Object.keys(node.props).forEach((key) => { 279 | if (key !== "name") { 280 | let extractedValue = extractValue(node.props[key], true); 281 | // Handle arrow function properties 282 | if (extractedValue?._ARROW_EXPR_) { 283 | extractedValue = lookupAction(extractedValue); 284 | } 285 | slotProps[key] = extractedValue; 286 | } 287 | }); 288 | } 289 | 290 | // 4. Determine content source based on slot type and parent context 291 | if (parentRenderContext) { 292 | if (templateName === undefined) { 293 | // Default slot - use parent's children 294 | if (!slotProps) { 295 | return parentRenderContext.renderChild(parentRenderContext.children, layoutContext); 296 | } else { 297 | return <SlotItem 298 | node={parentRenderContext.children} 299 | renderChild={parentRenderContext.renderChild} 300 | slotProps={slotProps} 301 | layoutContext={layoutContext} 302 | />; 303 | } 304 | } else { 305 | // Named slot - use parent's template property 306 | if (parentRenderContext.props[templateName]) { 307 | return <SlotItem 308 | node={parentRenderContext.props[templateName]} 309 | renderChild={parentRenderContext.renderChild} 310 | slotProps={slotProps} 311 | layoutContext={layoutContext} 312 | />; 313 | } 314 | } 315 | } 316 | 317 | // 5. No parent content - render default slot children 318 | if (node.children && node.children.length > 0) { 319 | return <SlotItem 320 | node={node.children} 321 | renderChild={renderChild} 322 | slotProps={slotProps ?? EMPTY_OBJECT} 323 | layoutContext={layoutContext} 324 | />; 325 | } 326 | 327 | return undefined; 328 | ``` 329 | 330 | **Decision Tree:** 331 | 332 | The function follows this logic to determine what to render: 333 | 334 | 1. **Named Slot with Parent Template** → Render parent's template property with slot props 335 | 2. **Named Slot without Parent Template** → Render default slot children with slot props 336 | 3. **Default Slot with Parent Children** → Render parent's children (with or without slot props) 337 | 4. **Default Slot without Parent Children** → Render default slot children 338 | 5. **No Content Available** → Return undefined (renders nothing) 339 | 340 | **Slot Properties Handling:** 341 | 342 | All properties on the Slot element (except `name`) become slot properties: 343 | 344 | ```xmlui 345 | <Slot name="itemTemplate" item="{$item}" index="{$index}" color="red" /> 346 | ``` 347 | 348 | Results in `slotProps`: 349 | 350 | ```javascript 351 | { 352 | item: extractValue($item), // Evaluates to current item value 353 | index: extractValue($index), // Evaluates to current index value 354 | color: "red" // Static string value 355 | } 356 | ``` 357 | 358 | These props are passed to `SlotItem` which transforms them into context variables (`$item`, `$index`, `$color`) available in the slot content. 359 | 360 | **Arrow Function Properties:** 361 | 362 | Slot properties can be arrow functions that execute in the component's scope: 363 | 364 | ```xmlui 365 | <Slot 366 | name="itemTemplate" 367 | item="{$item}" 368 | displayName="() => $item.toUpperCase()" /> 369 | ``` 370 | 371 | The `slotRenderer` detects arrow function expressions via the `_ARROW_EXPR_` marker and resolves them using `lookupAction()` before passing to `SlotItem`. 372 | 373 | ### SlotItem: The Context Variable Wrapper 374 | 375 | `SlotItem` is a React component that wraps slot content in a Container with context variables derived from slot properties. It implements the transformation that makes slot properties accessible as context variables in the parent's template markup. 376 | 377 | **Component Signature:** 378 | 379 | ```typescript 380 | type SlotItemProps = { 381 | node: ComponentDef | Array<ComponentDef>; 382 | slotProps?: any; 383 | renderChild: RenderChildFn; 384 | layoutContext?: LayoutContext; 385 | }; 386 | 387 | export const SlotItem = memo(({ node, renderChild, layoutContext, slotProps = EMPTY_OBJECT }: SlotItemProps) => { 388 | // ... implementation 389 | }); 390 | ``` 391 | 392 | **Transformation Process:** 393 | 394 | ```typescript 395 | // 1. Memoize slot props to prevent unnecessary re-renders 396 | const shallowMemoedSlotProps = useShallowCompareMemoize(slotProps); 397 | 398 | // 2. Transform slot properties into context variables 399 | const nodeWithItem = useMemo(() => { 400 | const templateProps = {}; 401 | Object.entries(shallowMemoedSlotProps).forEach(([key, value]) => { 402 | templateProps["$" + key] = value; // Prefix with $ 403 | }); 404 | 405 | // 3. Create Container with context variables 406 | return { 407 | type: "Container", 408 | contextVars: templateProps, 409 | children: Array.isArray(node) ? node : [node], 410 | } as ContainerWrapperDef; 411 | }, [node, shallowMemoedSlotProps]); 412 | 413 | // 4. Render containerized slot content 414 | return <>{renderChild(nodeWithItem, layoutContext)}</>; 415 | ``` 416 | 417 | **Key Operations:** 418 | 419 | 1. **Property Prefixing** - Each slot prop key is prefixed with `$` to create a context variable 420 | - `item` → `$item` 421 | - `index` → `$index` 422 | - `isSelected` → `$isSelected` 423 | 424 | 2. **Container Creation** - Wraps slot content in a Container component definition with `contextVars` 425 | - The Container creates a new state scope 426 | - Context variables are available to all components within the slot content 427 | - State isolation prevents slot content from interfering with component or parent state 428 | 429 | 3. **Rendering Delegation** - Calls `renderChild()` with the containerized node 430 | - Routes through ComponentWrapper → ContainerWrapper → ComponentAdapter 431 | - ContainerWrapper provides the context variable scope 432 | - Slot content components can access `$item`, `$index`, etc. through the value extractor 433 | 434 | **Memoization Strategy:** 435 | 436 | SlotItem uses two levels of memoization to optimize performance: 437 | 438 | - `React.memo` - Prevents re-rendering when props haven't changed 439 | - `useShallowCompareMemoize` - Creates stable references for slot props using shallow equality 440 | - `useMemo` - Recomputes containerized node only when slot props or content changes 441 | 442 | This prevents unnecessary re-renders when compound components re-render but slot properties haven't changed. 443 | 444 | **Example Data Flow:** 445 | 446 | ```xmlui 447 | <!-- Compound Component --> 448 | <Slot name="itemTemplate" item="{currentItem}" index="{i}" /> 449 | 450 | <!-- SlotItem receives --> 451 | { 452 | node: [/* parent's template definition */], 453 | slotProps: { item: "Apple", index: 0 }, 454 | renderChild: parentRenderChild, 455 | layoutContext: { ... } 456 | } 457 | 458 | <!-- SlotItem transforms to --> 459 | { 460 | type: "Container", 461 | contextVars: { $item: "Apple", $index: 0 }, 462 | children: [/* parent's template definition */] 463 | } 464 | 465 | <!-- Parent template can access --> 466 | <Text>{$item}</Text> <!-- Renders "Apple" --> 467 | <Text>#{$index}</Text> <!-- Renders "#0" --> 468 | ``` 469 | 470 | ### CompoundComponent: The Implementation Bridge 471 | 472 | `CompoundComponent` is the React component that bridges user-defined component definitions with their internal implementations. It manages property resolution, event emission, state updates, and parent render context assembly for slot transposition. 473 | 474 | **Component Signature:** 475 | 476 | ```typescript 477 | type CompoundComponentProps = { 478 | compound: ComponentDef; // Internal component implementation 479 | api?: Record<string, string>; // Component API method mappings 480 | scriptCollected?: CollectedDeclarations; // Collected script declarations 481 | } & RendererContext; // Full renderer context 482 | ``` 483 | 484 | **Responsibilities:** 485 | 486 | 1. **Property Resolution** - Extracts and evaluates all properties passed to the component 487 | 2. **Container Assembly** - Wraps internal implementation in Container with vars, loaders, methods 488 | 3. **Event Emission** - Provides `emitEvent()` function for component-to-parent communication 489 | 4. **Parent Context Creation** - Assembles parent render context for slot transposition 490 | 5. **Scope Isolation** - Ensures component implementation has access to `$props`, vars, and methods 491 | 492 | **Property Resolution:** 493 | 494 | ```typescript 495 | const resolvedPropsInner = useMemo(() => { 496 | const resolvedProps: any = {}; 497 | if (node.props) { 498 | Object.entries(node.props).forEach(([key, value]) => { 499 | const extractedProp = extractValue(value, true); 500 | if (extractedProp?._ARROW_EXPR_) { 501 | // Arrow functions are called synchronously 502 | resolvedProps[key] = lookupSyncCallback(extractedProp); 503 | } else { 504 | resolvedProps[key] = extractedProp; 505 | } 506 | }); 507 | } 508 | return resolvedProps; 509 | }, [extractValue, lookupSyncCallback, node.props]); 510 | 511 | const resolvedProps = useShallowCompareMemoize(resolvedPropsInner); 512 | ``` 513 | 514 | Properties are evaluated using the parent's value extractor, which means expressions like `<MyComponent count={parentVar + 1} />` are evaluated in the parent's scope. The resolved values become available in the component implementation via `$props.count`. 515 | 516 | **Container Assembly:** 517 | 518 | ```typescript 519 | const containerNode: ContainerWrapperDef = useMemo(() => { 520 | const { loaders, vars, functions, scriptError, ...rest } = compound; 521 | return { 522 | type: "Container", 523 | uses: EMPTY_ARRAY, 524 | api, 525 | scriptCollected, 526 | loaders: loaders, 527 | vars, 528 | functions: functions, 529 | scriptError: scriptError, 530 | containerUid: uid, 531 | props: { 532 | debug: (compound.props as any)?.debug, 533 | }, 534 | children: [rest], 535 | }; 536 | }, [api, compound, scriptCollected, uid]); 537 | ``` 538 | 539 | The internal component implementation is wrapped in a Container that provides: 540 | - `loaders` - DataSource and other loader components defined at component level 541 | - `vars` - Variables declared with `<variable>` tags 542 | - `functions` - Methods declared with `<method>` tags 543 | - `scriptCollected` - Collected script declarations from `.xmlui.xs` code-behind files 544 | - `api` - Component API method mappings for external access 545 | 546 | **Implicit Variables Injection:** 547 | 548 | ```typescript 549 | const vars = useMemo(() => { 550 | return { 551 | $props: resolvedProps, 552 | ...containerNode.vars, 553 | emitEvent, 554 | hasEventHandler, 555 | updateState, 556 | }; 557 | }, [containerNode.vars, emitEvent, hasEventHandler, resolvedProps, updateState]); 558 | ``` 559 | 560 | CompoundComponent injects several implicit variables into the component's scope: 561 | - `$props` - Object containing all resolved property values 562 | - `emitEvent` - Function to fire custom events to parent 563 | - `hasEventHandler` - Function to check if parent registered an event handler 564 | - `updateState` - Function to programmatically update component state 565 | 566 | These variables are available throughout the component implementation without explicit declaration. 567 | 568 | **Event Emission:** 569 | 570 | ```typescript 571 | const emitEvent = useEvent((eventName, ...args) => { 572 | const handler = lookupEventHandler(eventName); 573 | if (handler) { 574 | return handler(...args); 575 | } 576 | }); 577 | ``` 578 | 579 | The `emitEvent()` function allows component implementations to notify parents of state changes: 580 | 581 | ```xmlui 582 | <Component name="Counter"> 583 | <variable name="count" value="{0}" /> 584 | <Button 585 | label="Increment: {count}" 586 | onClick="count++; emitEvent('valueChanged', count)" /> 587 | </Component> 588 | 589 | <!-- Parent usage --> 590 | <Counter onValueChanged="(newValue) => console.log('Count is now', newValue)" /> 591 | ``` 592 | 593 | When the button is clicked, `emitEvent('valueChanged', count)` looks up the parent's `onValueChanged` handler and calls it with the current count value. 594 | 595 | **Parent Render Context Assembly:** 596 | 597 | ```typescript 598 | const hasTemplateProps = useMemo(() => { 599 | return Object.entries(node.props).some(([key, value]) => { 600 | return ( 601 | key.endsWith("Template") || 602 | (isObject(value) && (value as any).type !== undefined) || 603 | (isArray(value) && (value as any)[0]?.type !== undefined) 604 | ); 605 | }); 606 | }, [node.props]); 607 | 608 | const memoedParentRenderContext = useMemo(() => { 609 | if (!hasTemplateProps && (!node.children || node.children.length === 0)) { 610 | return undefined; 611 | } 612 | return { 613 | renderChild, 614 | props: node.props, 615 | children: node.children, 616 | }; 617 | }, [hasTemplateProps, node.children, node.props, renderChild]); 618 | ``` 619 | 620 | CompoundComponent analyzes the parent's usage to determine if it needs to create a parent render context: 621 | 622 | **Parent render context is created when:** 623 | - The parent provides template properties (properties ending with "Template") 624 | - The parent provides children (for default slot transposition) 625 | 626 | **Parent render context is undefined when:** 627 | - No template properties are passed 628 | - No children are provided 629 | 630 | The parent render context is passed down through `renderChild()` and becomes available in `slotRenderer()` for slot transposition. 631 | 632 | **Detection Logic:** 633 | 634 | The `hasTemplateProps` check uses a heuristic to identify template properties: 635 | - Property name ends with "Template" (named slot convention) 636 | - Property value is an object with a `type` field (component definition) 637 | - Property value is an array with a first element that has a `type` field (array of component definitions) 638 | 639 | This detection enables CompoundComponent to optimize - if no templates or children are passed, it skips parent render context creation entirely. 640 | 641 | **Rendering Delegation:** 642 | 643 | ```typescript 644 | const ret = renderChild(nodeWithPropsAndEvents, safeLayoutContext, memoedParentRenderContext); 645 | ``` 646 | 647 | CompoundComponent delegates to `renderChild()` with: 648 | - `nodeWithPropsAndEvents` - The containerized component implementation with injected vars 649 | - `safeLayoutContext` - Layout context with `wrapChild` removed (wrapping already happened) 650 | - `memoedParentRenderContext` - Parent context for slot transposition (or undefined) 651 | 652 | This delegation routes through ComponentWrapper → ContainerWrapper → ComponentAdapter, which renders the component's internal markup and handles any Slot components via `slotRenderer()`. 653 | 654 | ### Parent Render Context Structure 655 | 656 | The parent render context is a critical data structure that enables slot transposition by carrying information from the parent component to the compound component's internal Slot elements. 657 | 658 | **Type Definition:** 659 | 660 | ```typescript 661 | export type ParentRenderContext = { 662 | renderChild: RenderChildFn; 663 | props: Record<string, any>; 664 | children?: Array<ComponentDef>; 665 | }; 666 | ``` 667 | 668 | **Properties:** 669 | 670 | - **renderChild** - The parent component's render function 671 | - Used to render slot content in the parent's scope 672 | - Ensures parent variables, IDs, and context are accessible in slot content 673 | - Preserves correct event handler bindings to parent actions 674 | 675 | - **props** - All properties passed to the compound component 676 | - Includes template properties (e.g., `headerTemplate`, `itemTemplate`) 677 | - Includes regular properties (accessed via `$props` in component implementation) 678 | - Template properties contain component definitions to render in named slots 679 | 680 | - **children** - Array of child component definitions 681 | - Represents the default children passed between component tags 682 | - Used for default (unnamed) slot transposition 683 | - Optional - undefined if no children provided 684 | 685 | **Flow Through Rendering Pipeline:** 686 | 687 | ``` 688 | Parent Component 689 | <MyComponent header="Title"> 690 | <property name="headerTemplate"> 691 | <H2>{$processedHeader}</H2> 692 | </property> 693 | <Button label="Click me" /> 694 | </MyComponent> 695 | ↓ 696 | CompoundComponent assembles parent render context: 697 | { 698 | renderChild: parentRenderChild, 699 | props: { 700 | header: "Title", 701 | headerTemplate: [{ type: "H2", children: [...] }] 702 | }, 703 | children: [{ type: "Button", props: { label: "Click me" } }] 704 | } 705 | ↓ 706 | Passed to renderChild as third parameter 707 | ↓ 708 | Flows through ComponentWrapper → ComponentAdapter 709 | ↓ 710 | Available in slotRenderer when Slot is encountered 711 | ↓ 712 | slotRenderer uses: 713 | - parentRenderContext.props[templateName] for named slots 714 | - parentRenderContext.children for default slot 715 | - parentRenderContext.renderChild to render content 716 | ``` 717 | 718 | **Scope Preservation:** 719 | 720 | The parent render context preserves the parent's rendering scope, which is critical for correct slot behavior: 721 | 722 | ```xmlui 723 | <!-- Parent Component --> 724 | <App var.userName="Alice"> 725 | <MyDialog> 726 | <property name="headerTemplate"> 727 | <Text>Welcome, {userName}!</Text> 728 | </property> 729 | </MyDialog> 730 | </App> 731 | 732 | <!-- MyDialog Component --> 733 | <Component name="MyDialog"> 734 | <Card> 735 | <Slot name="headerTemplate" /> 736 | </Card> 737 | </Component> 738 | ``` 739 | 740 | When the headerTemplate renders: 741 | - `parentRenderContext.renderChild` is the App's renderChild function 742 | - The expression `{userName}` evaluates in App's scope (where `userName` variable exists) 743 | - The Text component renders "Welcome, Alice!" even though it's physically inside MyDialog's markup 744 | 745 | Without parent render context preservation, the template would render in MyDialog's scope and `{userName}` would be undefined. 746 | 747 | ### Rendering Pipeline for User-Defined Components 748 | 749 | **Complete Flow:** 750 | 751 | ``` 752 | 1. Parent Component References User-Defined Component 753 | <ActionBar header="Menu"> 754 | <Button label="Save" /> 755 | <Button label="Cancel" /> 756 | </ActionBar> 757 | ↓ 758 | 2. ComponentWrapper receives component definition 759 | ↓ 760 | 3. ComponentWrapper routes to ComponentAdapter 761 | ↓ 762 | 4. ComponentAdapter queries ComponentRegistry 763 | - Receives: renderer, descriptor, isCompoundComponent=true 764 | - Renderer is CompoundComponentHolder 765 | ↓ 766 | 5. ComponentAdapter prepares RendererContext 767 | - Assembles state, extractValue, lookupEventHandler, etc. 768 | - Skips behavior application (isCompoundComponent=true) 769 | ↓ 770 | 6. ComponentAdapter calls CompoundComponentHolder(rendererContext) 771 | ↓ 772 | 7. CompoundComponentHolder wraps in CompoundComponent 773 | ↓ 774 | 8. CompoundComponent resolves properties 775 | - Extracts "Menu" from header property 776 | - Creates resolvedProps: { header: "Menu" } 777 | ↓ 778 | 9. CompoundComponent assembles Container for implementation 779 | - Injects $props, emitEvent, hasEventHandler, vars, methods 780 | ↓ 781 | 10. CompoundComponent creates parent render context 782 | - props: { header: "Menu" } 783 | - children: [Button, Button] 784 | - renderChild: parent's renderChild function 785 | ↓ 786 | 11. CompoundComponent calls renderChild(containerNode, layoutContext, parentRenderContext) 787 | ↓ 788 | 12. renderChild routes through ComponentWrapper → ContainerWrapper → ComponentAdapter 789 | ↓ 790 | 13. ComponentAdapter renders component's internal Card, HStack, H3, Slot 791 | ↓ 792 | 14. ComponentAdapter encounters Slot component (type === "Slot") 793 | ↓ 794 | 15. ComponentAdapter calls slotRenderer(rendererContext, parentRenderContext) 795 | ↓ 796 | 16. slotRenderer determines slot type (default, unnamed) 797 | ↓ 798 | 17. slotRenderer extracts slot properties (if any) 799 | ↓ 800 | 18. slotRenderer retrieves content from parentRenderContext.children 801 | ↓ 802 | 19. slotRenderer delegates to SlotItem 803 | ↓ 804 | 20. SlotItem transforms slot props to context variables 805 | - Creates Container with contextVars 806 | ↓ 807 | 21. SlotItem calls parentRenderContext.renderChild(containerizedNode) 808 | ↓ 809 | 22. Parent's renderChild renders Button components in parent's scope 810 | ↓ 811 | 23. Buttons render with access to parent's variables, IDs, event handlers 812 | ↓ 813 | 24. Full component tree assembled and returned to parent 814 | ``` 815 | 816 | **Key Transition Points:** 817 | 818 | - **Steps 1-5**: Standard component rendering setup 819 | - **Steps 6-11**: CompoundComponent-specific processing (property resolution, parent context assembly) 820 | - **Steps 12-13**: Rendering component's internal implementation 821 | - **Steps 14-19**: Slot transposition triggered 822 | - **Steps 20-23**: SlotItem scope transformation and parent content rendering 823 | 824 | **Scope Transitions:** 825 | 826 | The rendering pipeline involves three distinct scopes: 827 | 828 | 1. **Parent Scope** (Steps 1-5, 22-24) 829 | - Parent's variables, IDs, state accessible 830 | - Parent's renderChild function active 831 | - Parent's event handlers bound 832 | 833 | 2. **Component Scope** (Steps 6-13) 834 | - Component's variables, methods, state accessible 835 | - `$props` contains resolved parent properties 836 | - Component's renderChild function active 837 | - Internal implementation rendering 838 | 839 | 3. **Slot Content Scope** (Steps 19-23) 840 | - Parent's scope restored via parentRenderContext.renderChild 841 | - Slot properties available as `$` context variables 842 | - Hybrid scope: parent variables + slot-provided context variables 843 | 844 | This scope management ensures that: 845 | - Component implementations can access their own state and `$props` 846 | - Slot content can access parent variables and IDs 847 | - Slot properties from component flow into parent's template as context variables 848 | 849 | ## Advanced Patterns 850 | 851 | ### Conditional Slots 852 | 853 | Slots can be conditionally rendered based on component state or properties: 854 | 855 | ```xmlui 856 | <Component name="CollapsiblePanel"> 857 | <variable name="isExpanded" value="{$props.defaultExpanded ?? false}" /> 858 | 859 | <Card> 860 | <HStack> 861 | <Slot name="headerTemplate"> 862 | <H3>{$props.title}</H3> 863 | </Slot> 864 | <Button 865 | label="{isExpanded ? '−' : '+'}" 866 | onClick="isExpanded = !isExpanded" /> 867 | </HStack> 868 | 869 | <If condition="{isExpanded}"> 870 | <Slot /> 871 | </If> 872 | </Card> 873 | </Component> 874 | ``` 875 | 876 | The default slot only renders when `isExpanded` is true. The `<If>` component controls slot rendering based on component state. 877 | 878 | ### Nested Slots 879 | 880 | Slots can contain other slots, enabling multi-level template composition: 881 | 882 | ```xmlui 883 | <Component name="MasterDetail"> 884 | <FlowLayout> 885 | <VStack width="30%"> 886 | <Slot name="masterTemplate"> 887 | <Text>Master list goes here</Text> 888 | </Slot> 889 | </VStack> 890 | <VStack width="70%"> 891 | <Slot name="detailTemplate"> 892 | <Card> 893 | <Slot name="detailHeaderTemplate"> 894 | <H3>Detail Header</H3> 895 | </Slot> 896 | <Slot name="detailContentTemplate"> 897 | <Text>Detail content goes here</Text> 898 | </Slot> 899 | </Card> 900 | </Slot> 901 | </VStack> 902 | </FlowLayout> 903 | </Component> 904 | ``` 905 | 906 | Parent components can provide templates at each level: 907 | 908 | ```xmlui 909 | <MasterDetail> 910 | <property name="masterTemplate"> 911 | <ItemList items="{users}" /> 912 | </property> 913 | <property name="detailTemplate"> 914 | <UserDetail user="{selectedUser}"> 915 | <property name="detailHeaderTemplate"> 916 | <HStack> 917 | <Avatar src="{selectedUser.avatar}" /> 918 | <H2>{selectedUser.name}</H2> 919 | </HStack> 920 | </property> 921 | <property name="detailContentTemplate"> 922 | <UserProfile user="{selectedUser}" /> 923 | </property> 924 | </UserDetail> 925 | </property> 926 | </MasterDetail> 927 | ``` 928 | 929 | Each nested component can define its own slots, creating hierarchical template composition. 930 | 931 | ### Slot Properties with Arrow Functions 932 | 933 | Slot properties can be arrow functions that execute in the component's scope: 934 | 935 | ```xmlui 936 | <Component name="DataTable"> 937 | <variable name="sortedData" value="{sortData($props.data, $props.sortBy)}" /> 938 | 939 | <For items="{sortedData}"> 940 | <Slot 941 | name="rowTemplate" 942 | row="{$item}" 943 | index="{$index}" 944 | isEven="{$index % 2 === 0}" 945 | formatCurrency="(value) => '$' + value.toFixed(2)" 946 | formatDate="(dateStr) => new Date(dateStr).toLocaleDateString()"> 947 | <Text>{$row.name}</Text> 948 | </Slot> 949 | </For> 950 | </Component> 951 | 952 | <!-- Parent Usage --> 953 | <DataTable data="{salesData}" sortBy="date"> 954 | <property name="rowTemplate"> 955 | <Card backgroundColor="{$isEven ? 'gray.50' : 'white'}"> 956 | <Text>{$index + 1}. {$row.product}</Text> 957 | <Text>Amount: {$formatCurrency($row.amount)}</Text> 958 | <Text>Date: {$formatDate($row.date)}</Text> 959 | </Card> 960 | </property> 961 | </DataTable> 962 | ``` 963 | 964 | Arrow function slot properties provide utility functions that execute in the component's scope but are callable from the parent's template. This pattern enables components to provide formatting, validation, or data transformation functions to parent templates. 965 | 966 | ### Default Content with Slot Properties 967 | 968 | Slots can provide both default content and slot properties: 969 | 970 | ```xmlui 971 | <Component name="StatusBadge"> 972 | <variable name="statusColor" value="{getColorForStatus($props.status)}" /> 973 | <variable name="statusIcon" value="{getIconForStatus($props.status)}" /> 974 | 975 | <Slot 976 | color="{statusColor}" 977 | icon="{statusIcon}" 978 | status="{$props.status}"> 979 | <!-- Default rendering if parent doesn't provide template --> 980 | <HStack> 981 | <Icon name="{statusIcon}" color="{statusColor}" /> 982 | <Text color="{statusColor}">{$props.status}</Text> 983 | </HStack> 984 | </Slot> 985 | </Component> 986 | 987 | <!-- Simple usage - uses default rendering --> 988 | <StatusBadge status="active" /> 989 | 990 | <!-- Custom rendering - uses slot properties --> 991 | <StatusBadge status="active"> 992 | <Badge variant="solid" colorScheme="{$color}"> 993 | <Icon name="{$icon}" /> 994 | <Text>{$status.toUpperCase()}</Text> 995 | </Badge> 996 | </StatusBadge> 997 | ``` 998 | 999 | This pattern allows components to provide sensible defaults while enabling parent customization when needed. 1000 | 1001 | ## Common Patterns and Best Practices 1002 | 1003 | ### Component Naming Conventions 1004 | 1005 | **Component Names:** 1006 | - Must start with uppercase letter (e.g., `MyComponent`, not `myComponent`) 1007 | - Use PascalCase for multi-word names (e.g., `ActionBar`, `UserProfile`) 1008 | - Match filename exactly (e.g., `ActionBar.xmlui` contains `<Component name="ActionBar">`) 1009 | 1010 | **Slot Names:** 1011 | - Must end with `Template` suffix for named slots (e.g., `headerTemplate`, `itemTemplate`) 1012 | - Use camelCase for consistency (e.g., `headerTemplate`, not `HeaderTemplate`) 1013 | - Be descriptive of content purpose (e.g., `emptyStateTemplate`, not `template1`) 1014 | 1015 | **Property Names:** 1016 | - Use camelCase (e.g., `userName`, `isVisible`, `onItemSelected`) 1017 | - Prefix event properties with `on` (e.g., `onValueChanged`, `onSubmit`) 1018 | - Keep names concise but descriptive 1019 | 1020 | ### Property Validation 1021 | 1022 | Use default values and type checking in component implementation: 1023 | 1024 | ```xmlui 1025 | <Component name="Avatar"> 1026 | <variable name="validSize" value="{['sm', 'md', 'lg'].includes($props.size) ? $props.size : 'md'}" /> 1027 | <variable name="initials" value="{$props.name ? $props.name.substring(0, 2).toUpperCase() : '??'}" /> 1028 | 1029 | <If condition="{$props.src}"> 1030 | <Image src="{$props.src}" width="{validSize}" height="{validSize}" /> 1031 | </If> 1032 | <If condition="{!$props.src}"> 1033 | <Card width="{validSize}" height="{validSize}"> 1034 | <Text>{initials}</Text> 1035 | </Card> 1036 | </If> 1037 | </Component> 1038 | ``` 1039 | 1040 | This pattern provides fallbacks for missing or invalid properties. 1041 | 1042 | ### State Management 1043 | 1044 | Keep component state internal and communicate changes through events: 1045 | 1046 | ```xmlui 1047 | <Component name="SearchBox"> 1048 | <variable name="query" value="{$props.initialQuery ?? ''}" /> 1049 | <variable name="results" value="{[]}" /> 1050 | 1051 | <VStack> 1052 | <TextBox 1053 | value="{query}" 1054 | onValueChanged="query = $event; search()" /> 1055 | 1056 | <method name="search"> 1057 | const newResults = await searchAPI(query); 1058 | results = newResults; 1059 | emitEvent('searchCompleted', { query, results: newResults }); 1060 | </method> 1061 | 1062 | <For items="{results}"> 1063 | <Slot name="resultTemplate" result="{$item}" index="{$index}"> 1064 | <Text>{$result.title}</Text> 1065 | </Slot> 1066 | </For> 1067 | </VStack> 1068 | </Component> 1069 | ``` 1070 | 1071 | The component manages internal state (`query`, `results`) and notifies parent of changes via `emitEvent()`. 1072 | 1073 | ### Slot Default Content 1074 | 1075 | Always provide meaningful default content for slots: 1076 | 1077 | ```xmlui 1078 | <Component name="Card"> 1079 | <Card> 1080 | <Slot name="headerTemplate"> 1081 | <H3>{$props.title ?? 'Untitled'}</H3> 1082 | </Slot> 1083 | 1084 | <Slot> 1085 | <Text color="gray.500">No content provided</Text> 1086 | </Slot> 1087 | 1088 | <Slot name="actionsTemplate"> 1089 | <Button label="Close" onClick="$this.close()" /> 1090 | </Slot> 1091 | </Card> 1092 | </Component> 1093 | ``` 1094 | 1095 | Default content provides fallback rendering when parents don't provide custom templates, making components more robust and self-documenting. 1096 | 1097 | ### Composition Over Inheritance 1098 | 1099 | Use slot composition to build specialized components from generic ones: 1100 | 1101 | ```xmlui 1102 | <!-- Generic Dialog Component --> 1103 | <Component name="Dialog"> 1104 | <ModalDialog> 1105 | <Slot name="headerTemplate" /> 1106 | <Slot /> 1107 | <Slot name="actionsTemplate" /> 1108 | </ModalDialog> 1109 | </Component> 1110 | 1111 | <!-- Specialized Confirmation Dialog --> 1112 | <Component name="ConfirmDialog"> 1113 | <Dialog> 1114 | <property name="headerTemplate"> 1115 | <HStack> 1116 | <Icon name="alert-triangle" color="warn" /> 1117 | <H3>{$props.title}</H3> 1118 | </HStack> 1119 | </property> 1120 | 1121 | <Text>{$props.message}</Text> 1122 | 1123 | <property name="actionsTemplate"> 1124 | <Button label="Cancel" onClick="emitEvent('cancel')" /> 1125 | <Button label="Confirm" onClick="emitEvent('confirm')" variant="primary" /> 1126 | </property> 1127 | </Dialog> 1128 | </Component> 1129 | ``` 1130 | 1131 | This pattern creates component hierarchies through composition rather than requiring inheritance mechanisms. 1132 | 1133 | ## Implementation Notes for Framework Developers 1134 | 1135 | ### Performance Considerations 1136 | 1137 | **Memoization Strategy:** 1138 | 1139 | - CompoundComponent uses `useShallowCompareMemoize` for resolved props to prevent re-renders when prop objects are recreated but values haven't changed 1140 | - SlotItem uses `React.memo` and shallow comparison for slot props to avoid re-rendering when compound component re-renders but slot data is stable 1141 | - Parent render context is memoized to prevent recreation on every render 1142 | 1143 | **Rendering Optimization:** 1144 | 1145 | - Parent render context is only created when templates or children are present 1146 | - `hasTemplateProps` check uses heuristics to detect template properties efficiently 1147 | - Slot renderer short-circuits when no parent context and no default content 1148 | 1149 | **State Isolation:** 1150 | 1151 | - Each compound component instance gets unique `uid` for state isolation 1152 | - Container wrapping ensures component state doesn't pollute parent or global state 1153 | - Slot content renders in isolated Container scope with context variables 1154 | 1155 | ### Error Handling 1156 | 1157 | **Slot Name Validation:** 1158 | 1159 | Named slots must end with "Template" suffix. The `slotRenderer` function validates this and renders an `InvalidComponent` with error message if violated: 1160 | 1161 | ```typescript 1162 | if (templateName && !templateName.endsWith("Template")) { 1163 | return ( 1164 | <InvalidComponent 1165 | node={node} 1166 | errors={[ 1167 | `Slot name '${templateName}' is not valid. ` + 1168 | "A named slot should use a name ending with 'Template'.", 1169 | ]} 1170 | /> 1171 | ); 1172 | } 1173 | ``` 1174 | 1175 | This validation prevents common mistakes and provides clear error messages during development. 1176 | 1177 | **Component Registry Failures:** 1178 | 1179 | When ComponentAdapter can't find a renderer for a component type, it renders `UnknownComponent`: 1180 | 1181 | ```typescript 1182 | if (!renderer) { 1183 | console.error( 1184 | `Component '${safeNode.type}' is not available. Did you forget to register it?`, 1185 | ); 1186 | return <UnknownComponent message={`${safeNode.type}`} />; 1187 | } 1188 | ``` 1189 | 1190 | This provides graceful degradation and clear feedback when components are referenced but not defined. 1191 | 1192 | **Rendering Exceptions:** 1193 | 1194 | ComponentAdapter wraps rendering in try-catch and renders `InvalidComponent` with error details when exceptions occur: 1195 | 1196 | ```typescript 1197 | try { 1198 | renderedNode = renderer(rendererContext); 1199 | // ... behavior application, decoration ... 1200 | } catch (e) { 1201 | renderingError = (e as Error)?.message || "Internal error"; 1202 | console.error(e); 1203 | } 1204 | 1205 | if (renderingError) { 1206 | return ( 1207 | <InvalidComponent errors={[renderingError]} node={safeNode}> 1208 | {renderedNode} 1209 | </InvalidComponent> 1210 | ); 1211 | } 1212 | ``` 1213 | 1214 | This ensures rendering errors don't crash the entire application. 1215 | 1216 | ### Extensibility Points 1217 | 1218 | **Custom Component Registration:** 1219 | 1220 | External packages can register user-defined components through the `ContributesDefinition` mechanism: 1221 | 1222 | ```typescript 1223 | export default { 1224 | namespace: "MyPackage", 1225 | components: [ 1226 | { 1227 | name: "MyComponent", 1228 | component: compoundDefinition, 1229 | metadata: componentMetadata, 1230 | } 1231 | ], 1232 | }; 1233 | ``` 1234 | 1235 | **Slot Renderer Extension:** 1236 | 1237 | The `slotRenderer` function is internal to ComponentAdapter but could be extended to support additional slot features: 1238 | - Conditional slots based on media queries 1239 | - Slot content caching for performance 1240 | - Slot content validation against schemas 1241 | 1242 | **Parent Render Context Extension:** 1243 | 1244 | The parent render context structure could be extended to include additional information: 1245 | - Parent component metadata 1246 | - Parent state snapshots 1247 | - Parent event handler registry 1248 | 1249 | ### Testing Strategies 1250 | 1251 | **Component Testing:** 1252 | 1253 | Test user-defined components by providing test implementations: 1254 | 1255 | ```typescript 1256 | await initTestBed(`<MyComponent testProp="value" />`, { 1257 | components: [ 1258 | ` 1259 | <Component name="MyComponent"> 1260 | <Text>{$props.testProp}</Text> 1261 | </Component> 1262 | `, 1263 | ], 1264 | }); 1265 | ``` 1266 | 1267 | **Slot Testing:** 1268 | 1269 | Test slot transposition by providing parent children and templates: 1270 | 1271 | ```typescript 1272 | await initTestBed( 1273 | ` 1274 | <MyComponent> 1275 | <property name="headerTemplate"> 1276 | <Text>Custom Header</Text> 1277 | </property> 1278 | <Text>Default content</Text> 1279 | </MyComponent> 1280 | `, 1281 | { 1282 | components: [`<Component name="MyComponent">...</Component>`], 1283 | } 1284 | ); 1285 | ``` 1286 | 1287 | **Slot Properties Testing:** 1288 | 1289 | Test slot properties by verifying context variables are available: 1290 | 1291 | ```typescript 1292 | await initTestBed( 1293 | ` 1294 | <MyComponent> 1295 | <property name="itemTemplate"> 1296 | <Text>{$item} at {$index}</Text> 1297 | </property> 1298 | </MyComponent> 1299 | `, 1300 | { 1301 | components: [ 1302 | ` 1303 | <Component name="MyComponent"> 1304 | <Slot name="itemTemplate" item="Apple" index="{0}" /> 1305 | </Component> 1306 | `, 1307 | ], 1308 | } 1309 | ); 1310 | ``` 1311 | 1312 | ## Summary 1313 | 1314 | User-defined components in XMLUI provide a powerful declarative component model built on slot-based template composition. The architecture centers on three key mechanisms: 1315 | 1316 | 1. **CompoundComponent** manages the bridge between component interface and implementation, resolving properties, providing event emission, and assembling parent render context. 1317 | 1318 | 2. **slotRenderer** implements the core transposition logic, determining what content to render in each slot based on parent-provided templates, children, and default content. 1319 | 1320 | 3. **SlotItem** transforms slot properties into context variables, enabling bidirectional data flow between compound components and parent templates. 1321 | 1322 | ComponentAdapter orchestrates the rendering pipeline, routing compound components through CompoundComponent, detecting Slot components, and delegating to slotRenderer for transposition. The parent render context structure preserves scope boundaries, ensuring slot content renders in the parent's scope with access to both parent variables and component-provided context variables. 1323 | 1324 | This architecture enables composition patterns like nested slots, conditional slots, slot properties with arrow functions, and default content with slot properties. The memoization strategy optimizes performance by preventing unnecessary re-renders, while error handling provides graceful degradation and clear feedback during development. 1325 | 1326 | For framework developers working on XMLUI core, understanding the slot transposition mechanism, parent render context flow, and scope management is essential for maintaining and extending the user-defined component infrastructure. 1327 | ```