This is page 103 of 181. Use http://codebase.md/xmlui-org/xmlui/mockApiDef.js?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .changeset
│ └── config.json
├── .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
│ ├── layout-changes.md
│ ├── 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
│ │ ├── resources
│ │ │ ├── favicon.ico
│ │ │ ├── files
│ │ │ │ └── for-download
│ │ │ │ └── xmlui
│ │ │ │ └── xmlui-standalone.umd.js
│ │ │ ├── github.svg
│ │ │ ├── llms.txt
│ │ │ ├── logo-dark.svg
│ │ │ ├── logo.svg
│ │ │ ├── pg-popout.svg
│ │ │ ├── rss.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
│ │ │ └── PageNotFound.xmlui
│ │ ├── config.ts
│ │ ├── Main.xmlui
│ │ └── themes
│ │ └── blog-theme.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
│ ├── components-with-options.md
│ ├── containers.md
│ ├── data-operations.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
│ ├── 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
│ │ │ ├── OptionContext.ts
│ │ │ ├── Select.md
│ │ │ ├── Select.module.scss
│ │ │ ├── Select.spec.ts
│ │ │ ├── Select.tsx
│ │ │ ├── SelectContext.tsx
│ │ │ └── SelectNative.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
│ │ │ ├── base64-utils.ts
│ │ │ ├── compound-utils.ts
│ │ │ ├── css-utils.ts
│ │ │ ├── DataLoaderQueryKeyGenerator.ts
│ │ │ ├── date-utils.ts
│ │ │ ├── extractParam.ts
│ │ │ ├── hooks.tsx
│ │ │ ├── LruCache.ts
│ │ │ ├── mergeProps.ts
│ │ │ ├── misc.ts
│ │ │ ├── request-params.ts
│ │ │ ├── statementUtils.ts
│ │ │ └── treeUtils.ts
│ │ └── xmlui-parser.ts
│ ├── index-standalone.ts
│ ├── index.scss
│ ├── index.ts
│ ├── language-server
│ │ ├── server-common.ts
│ │ ├── server-web-worker.ts
│ │ ├── server.ts
│ │ ├── services
│ │ │ ├── common
│ │ │ │ ├── docs-generation.ts
│ │ │ │ ├── lsp-utils.ts
│ │ │ │ ├── metadata-utils.ts
│ │ │ │ └── syntax-node-utilities.ts
│ │ │ ├── completion.ts
│ │ │ ├── diagnostic.ts
│ │ │ ├── format.ts
│ │ │ └── hover.ts
│ │ └── xmlui-metadata-generated.mjs
│ ├── logging
│ │ ├── LoggerContext.tsx
│ │ ├── LoggerInitializer.tsx
│ │ ├── LoggerService.ts
│ │ └── xmlui.ts
│ ├── logo.svg
│ ├── parsers
│ │ ├── common
│ │ │ ├── GenericToken.ts
│ │ │ ├── InputStream.ts
│ │ │ └── utils.ts
│ │ ├── scripting
│ │ │ ├── code-behind-collect.ts
│ │ │ ├── Lexer.ts
│ │ │ ├── modules.ts
│ │ │ ├── Parser.ts
│ │ │ ├── ParserError.ts
│ │ │ ├── ScriptingNodeTypes.ts
│ │ │ ├── TokenTrait.ts
│ │ │ ├── TokenType.ts
│ │ │ └── tree-visitor.ts
│ │ ├── style-parser
│ │ │ ├── errors.ts
│ │ │ ├── source-tree.ts
│ │ │ ├── StyleInputStream.ts
│ │ │ ├── StyleLexer.ts
│ │ │ ├── StyleParser.ts
│ │ │ └── tokens.ts
│ │ └── xmlui-parser
│ │ ├── CharacterCodes.ts
│ │ ├── diagnostics.ts
│ │ ├── fileExtensions.ts
│ │ ├── index.ts
│ │ ├── lint.ts
│ │ ├── parser.ts
│ │ ├── ParserError.ts
│ │ ├── scanner.ts
│ │ ├── syntax-kind.ts
│ │ ├── syntax-node.ts
│ │ ├── transform.ts
│ │ ├── utils.ts
│ │ ├── xmlui-serializer.ts
│ │ └── xmlui-tree.ts
│ ├── react-app-env.d.ts
│ ├── syntax
│ │ ├── monaco
│ │ │ ├── grammar.monacoLanguage.ts
│ │ │ ├── index.ts
│ │ │ ├── xmlui-dark.ts
│ │ │ ├── xmlui-light.ts
│ │ │ └── xmluiscript.monacoLanguage.ts
│ │ └── textMate
│ │ ├── index.ts
│ │ ├── xmlui-dark.json
│ │ ├── xmlui-light.json
│ │ ├── xmlui.json
│ │ └── xmlui.tmLanguage.json
│ ├── testing
│ │ ├── assertions.ts
│ │ ├── component-test-helpers.ts
│ │ ├── ComponentDrivers.ts
│ │ ├── drivers
│ │ │ ├── DateInputDriver.ts
│ │ │ ├── ModalDialogDriver.ts
│ │ │ ├── NumberBoxDriver.ts
│ │ │ ├── TextBoxDriver.ts
│ │ │ ├── TimeInputDriver.ts
│ │ │ ├── TimerDriver.ts
│ │ │ └── TreeDriver.ts
│ │ ├── fixtures.ts
│ │ ├── infrastructure
│ │ │ ├── index.html
│ │ │ ├── main.tsx
│ │ │ ├── public
│ │ │ │ ├── mockServiceWorker.js
│ │ │ │ ├── resources
│ │ │ │ │ ├── bell.svg
│ │ │ │ │ ├── box.svg
│ │ │ │ │ ├── doc.svg
│ │ │ │ │ ├── eye.svg
│ │ │ │ │ ├── flower-640x480.jpg
│ │ │ │ │ ├── sun.svg
│ │ │ │ │ ├── test-image-100x100.jpg
│ │ │ │ │ └── txt.svg
│ │ │ │ └── serve.json
│ │ │ └── TestBed.tsx
│ │ └── themed-app-test-helpers.ts
│ └── vite-env.d.ts
├── tests
│ ├── components
│ │ ├── CodeBlock
│ │ │ └── hightlight-code.test.ts
│ │ ├── playground-pattern.test.ts
│ │ └── Tree
│ │ └── Tree-states.test.ts
│ ├── components-core
│ │ ├── abstractions
│ │ │ └── treeAbstractions.test.ts
│ │ ├── container
│ │ │ └── buildProxy.test.ts
│ │ ├── interception
│ │ │ ├── orderBy.test.ts
│ │ │ ├── ReadOnlyCollection.test.ts
│ │ │ └── request-param-converter.test.ts
│ │ ├── scripts-runner
│ │ │ ├── AttributeValueParser.test.ts
│ │ │ ├── eval-tree-arrow-async.test.ts
│ │ │ ├── eval-tree-arrow.test.ts
│ │ │ ├── eval-tree-func-decl-async.test.ts
│ │ │ ├── eval-tree-func-decl.test.ts
│ │ │ ├── eval-tree-pre-post.test.ts
│ │ │ ├── eval-tree-regression.test.ts
│ │ │ ├── eval-tree.test.ts
│ │ │ ├── function-proxy.test.ts
│ │ │ ├── parser-regression.test.ts
│ │ │ ├── process-event.test.ts
│ │ │ ├── process-function.test.ts
│ │ │ ├── process-implicit-context.test.ts
│ │ │ ├── process-statement-asgn.test.ts
│ │ │ ├── process-statement-destruct.test.ts
│ │ │ ├── process-statement-regs.test.ts
│ │ │ ├── process-statement-sync.test.ts
│ │ │ ├── process-statement.test.ts
│ │ │ ├── process-switch-sync.test.ts
│ │ │ ├── process-switch.test.ts
│ │ │ ├── process-try-sync.test.ts
│ │ │ ├── process-try.test.ts
│ │ │ └── test-helpers.ts
│ │ ├── test-metadata-handler.ts
│ │ ├── theming
│ │ │ ├── border-segments.test.ts
│ │ │ ├── component-layout.resolver.test.ts
│ │ │ ├── layout-property-parser.test.ts
│ │ │ ├── layout-resolver.test.ts
│ │ │ ├── layout-resolver2.test.ts
│ │ │ ├── layout-vp-override.test.ts
│ │ │ └── padding-segments.test.ts
│ │ └── utils
│ │ ├── date-utils.test.ts
│ │ ├── format-human-elapsed-time.test.ts
│ │ └── LruCache.test.ts
│ ├── language-server
│ │ ├── completion.test.ts
│ │ ├── format.test.ts
│ │ ├── hover.test.ts
│ │ └── mockData.ts
│ └── parsers
│ ├── common
│ │ └── input-stream.test.ts
│ ├── markdown
│ │ └── parse-binding-expression.test.ts
│ ├── parameter-parser.test.ts
│ ├── paremeter-parser.test.ts
│ ├── scripting
│ │ ├── eval-tree-arrow.test.ts
│ │ ├── eval-tree-pre-post.test.ts
│ │ ├── eval-tree.test.ts
│ │ ├── function-proxy.test.ts
│ │ ├── lexer-literals.test.ts
│ │ ├── lexer-misc.test.ts
│ │ ├── module-parse.test.ts
│ │ ├── parser-arrow.test.ts
│ │ ├── parser-assignments.test.ts
│ │ ├── parser-binary.test.ts
│ │ ├── parser-destructuring.test.ts
│ │ ├── parser-errors.test.ts
│ │ ├── parser-expressions.test.ts
│ │ ├── parser-function.test.ts
│ │ ├── parser-literals.test.ts
│ │ ├── parser-primary.test.ts
│ │ ├── parser-regex.test.ts
│ │ ├── parser-statements.test.ts
│ │ ├── parser-unary.test.ts
│ │ ├── process-event.test.ts
│ │ ├── process-implicit-context.test.ts
│ │ ├── process-statement-asgn.test.ts
│ │ ├── process-statement-destruct.test.ts
│ │ ├── process-statement-regs.test.ts
│ │ ├── process-statement-sync.test.ts
│ │ ├── process-statement.test.ts
│ │ ├── process-switch-sync.test.ts
│ │ ├── process-switch.test.ts
│ │ ├── process-try-sync.test.ts
│ │ ├── process-try.test.ts
│ │ ├── simplify-expression.test.ts
│ │ ├── statement-hooks.test.ts
│ │ └── test-helpers.ts
│ ├── style-parser
│ │ ├── generateHvarChain.test.ts
│ │ ├── parseHVar.test.ts
│ │ ├── parser.test.ts
│ │ └── tokens.test.ts
│ └── xmlui
│ ├── lint.test.ts
│ ├── parser.test.ts
│ ├── scanner.test.ts
│ ├── transform.attr.test.ts
│ ├── transform.circular.test.ts
│ ├── transform.element.test.ts
│ ├── transform.errors.test.ts
│ ├── transform.escape.test.ts
│ ├── transform.regression.test.ts
│ ├── transform.script.test.ts
│ ├── transform.test.ts
│ └── xmlui.ts
├── tests-e2e
│ ├── api-bound-component-regression.spec.ts
│ ├── api-call-as-extracted-component.spec.ts
│ ├── assign-to-object-or-array-regression.spec.ts
│ ├── binding-regression.spec.ts
│ ├── children-as-template-context-vars.spec.ts
│ ├── compound-component.spec.ts
│ ├── context-vars-regression.spec.ts
│ ├── data-bindings.spec.ts
│ ├── datasource-and-api-usage-in-var.spec.ts
│ ├── datasource-direct-binding.spec.ts
│ ├── datasource-onLoaded-regression.spec.ts
│ ├── modify-array-item-regression.spec.ts
│ ├── namespaces.spec.ts
│ ├── push-to-array-regression.spec.ts
│ ├── screen-breakpoints.spec.ts
│ ├── scripting.spec.ts
│ ├── state-scope-in-pages.spec.ts
│ └── state-var-scopes.spec.ts
├── tsconfig.bin.json
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
└── vitest.config.ts
```
# Files
--------------------------------------------------------------------------------
/xmlui/src/components/App/App.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import styles from "./App.module.scss";
2 | import drawerStyles from "./Sheet.module.scss";
3 |
4 | import { type ComponentDef } from "../../abstractions/ComponentDefs";
5 | import { createComponentRenderer } from "../../components-core/renderers";
6 | import { parseScssVar } from "../../components-core/theming/themeVars";
7 |
8 | import { createMetadata, dComponent } from "../../components/metadata-helpers";
9 | import { appLayoutMd } from "./AppLayoutContext";
10 | import { App, defaultProps } from "./AppNative";
11 | import type { CSSProperties } from "react";
12 | import { useCallback, useEffect, useMemo, useRef, useState, useTransition } from "react";
13 | import type { PageMd } from "../Pages/Pages";
14 | import type { RenderChildFn } from "../../abstractions/RendererDefs";
15 | import { IndexerContext } from "./IndexerContext";
16 | import { createPortal } from "react-dom";
17 | import { useAppContext } from "../../components-core/AppContext";
18 | import { useSearchContextSetIndexing, useSearchContextUpdater } from "./SearchContext";
19 |
20 | // --- Define a structure to represent navigation hierarchy
21 | interface NavHierarchyNode {
22 | type: string;
23 | label: string;
24 | path?: string;
25 | children?: NavHierarchyNode[];
26 | }
27 |
28 | const COMP = "App";
29 |
30 | export const AppMd = createMetadata({
31 | status: "stable",
32 | description:
33 | "The `App` component is the root container that defines your application's overall " +
34 | "structure and layout. It provides a complete UI framework with built-in navigation, " +
35 | "header, footer, and content areas that work together seamlessly.",
36 | props: {
37 | layout: {
38 | description:
39 | `This property sets the layout template of the app. This setting determines the position ` +
40 | `and size of the app parts (such as header, navigation bar, footer, etc.) and the app's ` +
41 | `scroll behavior.`,
42 | availableValues: appLayoutMd,
43 | },
44 | loggedInUser: {
45 | description:
46 | "Stores information about the currently logged-in user. By not defining this property, " +
47 | "you can indicate that no user is logged in.",
48 | valueType: "string",
49 | },
50 | logoTemplate: dComponent("Optional template of the app logo"),
51 | logo: {
52 | description: "Optional logo path",
53 | valueType: "string",
54 | },
55 | "logo-dark": {
56 | description: "Optional logo path in dark tone",
57 | valueType: "string",
58 | },
59 | "logo-light": {
60 | description: "Optional logo path in light tone",
61 | valueType: "string",
62 | },
63 | name: {
64 | description:
65 | "Optional application name (visible in the browser tab). When you do not define " +
66 | "this property, the tab name falls back to the one defined in the app\'s configuration. " +
67 | 'If the name is not configured, "XMLUI App" is displayed in the tab.',
68 | valueType: "string",
69 | },
70 | scrollWholePage: {
71 | description:
72 | `This boolean property specifies whether the whole page should scroll (\`true\`) or just ` +
73 | `the content area (\`false\`). The default value is \`true\`.`,
74 | valueType: "boolean",
75 | defaultValue: defaultProps.scrollWholePage,
76 | },
77 | noScrollbarGutters: {
78 | description:
79 | "This boolean property specifies whether the scrollbar gutters should be hidden.",
80 | valueType: "boolean",
81 | defaultValue: defaultProps.noScrollbarGutters,
82 | },
83 | defaultTone: {
84 | description: 'This property sets the app\'s default tone ("light" or "dark").',
85 | valueType: "string",
86 | defaultValue: defaultProps.defaultTone,
87 | availableValues: ["light", "dark"],
88 | },
89 | defaultTheme: {
90 | description: "This property sets the app's default theme.",
91 | valueType: "string",
92 | defaultValue: defaultProps.defaultTheme,
93 | },
94 | autoDetectTone: {
95 | description:
96 | 'This boolean property enables automatic detection of the system theme preference. ' +
97 | 'When set to true and no defaultTone is specified, the app will automatically use ' +
98 | '"light" or "dark" tone based on the user\'s system theme setting. The app will ' +
99 | 'also respond to changes in the system theme preference.',
100 | valueType: "boolean",
101 | defaultValue: defaultProps.autoDetectTone,
102 | },
103 | },
104 | events: {
105 | ready: {
106 | description: `This event fires when the \`${COMP}\` component finishes rendering on the page.`,
107 | },
108 | messageReceived: {
109 | description: `This event fires when the \`${COMP}\` component receives a message from another window or iframe via the window.postMessage API.`,
110 | },
111 | },
112 | themeVars: { ...parseScssVar(styles.themeVars), ...parseScssVar(drawerStyles.themeVars) },
113 | limitThemeVarsToComponent: true,
114 | themeVarDescriptions: {
115 | "maxWidth-content-App":
116 | "This theme variable defines the maximum width of the main content. If the main " +
117 | "content is broader, the engine adds margins to keep the expected maximum size.",
118 | "boxShadow‑header‑App": "This theme variable sets the shadow of the app's header section.",
119 | "boxShadow‑navPanel‑App":
120 | "This theme variable sets the shadow of the app's navigation panel section " +
121 | "(visible only in vertical layouts).",
122 | "width‑navPanel‑App":
123 | "This theme variable sets the width of the navigation panel when the app is displayed " +
124 | "with one of the vertical layouts.",
125 | },
126 | defaultThemeVars: {
127 | "maxWidth-Drawer": "100%",
128 | [`width-navPanel-${COMP}`]: "$space-64",
129 | [`backgroundColor-navPanel-${COMP}`]: "$backgroundColor",
130 | [`maxWidth-content-${COMP}`]: "$maxWidth-content",
131 | [`boxShadow-header-${COMP}`]: "none",
132 | [`boxShadow-navPanel-${COMP}`]: "$boxShadow-spread",
133 | [`scroll-padding-block-Pages`]: "$space-4",
134 | [`backgroundColor-content-App`]: "$backgroundColor",
135 | light: {
136 | // --- No light-specific theme vars
137 | },
138 | dark: {
139 | // --- No dark-specific theme vars
140 | },
141 | },
142 | });
143 |
144 |
145 | function AppNode({ node, extractValue, renderChild, className, lookupEventHandler, registerComponentApi }) {
146 | // --- Use ref to track if we've already processed the navigation to avoid duplicates in strict mode
147 | const processedNavRef = useRef(false);
148 |
149 | // --- Memoize the layout type to avoid unnecessary re-extraction
150 | const layoutType = useMemo(
151 | () => extractValue(node.props.layout),
152 | [node.props.layout, extractValue],
153 | );
154 |
155 | // --- Memoize helper functions that are used in multiple places
156 |
157 | // --- Parse a string into hierarchy labels, handling escaped pipe characters
158 | const parseHierarchyLabels = useMemo(() => {
159 | // Cache to hold previously computed results
160 | const cache = new Map<string, string[]>();
161 |
162 | return (labelText: string): string[] => {
163 | // Return cached result if we've seen this input before
164 | if (cache.has(labelText)) {
165 | return cache.get(labelText)!;
166 | }
167 |
168 | const result: string[] = [];
169 | let currentLabel = "";
170 | let escaped = false;
171 |
172 | for (let i = 0; i < labelText.length; i++) {
173 | const char = labelText[i];
174 |
175 | if (escaped) {
176 | // --- If this character was escaped, just add it literally
177 | currentLabel += char;
178 | escaped = false;
179 | } else if (char === "\\") {
180 | // --- Start of an escape sequence
181 | escaped = true;
182 | } else if (char === "|") {
183 | // --- Unescaped pipe indicates hierarchy separator
184 | result.push(currentLabel.trim());
185 | currentLabel = "";
186 | } else {
187 | // --- Regular character
188 | currentLabel += char;
189 | }
190 | }
191 |
192 | // --- Don't forget to add the last segment
193 | if (currentLabel.length > 0) {
194 | result.push(currentLabel.trim());
195 | }
196 |
197 | // Cache the result
198 | cache.set(labelText, result);
199 |
200 | return result;
201 | };
202 | }, []);
203 |
204 | // --- Helper function to check if a label exists in the navigation hierarchy
205 | const labelExistsInHierarchy = useMemo(() => {
206 | // Cache for previously checked labels within a hierarchy
207 | const cache = new Map<string, boolean>();
208 |
209 | return (searchLabel: string, hierarchy: any[]): boolean => {
210 | // Create a cache key (could be improved with a better serialization of hierarchy)
211 | const cacheKey = searchLabel + "_" + hierarchy.length;
212 |
213 | if (cache.has(cacheKey)) {
214 | return cache.get(cacheKey)!;
215 | }
216 |
217 | const result = hierarchy.some((node) => {
218 | if (node.label === searchLabel) {
219 | return true;
220 | }
221 |
222 | if (node.children && node.children.length > 0) {
223 | return labelExistsInHierarchy(searchLabel, node.children);
224 | }
225 |
226 | return false;
227 | });
228 |
229 | cache.set(cacheKey, result);
230 | return result;
231 | };
232 | }, []);
233 |
234 | // --- Helper function to find or create NavGroups in the hierarchy
235 | const findOrCreateNavGroup = useMemo(() => {
236 | return (navItems: any[], groupLabel: string): any => {
237 | // --- Check if a NavGroup with this label already exists
238 | const existingGroup = navItems.find(
239 | (item) => item.type === "NavGroup" && item.props?.label === groupLabel,
240 | );
241 |
242 | if (existingGroup) {
243 | return existingGroup;
244 | }
245 |
246 | // --- Create a new NavGroup and add it to the array
247 | const newGroup = {
248 | type: "NavGroup",
249 | props: {
250 | label: groupLabel,
251 | },
252 | children: [],
253 | };
254 |
255 | navItems.push(newGroup);
256 | return newGroup;
257 | };
258 | }, []);
259 |
260 | const { AppHeader, Footer, NavPanel, Pages, restChildren } = useMemo(() => {
261 | let AppHeader: ComponentDef;
262 | let Footer: ComponentDef;
263 | let NavPanel: ComponentDef;
264 | let Pages: ComponentDef;
265 | const restChildren: any[] = [];
266 | node.children?.forEach((rootChild) => {
267 | let transformedChild = { ...rootChild };
268 | if (rootChild.type === "Theme") {
269 | transformedChild.children = rootChild.children?.filter((child) => {
270 | if (child.type === "AppHeader") {
271 | AppHeader = {
272 | ...rootChild,
273 | children: [child],
274 | };
275 | return false;
276 | } else if (child.type === "Footer") {
277 | Footer = {
278 | ...rootChild,
279 | children: [child],
280 | };
281 | return false;
282 | } else if (child.type === "NavPanel") {
283 | NavPanel = {
284 | ...rootChild,
285 | children: [child],
286 | };
287 | return false;
288 | }
289 | return true;
290 | });
291 | if (!transformedChild.children.length) {
292 | transformedChild = null;
293 | }
294 | }
295 | if (rootChild.type === "AppHeader") {
296 | AppHeader = rootChild;
297 | } else if (rootChild.type === "Footer") {
298 | Footer = rootChild;
299 | } else if (rootChild.type === "NavPanel") {
300 | NavPanel = rootChild;
301 | } else if (rootChild.type === "Pages") {
302 | Pages = rootChild;
303 | restChildren.push(transformedChild);
304 | } else if (transformedChild !== null) {
305 | restChildren.push(transformedChild);
306 | }
307 | });
308 |
309 | // --- Check if there is any extra NavPanel in Pages
310 | const extraNavs = extractNavPanelFromPages(
311 | Pages,
312 | NavPanel,
313 | processedNavRef,
314 | extractValue,
315 | parseHierarchyLabels,
316 | labelExistsInHierarchy,
317 | findOrCreateNavGroup,
318 | );
319 |
320 | // --- If we found extra navigation items
321 | if (extraNavs?.length) {
322 | if (NavPanel) {
323 | // --- Create a new NavPanel with combined children instead of mutating the existing one
324 | NavPanel = {
325 | ...NavPanel,
326 | children: NavPanel.children ? [...NavPanel.children, ...extraNavs] : extraNavs,
327 | };
328 | } else {
329 | // --- Create a new NavPanel component definition if none exists
330 | NavPanel = {
331 | type: "NavPanel",
332 | props: {},
333 | children: extraNavs,
334 | };
335 | }
336 | }
337 |
338 | return {
339 | AppHeader,
340 | Footer,
341 | NavPanel,
342 | Pages,
343 | restChildren,
344 | };
345 | }, [
346 | node.children,
347 | extractValue,
348 | parseHierarchyLabels,
349 | labelExistsInHierarchy,
350 | findOrCreateNavGroup,
351 | ]);
352 |
353 | const applyDefaultContentPadding= !Pages;
354 |
355 | // --- Memoize all app props to prevent unnecessary re-renders
356 | const appProps = useMemo(
357 | () => ({
358 | scrollWholePage: extractValue.asOptionalBoolean(node.props.scrollWholePage, true),
359 | noScrollbarGutters: extractValue.asOptionalBoolean(node.props.noScrollbarGutters, false),
360 | className,
361 | layout: layoutType,
362 | loggedInUser: extractValue(node.props.loggedInUser),
363 | onReady: lookupEventHandler("ready"),
364 | onMessageReceived: lookupEventHandler("messageReceived"),
365 | name: extractValue(node.props.name),
366 | logo: extractValue(node.props.logo),
367 | logoDark: extractValue(node.props["logo-dark"]),
368 | logoLight: extractValue(node.props["logo-light"]),
369 | defaultTone: extractValue(node.props.defaultTone),
370 | defaultTheme: extractValue(node.props.defaultTheme),
371 | autoDetectTone: extractValue.asOptionalBoolean(node.props.autoDetectTone, false),
372 | applyDefaultContentPadding
373 | }),
374 | [
375 | extractValue,
376 | layoutType,
377 | lookupEventHandler,
378 | node.props.loggedInUser,
379 | node.props.noScrollbarGutters,
380 | node.props.scrollWholePage,
381 | node.props.name,
382 | node.props.logo,
383 | node.props["logo-dark"],
384 | node.props["logo-light"],
385 | node.props.defaultTone,
386 | node.props.defaultTheme,
387 | node.props.autoDetectTone,
388 | className,
389 | applyDefaultContentPadding
390 | ],
391 | );
392 |
393 | // Memoize the rendered children to prevent unnecessary re-renders
394 | const renderedHeader = useMemo(() => renderChild(AppHeader), [AppHeader, renderChild]);
395 | const renderedFooter = useMemo(() => renderChild(Footer), [Footer, renderChild]);
396 | const renderedNavPanel = useMemo(() => renderChild(NavPanel), [NavPanel, renderChild]);
397 | const renderedContent = useMemo(() => renderChild(restChildren), [restChildren, renderChild]);
398 |
399 | return (
400 | <App
401 | {...appProps}
402 | header={renderedHeader}
403 | footer={renderedFooter}
404 | navPanel={renderedNavPanel}
405 | navPanelDef={NavPanel}
406 | logoContentDef={node.props.logoTemplate}
407 | renderChild={renderChild}
408 | registerComponentApi={registerComponentApi}
409 | >
410 | {renderedContent}
411 | <SearchIndexCollector Pages={Pages} renderChild={renderChild} />
412 | </App>
413 | );
414 | }
415 |
416 | const HIDDEN_STYLE: CSSProperties = {
417 | position: "absolute",
418 | top: "-9999px",
419 | display: "none",
420 | };
421 |
422 | const indexerContextValue = {
423 | indexing: true,
424 | };
425 |
426 | function SearchIndexCollector({ Pages, renderChild }) {
427 | const appContext = useAppContext();
428 | const setIndexing = useSearchContextSetIndexing();
429 |
430 | const [isClient, setIsClient] = useState(false);
431 | useEffect(() => {
432 | setIsClient(true); // Ensure document.body is available
433 |
434 | return () => {
435 | setIndexing(false);
436 | };
437 | }, [setIndexing]);
438 |
439 | // 1. Memoize the list of pages to be indexed
440 | const pagesToIndex = useMemo(() => {
441 | return (
442 | Pages?.children?.filter(
443 | (child) =>
444 | child.type === "Page" && // Ensure 'Page' matches your actual component type name
445 | child.props?.url && // Ensure URL exists
446 | !child.props.url.includes("*") &&
447 | !child.props.url.includes(":"),
448 | ) || []
449 | );
450 | }, [Pages?.children]);
451 |
452 | const [currentIndex, setCurrentIndex] = useState(0);
453 | const [isDoneIndexing, setIsDoneIndexing] = useState(false);
454 | const [, startTransitionParent] = useTransition(); // Transition for parent updates
455 |
456 | const handlePageIndexed = useCallback(() => {
457 | startTransitionParent(() => {
458 | // Transition the update to the next page
459 | setCurrentIndex((prevIndex) => {
460 | const nextIndex = prevIndex + 1;
461 | if (nextIndex >= pagesToIndex.length) {
462 | // console.log("All pages indexed.");
463 | setIsDoneIndexing(true); // All pages processed
464 | }
465 | return nextIndex;
466 | });
467 | });
468 | }, [pagesToIndex.length]); // Recreate if the total number of pages changes
469 |
470 | if (!appContext.appGlobals?.searchIndexEnabled || isDoneIndexing || !isClient) {
471 | return null;
472 | }
473 |
474 | const currentPageToProcess = pagesToIndex[currentIndex];
475 |
476 | if (!currentPageToProcess) {
477 | // This can happen if pagesToIndex is empty or currentIndex went out of bounds unexpectedly.
478 | // Setting isDoneIndexing if pagesToIndex is empty initially.
479 | if (pagesToIndex.length === 0 && currentIndex === 0 && !isDoneIndexing) {
480 | setIsDoneIndexing(true);
481 | }
482 | return null;
483 | }
484 |
485 | return (
486 | <IndexerContext.Provider value={indexerContextValue}>
487 | {createPortal(
488 | <div style={HIDDEN_STYLE} aria-hidden="true">
489 | {/* Render only one PageIndexer at a time */}
490 | <PageIndexer
491 | Page={currentPageToProcess}
492 | renderChild={renderChild}
493 | onIndexed={handlePageIndexed}
494 | key={currentPageToProcess.props?.url || currentIndex} // Key ensures re-mount
495 | />
496 | </div>,
497 | document.body,
498 | )}
499 | </IndexerContext.Provider>
500 | );
501 | }
502 |
503 | function PageIndexer({
504 | Page,
505 | renderChild,
506 | onIndexed,
507 | }: {
508 | Page: ComponentDef<typeof PageMd>; // Use the defined PageMdProps
509 | renderChild: RenderChildFn;
510 | onIndexed: () => void;
511 | }) {
512 | const contentRef = useRef<HTMLDivElement>(null);
513 | const pageUrl = Page.props?.url || "";
514 | const navLabel = Page.props?.navLabel || "";
515 | const searchContextUpdater = useSearchContextUpdater();
516 |
517 | const [isContentRendered, setIsContentRendered] = useState(false);
518 | const [isCollected, setIsCollected] = useState(false);
519 | const [isProcessing, startTransition] = useTransition();
520 |
521 | // Effect 1: Schedule the rendering of the Page's children (low priority)
522 | useEffect(() => {
523 | // console.log(`PageIndexer (${pageUrl}): Scheduling content render.`);
524 | startTransition(() => {
525 | setIsContentRendered(true); // This will trigger rendering of Page.children
526 | });
527 | }, [pageUrl]); // Re-run if the Page prop itself changes identity (due to key in parent)
528 |
529 | // Effect 2: Extract content once Page.children is rendered and ref is available (low priority)
530 | useEffect(() => {
531 | if (isContentRendered && contentRef.current && !isCollected && !isProcessing) {
532 | // console.log(`PageIndexer (${pageUrl}): Content rendered, scheduling extraction.`);
533 | startTransition(() => {
534 | // console.log(`PageIndexer (${pageUrl}): Starting extraction...`);
535 | const currentContent = contentRef.current; // Capture ref value
536 | if (!currentContent) return;
537 |
538 | const clone = currentContent.cloneNode(true) as HTMLDivElement;
539 | const elementsToRemove = clone.querySelectorAll("style, script");
540 | elementsToRemove.forEach((el) => el.remove());
541 | const titleElement = clone.querySelector("h1");
542 | const title = titleElement
543 | ? titleElement.innerText
544 | : navLabel || pageUrl.split("/").pop() || pageUrl;
545 | titleElement?.remove(); // Remove title element from clone to avoid duplication
546 | const textContent = (clone.textContent || "").trim().replace(/\s+/g, " ");
547 |
548 | const entry = {
549 | title: title,
550 | content: textContent,
551 | path: pageUrl,
552 | };
553 |
554 | searchContextUpdater(entry);
555 | // console.log(`PageIndexer (${pageUrl}): Extraction complete, signaling parent.`);
556 | onIndexed(); // Signal completion to parent
557 | setIsCollected(true); // Mark as collected
558 | });
559 | }
560 | }, [
561 | isContentRendered,
562 | pageUrl,
563 | searchContextUpdater,
564 | onIndexed,
565 | isCollected,
566 | isProcessing,
567 | navLabel,
568 | ]); // Ensure all dependencies are listed
569 |
570 | // If this PageIndexer instance's work is done, or content not yet rendered, render nothing.
571 | // The parent (SearchIndexCollector) will unmount this and mount the next one.
572 | if (isCollected || !isContentRendered) {
573 | // console.log(`PageIndexer (${pageUrl}): Null render (isCollected: ${isCollected}, isContentRendered: ${isContentRendered})`);
574 | return null;
575 | }
576 |
577 | // This part renders when isContentRendered is true and isCollected is false.
578 | // The content needs to be in the DOM for contentRef.current to be populated.
579 | // console.log(`PageIndexer (${pageUrl}): Rendering content for ref population.`);
580 | return <div ref={contentRef}>{renderChild(Page.children)}</div>;
581 | }
582 |
583 | export const appRenderer = createComponentRenderer(
584 | COMP,
585 | AppMd,
586 | ({ node, extractValue, renderChild, className, lookupEventHandler, registerComponentApi }) => {
587 | return (
588 | <AppNode
589 | node={node}
590 | renderChild={renderChild}
591 | extractValue={extractValue}
592 | className={className}
593 | lookupEventHandler={lookupEventHandler}
594 | registerComponentApi={registerComponentApi}
595 | />
596 | );
597 | },
598 | );
599 |
600 | // --- Process the entire navigation tree recursively and build hierarchy
601 | function processNavItems(
602 | items: ComponentDef[],
603 | parentHierarchy: NavHierarchyNode[],
604 | extractValue: (value: any) => any,
605 | ) {
606 | items.forEach((navItem) => {
607 | // --- Process NavLink items
608 | if (navItem.type === "NavLink") {
609 | let itemLabel = navItem.props?.label;
610 | let itemPath = navItem.props?.to;
611 |
612 | if (!itemLabel) {
613 | if (navItem.children?.length === 1 && navItem.children[0].type === "TextNode") {
614 | itemLabel = navItem.children[0].props.value;
615 | }
616 | }
617 |
618 | if (itemLabel) {
619 | const labelValue = extractValue(itemLabel);
620 |
621 | // --- Add to hierarchy
622 | parentHierarchy.push({
623 | type: "NavLink",
624 | label: labelValue,
625 | path: itemPath ? extractValue(itemPath) : undefined,
626 | });
627 | }
628 | }
629 | // --- Process NavGroup items (which may contain nested NavLink or NavGroup items)
630 | else if (navItem.type === "NavGroup") {
631 | let groupLabel = navItem.props?.label;
632 |
633 | if (groupLabel) {
634 | const labelValue = extractValue(groupLabel);
635 |
636 | // --- Create group node
637 | const groupNode: NavHierarchyNode = {
638 | type: "NavGroup",
639 | label: labelValue,
640 | children: [],
641 | };
642 |
643 | // --- Add to parent hierarchy
644 | parentHierarchy.push(groupNode);
645 |
646 | // --- Recursively process children of the NavGroup
647 | if (navItem.children && navItem.children.length > 0) {
648 | processNavItems(navItem.children, groupNode.children, extractValue);
649 | }
650 | } else if (navItem.children && navItem.children.length > 0) {
651 | // --- If no label but has children, still process them under parent
652 | processNavItems(navItem.children, parentHierarchy, extractValue);
653 | }
654 | }
655 | });
656 | }
657 |
658 | // --- Extract navigation panel items from Pages component
659 | function extractNavPanelFromPages(
660 | Pages: ComponentDef,
661 | NavPanel: ComponentDef | undefined,
662 | processedNavRef: React.MutableRefObject<boolean>,
663 | extractValue: (value: any) => any,
664 | parseHierarchyLabels: (labelText: string) => string[],
665 | labelExistsInHierarchy: (searchLabel: string, hierarchy: NavHierarchyNode[]) => boolean,
666 | findOrCreateNavGroup: (navItems: ComponentDef[], groupLabel: string) => ComponentDef,
667 | ): ComponentDef[] | null {
668 | // --- Skip extraction if we've already processed this navigation structure
669 | // --- This prevents duplicate items when React renders twice in strict mode
670 | if (!Pages || processedNavRef.current) return null;
671 |
672 | // --- Mark as processed
673 | processedNavRef.current = true;
674 |
675 | const extraNavs: ComponentDef[] = [];
676 |
677 | // --- Root of navigation hierarchy
678 | const navigationHierarchy: NavHierarchyNode[] = [];
679 |
680 | // --- Start processing the navigation tree if NavPanel exists
681 | if (NavPanel?.children) {
682 | processNavItems(NavPanel.children, navigationHierarchy, extractValue);
683 | }
684 |
685 | // --- Process Pages to create hierarchical navigation structure
686 | Pages.children?.forEach((page) => {
687 | if (page.type === "Page" && page.props.navLabel) {
688 | const label = extractValue(page.props.navLabel);
689 | const url = extractValue(page.props.url);
690 |
691 | // --- Parse hierarchy labels separated by unescaped pipe characters
692 | const hierarchyLabels = parseHierarchyLabels(label);
693 |
694 | // --- Skip if we have no labels
695 | if (hierarchyLabels.length === 0) {
696 | return;
697 | }
698 |
699 | // --- For a single level, just add a NavLink directly
700 | if (hierarchyLabels.length === 1) {
701 | // --- Check if this exact NavLink already exists in the navigation hierarchy
702 | if (!labelExistsInHierarchy(hierarchyLabels[0], navigationHierarchy)) {
703 | extraNavs.push({
704 | type: "NavLink",
705 | props: {
706 | label: hierarchyLabels[0],
707 | to: url,
708 | },
709 | });
710 | }
711 | return;
712 | }
713 |
714 | // --- For multi-level hierarchies, create NavGroups and a final NavLink
715 | let currentLevel = extraNavs;
716 |
717 | // --- Create NavGroups for all levels except the last one
718 | for (let i = 0; i < hierarchyLabels.length - 1; i++) {
719 | const groupLabel = hierarchyLabels[i];
720 | const navGroup = findOrCreateNavGroup(currentLevel, groupLabel);
721 |
722 | // --- Initialize children array if it doesn't exist
723 | if (!navGroup.children) {
724 | navGroup.children = [];
725 | }
726 |
727 | // --- Move to the next level
728 | currentLevel = navGroup.children;
729 | }
730 |
731 | // --- Add the leaf NavLink to the deepest NavGroup
732 | const leafLabel = hierarchyLabels[hierarchyLabels.length - 1];
733 |
734 | // --- Check if this NavLink already exists at this level
735 | const existingNavLink = currentLevel.find(
736 | (item) => item.type === "NavLink" && item.props?.label === leafLabel,
737 | );
738 |
739 | if (!existingNavLink) {
740 | currentLevel.push({
741 | type: "NavLink",
742 | props: {
743 | label: leafLabel,
744 | to: url,
745 | },
746 | });
747 | }
748 | }
749 | });
750 |
751 | return extraNavs;
752 | }
753 |
```
--------------------------------------------------------------------------------
/docs/public/pages/build-hello-world-component.md:
--------------------------------------------------------------------------------
```markdown
1 | # Build a Hello World component
2 |
3 | In this tutorial we'll build a HelloWorld component that demonstrates the core patterns for XMLUI component development.
4 |
5 | The XMLUI framework supports two types of components:
6 |
7 | **Core components** are built into the main XMLUI library and available by default. Components like Button, Text, Card, and Stack are always available in any XMLUI app.
8 |
9 | **Extension packages** are standalone components that can be optionally included. They are built, distributed, and imported separately, making them perfect for custom components that aren't needed by every XMLUI application.
10 |
11 | We'll build an extension package so the HelloWorld component can:
12 |
13 | - Live separately from the core XMLUI library
14 | - Be optionally included in standalone apps
15 | - Be distributed and reused across different projects
16 |
17 | Extensions are the recommended approach for custom components. By the end of this guide, you'll have created a HelloWorld component that:
18 |
19 | - Displays a customizable greeting message
20 | - Features an interactive click counter
21 | - Uses XMLUI's standard theming system
22 | - Defines event handlers
23 | - Provides callable methods
24 |
25 | ## XMLUI component architecture
26 |
27 | XMLUI components are made of three main parts:
28 |
29 | 1. Native React component (`HelloWorldNative.tsx`) - The actual React implementation
30 | 2. Component metadata (`HelloWorld.tsx`) - Describes props and integrates with XMLUI
31 | 3. For visual components, a .scss file (`HelloWorld.module.scss`)
32 |
33 | This separation allows XMLUI to understand your component's interface while maintaining clean React code.
34 |
35 | ## Prerequisites
36 |
37 | - Familiarity with React and TypeScript
38 | - Basic understanding of XMLUI markup
39 | - Node.js 18.0.0 or higher
40 | - npm (comes with Node.js)
41 |
42 | ## Step 1: Create your project directory
43 |
44 | Let's start by creating a new directory for your HelloWorld component project.
45 |
46 | **Windows**
47 |
48 | ```xmlui copy
49 | mkdir xmlui-hello-world
50 | cd xmlui-hello-world
51 | ```
52 |
53 | **Mac / WSL / Linux**
54 |
55 | ```xmlui copy
56 | mkdir xmlui-hello-world
57 | cd xmlui-hello-world
58 | ```
59 |
60 | This creates a fresh project directory where you'll build your component from scratch.
61 |
62 | > [!INFO]
63 | > This page includes playground examples that use the HelloWorld component. They are available here because this site loads the final extension package that you will build. That means the live playground examples here reflect the final state, not the interim states described as we go along. But in the standalone app that you'll create you will see the progression exactly as described here.
64 |
65 | ## Step 2: Create the package configuration
66 |
67 | First, let's initialize a new npm project and install the xmlui package:
68 |
69 | ```xmlui copy
70 | npm init -y
71 | npm install --save-dev xmlui
72 | ```
73 |
74 | This creates a basic `package.json` and installs the xmlui package as a development dependency.
75 |
76 | Now let's update the `package.json` with the proper configuration for our extension:
77 |
78 | ```xmlui copy
79 | {
80 | "name": "xmlui-hello-world",
81 | "version": "0.1.0",
82 | "type": "module",
83 | "scripts": {
84 | "build:extension": "xmlui build-lib"
85 | },
86 | "devDependencies": {
87 | "xmlui": "*"
88 | },
89 | "main": "./dist/xmlui-hello-world.js",
90 | "module": "./dist/xmlui-hello-world.mjs",
91 | "exports": {
92 | ".": {
93 | "import": "./dist/xmlui-hello-world.mjs",
94 | "require": "./dist/xmlui-hello-world.js"
95 | }
96 | },
97 | "files": [
98 | "dist"
99 | ]
100 | }
101 | ```
102 |
103 | The build system will generate both:
104 |
105 | - xmlui-hello-world.js (CommonJS/UMD for browser script tags)
106 | - xmlui-hello-world.mjs (ES modules for import statements)
107 |
108 | `xmlui-hello-world.js` is the file you'll pull into a standalone XMLUI app using a `<script>` tag.
109 |
110 | ## Step 3: Create the React component
111 |
112 | First, let's create the `src` directory for our component files:
113 |
114 | ```xmlui copy
115 | mkdir src
116 | ```
117 |
118 | Create `src/HelloWorldNative.tsx` with the core React implementation.
119 |
120 | ```xmlui copy
121 | import React, { useState } from "react";
122 | import styles from "./HelloWorld.module.scss";
123 |
124 | type Props = {
125 | id?: string;
126 | message?: string;
127 | };
128 |
129 | export const defaultProps = {
130 | message: "Hello, World!",
131 | };
132 |
133 | export function HelloWorld({
134 | id,
135 | message = defaultProps.message,
136 | }: Props) {
137 | const [clickCount, setClickCount] = useState(0);
138 |
139 | const handleClick = () => {
140 | setClickCount(clickCount + 1);
141 | };
142 |
143 | return (
144 | <div className={styles.container} id={id}>
145 | <h2 className={styles.message}>{message}</h2>
146 | <button className={styles.button} onClick={handleClick}>
147 | Click me!
148 | </button>
149 | <div className={styles.counter}>Clicks: {clickCount}</div>
150 | </div>
151 | );
152 | }
153 | ```
154 |
155 | This creates the core React component with:
156 |
157 | - Essential props (id, message)
158 | - Internal click counter
159 |
160 | ## Step 4: Create basic styles
161 |
162 | ```xmlui copy
163 | .container {
164 | background-color: #f5f5f5;
165 | color: #333;
166 | padding: 1rem;
167 | border-radius: 8px;
168 | text-align: center;
169 | display: inline-block;
170 | min-width: 200px;
171 | }
172 |
173 | .message {
174 | margin: 0 0 1rem 0;
175 | font-size: 1.5rem;
176 | }
177 |
178 | .button {
179 | background-color: #4a90e2;
180 | color: white;
181 | border: none;
182 | padding: 0.75rem 1.5rem;
183 | border-radius: 4px;
184 | cursor: pointer;
185 | font-size: 1rem;
186 | margin-bottom: 1rem;
187 |
188 | &:hover {
189 | opacity: 0.9;
190 | }
191 | }
192 |
193 | .counter {
194 | font-size: 1.2rem;
195 | font-weight: bold;
196 | }
197 | ```
198 |
199 | This SCSS module defines the basic visual styling for our HelloWorld component:
200 | - `.container` - Main wrapper with background, padding, and layout
201 | - `.message` - Styling for the greeting text
202 | - `.button` - Interactive button with hover effects
203 | - `.counter` - Display for the click count
204 |
205 | At this stage, we use hardcoded colors. In Step 9, we'll replace these theme variables.
206 |
207 |
208 | ## Step 5: Create component metadata and renderer
209 |
210 | Create `HelloWorld.tsx`.
211 |
212 | ```xmlui copy
213 | import styles from "./HelloWorld.module.scss";
214 | import { createComponentRenderer, createMetadata } from "xmlui";
215 | import { HelloWorld, defaultProps } from "./HelloWorldNative";
216 |
217 | const HelloWorldMd = createMetadata({
218 | description: "`HelloWorld` is a demonstration component.",
219 | status: "experimental",
220 | props: {
221 | message: {
222 | description: "The message to display.",
223 | isRequired: false,
224 | type: "string",
225 | defaultValue: defaultProps.message,
226 | },
227 | },
228 | });
229 |
230 | export const helloWorldComponentRenderer = createComponentRenderer(
231 | "HelloWorld",
232 | HelloWorldMd,
233 | ({ node, extractValue }) => {
234 | return (
235 | <HelloWorld
236 | id={extractValue.asOptionalString(node.props?.id)}
237 | message={extractValue.asOptionalString(node.props?.message)}
238 | />
239 | );
240 | }
241 | );
242 | ```
243 |
244 | **What we're creating**
245 |
246 | This file bridges the gap between XMLUI markup and React components.
247 |
248 | - Metadata (`HelloWorldMd`) - Documents the component's props, behavior, and usage
249 | - Renderer (`helloWorldComponentRenderer`) - Converts XMLUI markup to React component calls
250 |
251 | **The renderer pattern**
252 |
253 | The renderer function receives XMLUI context (node, extractValue, etc.) and returns a React component.
254 |
255 | It:
256 |
257 | - Extracts prop values from XMLUI markup using `extractValue.asOptionalString()`
258 | - Passes them to the native React component
259 | - Handles optional props gracefully (undefined becomes default values)
260 |
261 |
262 | This pattern enables XMLUI to:
263 |
264 | - Validate markup against metadata
265 | - Provide IntelliSense and documentation
266 | - Handle prop type conversion automatically
267 | - Support XMLUI-specific features like theming (step 9) and event handling (Step 10)
268 |
269 | ## Step 6: Create the extension index
270 |
271 | Create `src/index.tsx` which exports your component as an extension.
272 |
273 | ```xmlui copy
274 | cat > src/index.tsx << 'EOF'
275 | import { helloWorldComponentRenderer } from "./HelloWorld";
276 |
277 | export default {
278 | namespace: "XMLUIExtensions",
279 | components: [helloWorldComponentRenderer],
280 | };
281 | EOF
282 | ```
283 |
284 | This creates the main entry point that exports your HelloWorld component under the XMLUIExtensions namespace.
285 |
286 | ## Step 7: Build the extension
287 |
288 |
289 | ```xmlui copy
290 | npm run build:extension
291 | ```
292 |
293 | This creates `xmlui-hello-world.js` in the `dist` folder.
294 |
295 | ```xmlui-pg noHeader
296 | ---app
297 | <TreeDisplay content="
298 | packages/xmlui-hello-world
299 | package.json
300 | src
301 | index.tsx
302 | HelloWorld.tsx
303 | HelloWorldNative.tsx
304 | HelloWorld.module.scss
305 | dist
306 | xmlui-hello-world.js
307 | " />
308 | ```
309 |
310 | ## Step 8: Test the extension
311 |
312 | Since we've integrated it into the docs site, you can see it live right here.
313 |
314 | ```xmlui-pg
315 | ---app display
316 | <App>
317 | <VStack gap="2rem" padding="2rem">
318 | <H1>HelloWorld Component Live Demo</H1>
319 |
320 | <Card>
321 | <HelloWorld message="Hello from the docs site!" />
322 | </Card>
323 |
324 | </VStack>
325 |
326 | <script>
327 | // Event handlers for the HelloWorld component
328 | window.addEventListener('helloWorldClick', (event) => {
329 | console.log('HelloWorld clicked!', event.detail);
330 | });
331 | </script>
332 | </App>
333 | ```
334 |
335 | But you will want to see it in a standalone app. Let's create a simple test app to verify our component works.
336 |
337 | First, create a test directory and an `xmlui` subdirectory within it:
338 |
339 | ```xmlui copy
340 | mkdir test-app
341 | cd test-app
342 | mkdir xmlui
343 | ```
344 |
345 | Now, copy your built component into the `xmlui` subdirectory:
346 |
347 | ```xmlui copy
348 | cp ../dist/xmlui-hello-world.js xmlui/xmlui-hello-world.js
349 | ```
350 |
351 | Create the `Main.xmlui` file with your component's markup:
352 |
353 | ```xmlui copy
354 | <App>
355 | <VStack gap="2rem" padding="2rem">
356 | <Heading>HelloWorld Component Test</Heading>
357 | <HelloWorld message="Hello from standalone app!" />
358 | </VStack>
359 |
360 | <script>
361 | // Event handlers for the HelloWorld component
362 | window.addEventListener('helloWorldClick', (event) => {
363 | console.log('HelloWorld clicked!', event.detail);
364 | });
365 | </script>
366 | </App>
367 | ```
368 |
369 | Finally, create a simple `index.html` file to load the XMLUI engine from CDN and your component:
370 |
371 | ```xmlui copy
372 | <!DOCTYPE html>
373 | <html lang="en">
374 | <head>
375 | <meta charset="UTF-8" />
376 | <meta name="viewport" content="width=device-width, initial-scale=1.0" />
377 | <title>HelloWorld Extension Test</title>
378 | <script src="https://unpkg.com/xmlui@latest/dist/standalone/xmlui-standalone.umd.js"></script>
379 | <script src="xmlui/xmlui-hello-world.js"></script>
380 | </head>
381 | <body>
382 | </body>
383 | </html>
384 | ```
385 |
386 | This creates a simple test app that loads your component.
387 |
388 | To run the app with Python:
389 |
390 | ```xmlui copy
391 | python -m http.server # visit 8000
392 | ```
393 |
394 | With Node.js:
395 |
396 | ```xmlui copy
397 | npx server # visit 3000
398 | ```
399 |
400 | ## Step 9: Add theming support
401 |
402 | So far, our HelloWorld component uses hardcoded colors. Let's integrate it with XMLUI's theming system to make it more flexible and consistent with the rest of the UI.
403 |
404 | **Understanding XMLUI's theme system**
405 |
406 | XMLUI provides a sophisticated theming system that:
407 | - Uses semantic design tokens (like `$color-surface-50`, `$color-content-primary`)
408 | - Automatically supports light and dark modes
409 | - Maintains consistency across all components
410 | - Allows runtime customization via the `<Theme>` component
411 |
412 | **Adding theme variables**
413 |
414 | Let's update our SCSS to use XMLUI's theme system:
415 |
416 | ```xmlui copy
417 | @use "xmlui/themes.scss" as t;
418 |
419 | $themeVars: ();
420 | @function createThemeVar($componentVariable) {
421 | $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global;
422 | @return t.getThemeVar($themeVars, $componentVariable);
423 | }
424 |
425 | $component: "HelloWorld";
426 |
427 | // Define theme variables for our component
428 | $backgroundColor: createThemeVar("backgroundColor-#{$component}");
429 | $textColor: createThemeVar("textColor-#{$component}");
430 |
431 | .container {
432 | background-color: $backgroundColor;
433 | color: $textColor;
434 | padding: 1rem;
435 | border-radius: 8px;
436 | text-align: center;
437 | display: inline-block;
438 | min-width: 200px;
439 | }
440 |
441 | .message {
442 | margin: 0 0 1rem 0;
443 | font-size: 1.5rem;
444 | }
445 |
446 | .button {
447 | background-color: #4a90e2;
448 | color: white;
449 | border: none;
450 | padding: 0.75rem 1.5rem;
451 | border-radius: 4px;
452 | cursor: pointer;
453 | font-size: 1rem;
454 | margin-bottom: 1rem;
455 |
456 | &:hover {
457 | opacity: 0.9;
458 | }
459 | }
460 |
461 | .counter {
462 | font-size: 1.2rem;
463 | font-weight: bold;
464 | }
465 |
466 | :export {
467 | themeVars: t.json-stringify($themeVars);
468 | }
469 | ```
470 |
471 | **What changed**
472 |
473 | Instead of hardcoded colors like `#f5f5f5` and `#333`, we now use:
474 | - `$backgroundColor` - Uses XMLUI's surface color tokens
475 | - `$textColor` - Uses XMLUI's content color tokens
476 |
477 | The `createThemeVar()` function registers these variables with XMLUI, making them available for customization via the `<Theme>` component and automatic light/dark mode adaptation.
478 |
479 | The `:export { themeVars: t.json-stringify($themeVars); }` exports the theme variables so XMLUI can read them.
480 |
481 | **Update component metadata**
482 |
483 | We also need to tell XMLUI about our theme variables. Update the metadata in `HelloWorld.tsx`:
484 |
485 | ```xmlui copy
486 | import styles from "./HelloWorld.module.scss";
487 | import { createComponentRenderer, parseScssVar, createMetadata } from "xmlui";
488 | import { HelloWorld, defaultProps } from "./HelloWorldNative";
489 |
490 | const HelloWorldMd = createMetadata({
491 | description: "`HelloWorld` is a demonstration component.",
492 | status: "experimental",
493 | props: {
494 | message: {
495 | description: "The message to display.",
496 | isRequired: false,
497 | type: "string",
498 | defaultValue: defaultProps.message,
499 | },
500 | },
501 | themeVars: parseScssVar(styles.themeVars),
502 | defaultThemeVars: {
503 | [`backgroundColor-HelloWorld`]: "$color-surface-50",
504 | [`textColor-HelloWorld`]: "$color-content-primary",
505 | dark: {
506 | [`backgroundColor-HelloWorld`]: "$color-surface-800",
507 | // No textColor override needed - $color-content-primary should auto-adapt
508 | },
509 | },
510 | });
511 |
512 | export const helloWorldComponentRenderer = createComponentRenderer(
513 | "HelloWorld",
514 | HelloWorldMd,
515 | ({ node, extractValue }) => {
516 | return (
517 | <HelloWorld
518 | id={extractValue.asOptionalString(node.props?.id)}
519 | message={extractValue.asOptionalString(node.props?.message)}
520 | />
521 | );
522 | }
523 | );
524 | ```
525 |
526 | **Rebuild and test**
527 |
528 | ```xmlui copy
529 | npm run build:extension
530 | ```
531 |
532 | Now your component uses XMLUI's theme system! It will automatically adapt to light/dark modes and can be customized using the `<Theme>` component.
533 |
534 | **Test the themed component**
535 |
536 | Copy the new `xmlui-hello-world.js` into your standalone app's `xmlui` folder, and update its `Main.xmlui`.
537 |
538 | ```xmlui-pg
539 | ---app display copy
540 | <App>
541 | <VStack gap="2rem" padding="2rem">
542 | <H1>HelloWorld with Theme Variables</H1>
543 |
544 | <HelloWorld message="Default styling" />
545 |
546 | <Card>
547 | <H2>Custom Colors</H2>
548 | <Theme
549 | backgroundColor-HelloWorld="$color-warn-300"
550 | textColor-HelloWorld="$textColor-primary"
551 | >
552 | <HelloWorld message="Custom colors!" />
553 | </Theme>
554 | </Card>
555 |
556 | <ToneSwitch />
557 | </VStack>
558 | </App>
559 | ```
560 |
561 | Notice how the component now uses theme variables instead of hardcoded colors. The `<Theme>` component allows you to override any theme variable at runtime, making your components incredibly flexible for different contexts and user preferences.
562 |
563 | ## Step 10: Add event handling
564 |
565 | The HelloWorld component has a click handler that increments a counter, and a reset that sets the count to zero. Let's add event definitions to signal parent components when these events happen.
566 |
567 | **Add event definitions**
568 |
569 | Update the component metadata in `src/HelloWorld.tsx`:
570 |
571 | ```xmlui copy
572 | import styles from "./HelloWorld.module.scss";
573 | import { createComponentRenderer, parseScssVar, createMetadata } from "xmlui";
574 | import { HelloWorld, defaultProps } from "./HelloWorldNative";
575 |
576 | const HelloWorldMd = createMetadata({
577 | description: "`HelloWorld` is a demonstration component.",
578 | status: "experimental",
579 | props: {
580 | message: {
581 | description: "The message to display.",
582 | isRequired: false,
583 | type: "string",
584 | defaultValue: defaultProps.message,
585 | },
586 | },
587 | events: {
588 | onClick: {
589 | description:
590 | "Triggered when the click button is pressed. " + "Receives the current click count.",
591 | type: "function",
592 | },
593 | onReset: {
594 | description:
595 | "Triggered when the reset button is pressed. " + "Called when count is reset to 0.",
596 | type: "function",
597 | },
598 | },
599 | themeVars: parseScssVar(styles.themeVars),
600 | defaultThemeVars: {
601 | [`backgroundColor-HelloWorld`]: "$color-surface-50",
602 | [`textColor-HelloWorld`]: "$color-content-primary",
603 | dark: {
604 | [`backgroundColor-HelloWorld`]: "$color-surface-800",
605 | // No textColor override needed - $color-content-primary should auto-adapt
606 | },
607 | },
608 | });
609 |
610 | export const helloWorldComponentRenderer = createComponentRenderer(
611 | "HelloWorld",
612 | HelloWorldMd,
613 |
614 | ({ node, extractValue, lookupEventHandler, className }) => {
615 | return (
616 | <HelloWorld
617 | id={extractValue.asOptionalString(node.props?.id)}
618 | message={extractValue.asOptionalString(node.props?.message)}
619 | onClick={lookupEventHandler("onClick")}
620 | onReset={lookupEventHandler("onReset")}
621 | className={className}
622 | />
623 | );
624 | },
625 | );
626 | ```
627 |
628 | **New props**
629 | - `onClick?: (event: React.MouseEvent) => void` - Called when the click button is pressed
630 | - `onReset?: (event: React.MouseEvent) => void` - Called when the reset button is pressed
631 |
632 | **Event handler changes:**
633 | - `handleClick` now calls `onClick?.(event)` after updating internal state
634 | - `handleReset` now calls `onReset?.(event)` after resetting the counter
635 | - Both pass the DOM event object (not custom data) to match XMLUI's event system
636 |
637 | **Update the native component**
638 |
639 | Update `src/HelloWorldNative.tsx` to accept and call the event handler.
640 |
641 | ```xmlui copy
642 | import React, { useState } from "react";
643 | import styles from "./HelloWorld.module.scss";
644 |
645 | type Props = {
646 | id?: string;
647 | message?: string;
648 | className?: string;
649 | onClick?: (event: React.MouseEvent) => void;
650 | onReset?: (event: React.MouseEvent) => void;
651 | };
652 |
653 | export const defaultProps = {
654 | message: "Hello, World!",
655 | };
656 |
657 | export const HelloWorld = React.forwardRef<HTMLDivElement, Props>(
658 | function HelloWorld(
659 | {
660 | id,
661 | message = defaultProps.message,
662 | className,
663 | onClick,
664 | onReset
665 | },
666 | ref
667 | ) {
668 | const [clickCount, setClickCount] = useState(0);
669 |
670 | const handleClick = (event: React.MouseEvent) => {
671 | const newCount = clickCount + 1;
672 | setClickCount(newCount);
673 | onClick?.(event);
674 | };
675 |
676 | const handleReset = (event: React.MouseEvent) => {
677 | setClickCount(0);
678 | onReset?.(event);
679 | };
680 |
681 | return (
682 | <div className={`${styles.container} ${className || ''}`} id={id}>
683 | <h2 className={styles.message}>{message}</h2>
684 | <button
685 | className={styles.button}
686 | onClick={handleClick}
687 | >
688 | Click me!
689 | </button>
690 | <div className={styles.counter}>
691 | Clicks: <span className={styles.count}>{clickCount}</span>
692 | </div>
693 |
694 | {clickCount > 0 && (
695 | <button
696 | className={styles.button}
697 | onClick={handleReset}
698 | >
699 | Reset
700 | </button>
701 | )}
702 | </div>
703 | );
704 | }
705 | );
706 | ```
707 |
708 | **Metadata changes:**
709 | - Added `events` section defining `onClick` and `onReset` event handlers
710 | - Each event includes description and type information for documentation
711 |
712 | **Renderer changes:**
713 | - Added `lookupEventHandler` to the renderer context
714 | - `lookupEventHandler("onClick")` and `lookupEventHandler("onReset")` convert XMLUI event bindings to function references
715 | - These function references are passed to the native React component
716 |
717 | **The event flow:**
718 | 1. XMLUI markup: `<HelloWorld onClick="handleHelloClick" />`
719 | 2. Renderer: `lookupEventHandler("onClick")` finds the `handleHelloClick` function
720 | 3. Native component: Receives the function as `onClick` prop
721 | 4. User interaction: Triggers the function with the DOM event
722 |
723 | **Rebuild the extension**
724 |
725 | ```xmlui copy
726 | npm run build:extension
727 | ```
728 |
729 | **Define the handlers**
730 |
731 | This site's `index.html` defines these handler functions. For your standalone app you'll need to add them into its `index.html`.
732 |
733 | ```xmlui copy
734 | <script>
735 | window.handleHelloClick = function(event) {
736 | console.log('Hello World clicked!', event);
737 | alert('Button clicked!');
738 | };
739 |
740 | window.handleHelloReset = function(event) {
741 | console.log('Hello World reset!', event);
742 | alert('Counter was reset!');
743 | };
744 | </script>
745 | ```
746 |
747 | **Test event handling**
748 |
749 | Copy the new `xmlui-hello-world.js` into your standalone app's `xmlui` folder, and update its `Main.xmlui`.
750 |
751 | Now you can use the component with event handling.
752 |
753 | ```xmlui-pg
754 | ---app display copy
755 | <App>
756 | <HelloWorld
757 | onClick="handleHelloClick"
758 | onReset="handleHelloReset"
759 | />
760 | </App>
761 | ```
762 |
763 | ## Step 11: Add component APIs (external methods)
764 |
765 | Update `src/HelloWorldNative.tsx`.
766 |
767 |
768 | ```xmlui copy
769 | import React, { useState, useEffect } from "react";
770 | import styles from "./HelloWorld.module.scss";
771 | import type { RegisterComponentApiFn } from "xmlui";
772 |
773 | type Props = {
774 | id?: string;
775 | message?: string;
776 | className?: string;
777 | onClick?: (event: React.MouseEvent) => void;
778 | onReset?: (event: React.MouseEvent) => void;
779 | registerComponentApi?: RegisterComponentApiFn;
780 | };
781 |
782 | export const defaultProps = {
783 | message: "Hello, World!",
784 | };
785 |
786 | export const HelloWorld = React.forwardRef<HTMLDivElement, Props>(
787 | function HelloWorld(
788 | {
789 | id,
790 | message = defaultProps.message,
791 | className,
792 | onClick,
793 | onReset,
794 | registerComponentApi
795 | },
796 | ref
797 | ) {
798 | const [clickCount, setClickCount] = useState(0);
799 |
800 | // Create setValue method for external API access
801 | const setValue = (newCount: number) => {
802 | setClickCount(newCount);
803 | };
804 |
805 | // Register component API
806 | useEffect(() => {
807 | registerComponentApi?.({
808 | setValue,
809 | value: clickCount,
810 | });
811 | }, [registerComponentApi, setValue, clickCount]);
812 |
813 | const handleClick = (event: React.MouseEvent) => {
814 | const newCount = clickCount + 1;
815 | setClickCount(newCount);
816 | onClick?.(event);
817 | };
818 |
819 | const handleReset = (event: React.MouseEvent) => {
820 | setClickCount(0);
821 | onReset?.(event);
822 | };
823 |
824 | return (
825 | <div className={`${styles.container} ${className || ''}`} id={id} ref={ref}>
826 | <h2 className={styles.message}>{message}</h2>
827 | <button
828 | className={styles.button}
829 | onClick={handleClick}
830 | >
831 | Click me!
832 | </button>
833 | <div className={styles.counter}>
834 | Clicks: <span className={styles.count}>{clickCount}</span>
835 | </div>
836 |
837 | {clickCount > 0 && (
838 | <button
839 | className={styles.button}
840 | onClick={handleReset}
841 | >
842 | Reset
843 | </button>
844 | )}
845 | </div>
846 | );
847 | }
848 | );
849 | ```
850 |
851 | **New props**
852 | - `registerComponentApi?: RegisterComponentApiFn` - Function to register component APIs with XMLUI
853 |
854 | **New imports:**
855 | - `useEffect` from React - For API registration and state synchronization
856 | - `RegisterComponentApiFn` type from "xmlui" - Type for the API registration function
857 |
858 | **API registration:**
859 | - `setValue` method - Allows external code to set the click count
860 | - `useEffect` hook registers the API with XMLUI, exposing both `setValue` and `value`
861 | - API updates whenever `clickCount` changes, ensuring `value` is always current
862 |
863 | This enables XMLUI markup to directly call `demo.setValue(5)` and read `demo.value`.
864 |
865 | Update `src/HelloWorldNative.tsx`.
866 |
867 | ```xmlui copy
868 | cat > src/HelloWorld.tsx << 'EOF'
869 | import styles from "./HelloWorld.module.scss";
870 | import { createComponentRenderer, parseScssVar, createMetadata } from "xmlui";
871 | import { HelloWorld, defaultProps } from "./HelloWorldNative";
872 |
873 | const HelloWorldMd = createMetadata({
874 | description:
875 | "`HelloWorld` is a demonstration component that shows basic XMLUI patterns.",
876 | status: "experimental",
877 | props: {
878 | message: {
879 | description: "The greeting message to display.",
880 | isRequired: false,
881 | type: "string",
882 | defaultValue: defaultProps.message,
883 | },
884 | },
885 | events: {
886 | onClick: {
887 | description:
888 | "Triggered when the click button is pressed. " + "Receives the current click count.",
889 | type: "function",
890 | },
891 | onReset: {
892 | description:
893 | "Triggered when the reset button is pressed. " + "Called when count is reset to 0.",
894 | type: "function",
895 | },
896 | },
897 | apis: {
898 | value: {
899 | description: "The current click count value.",
900 | type: "number",
901 | },
902 | setValue: {
903 | description: "Set the click count to a specific value.",
904 | type: "function",
905 | },
906 | },
907 | themeVars: parseScssVar(styles.themeVars),
908 | defaultThemeVars: {
909 | [`backgroundColor-HelloWorld`]: "$color-surface-50",
910 | [`textColor-HelloWorld`]: "$color-content-primary",
911 | dark: {
912 | [`backgroundColor-HelloWorld`]: "$color-surface-800",
913 | // No textColor override needed - $color-content-primary should auto-adapt
914 | },
915 | },
916 | });
917 |
918 | export const helloWorldComponentRenderer = createComponentRenderer(
919 | "HelloWorld",
920 | HelloWorldMd,
921 |
922 | ({ node, extractValue, lookupEventHandler, className, registerComponentApi }) => {
923 | return (
924 | <HelloWorld
925 | id={extractValue.asOptionalString(node.props?.id)}
926 | message={extractValue.asOptionalString(node.props?.message)}
927 | onClick={lookupEventHandler("onClick")}
928 | onReset={lookupEventHandler("onReset")}
929 | className={className}
930 | registerComponentApi={registerComponentApi}
931 | />
932 | );
933 | },
934 | );
935 | EOF
936 | ```
937 |
938 | **Metadata**
939 | - Added `apis` section defining `value` (number) and `setValue` (function) APIs
940 |
941 | **Renderer Changes**
942 | - Added `registerComponentApi` to the renderer context
943 | - Passes `registerComponentApi` to the native component for API registration
944 |
945 | **The API flow:**
946 | 1. XMLUI markup: `<HelloWorld id="demo" />` creates component with ID
947 | 2. Renderer: Registers component APIs via `registerComponentApi`
948 | 3. External access: `demo.setValue(5)` calls the component's setValue method
949 | 4. State reading: `demo.value` returns the current click count
950 |
951 | ```xmlui copy
952 | npm run build:extension
953 | ```
954 |
955 | Copy the new `xmlui-hello-world.js` into your standalone app's `xmlui` folder, and update its `Main.xmlui` to see this final version.
956 |
957 | ```xmlui-pg
958 | ---app display copy
959 | <App xmlns:Extensions="component-ns:XMLUIExtensions">
960 |
961 | <Extensions:HelloWorld id="demo" message="API Demo" />
962 |
963 | <CHStack>
964 | <Button onClick="{ console.log('demo.value', demo.value) }">Get Count</Button>
965 | <Button onClick="{ demo.setValue(5) }">Set to 5</Button>
966 | <Button onClick="{ demo.setValue(0) }">Reset</Button>
967 | </CHStack>
968 |
969 | </App>
970 | ```
```