This is page 36 of 141. Use http://codebase.md/xmlui-org/xmlui/mockApiDef.js?lines=false&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/Select/Select.tsx:
--------------------------------------------------------------------------------
```typescript
import styles from "./Select.module.scss";
import { createComponentRenderer } from "../../components-core/renderers";
import { parseScssVar } from "../../components-core/theming/themeVars";
import {
dPlaceholder,
dInitialValue,
dAutoFocus,
dRequired,
dReadonly,
dEnabled,
dValidationStatus,
dDidChange,
dGotFocus,
dLostFocus,
dMulti,
dComponent,
createMetadata,
d,
} from "../metadata-helpers";
import { MemoizedItem } from "../container-helpers";
import { Select, defaultProps } from "./SelectNative";
const COMP = "Select";
export const SelectMd = createMetadata({
status: "stable",
description:
"`Select` provides a dropdown interface for choosing from a list of options, " +
"supporting both single and multiple selection modes. It offers extensive " +
"customization capabilities including search functionality, custom templates, " +
"and comprehensive form integration.",
props: {
placeholder: {
...dPlaceholder(),
defaultValue: defaultProps.placeholder,
},
initialValue: dInitialValue(),
value: {
description: "This property sets the current value of the component.",
isInternal: true, //TODO illesg temp
},
autoFocus: {
...dAutoFocus(),
defaultValue: defaultProps.autoFocus,
},
required: {
...dRequired(),
defaultValue: defaultProps.required,
},
readOnly: {
...dReadonly(),
defaultValue: defaultProps.readOnly,
},
enabled: {
...dEnabled(),
defaultValue: defaultProps.enabled,
},
validationStatus: {
...dValidationStatus(),
defaultValue: defaultProps.validationStatus,
},
optionLabelTemplate: dComponent(
`This property allows replacing the default template to display an option in the dropdown list.`,
),
optionTemplate: dComponent(
`This property allows replacing the default template to display an option in the dropdown list.`,
),
valueTemplate: dComponent(
`This property allows replacing the default template to display a selected value when ` +
`multiple selections (\`multiSelect\` is \`true\`) are enabled.`,
),
dropdownHeight: d(
"This property sets the height of the dropdown list. If not set, the height is determined automatically.",
),
emptyListTemplate: dComponent(
`This optional property provides the ability to customize what is displayed when the ` +
`list of options is empty.`,
),
multiSelect: {
...dMulti(),
defaultValue: defaultProps.multiSelect,
},
searchable: {
description: `This property enables the search functionality in the dropdown list.`,
defaultValue: defaultProps.searchable,
},
inProgress: {
description: `This property indicates whether the component is in progress. It can be used to show a loading message.`,
defaultValue: defaultProps.inProgress,
},
inProgressNotificationMessage: {
description: `This property indicates the message to display when the component is in progress.`,
defaultValue: defaultProps.inProgressNotificationMessage,
},
},
events: {
gotFocus: dGotFocus(COMP),
lostFocus: dLostFocus(COMP),
didChange: dDidChange(COMP),
},
apis: {
focus: {
description: `This method focuses the \`${COMP}\` component. You can use it to programmatically focus the component.`,
signature: "focus(): void",
},
setValue: {
description: `This API sets the value of the \`${COMP}\`. You can use it to programmatically change the value.`,
signature: "setValue(value: string | string[] | undefined): void",
parameters: {
value:
"The new value to set. Can be a single value or an array of values for multi-select.",
},
},
value: {
description: `This API retrieves the current value of the \`${COMP}\`. You can use it to get the value programmatically.`,
signature: "get value(): string | string[] | undefined",
},
reset: {
description: `This method resets the component to its initial value, or clears the selection if no initial value was provided.`,
signature: "reset(): void",
},
},
contextVars: {
$item: d("Represents the current option's data (label and value properties)"),
$itemContext: d("Provides utility methods like `removeItem()` for multi-select scenarios"),
},
themeVars: parseScssVar(styles.themeVars),
defaultThemeVars: {
[`backgroundColor-menu-${COMP}`]: "$color-surface-raised",
[`boxShadow-menu-${COMP}`]: "$boxShadow-md",
[`borderRadius-menu-${COMP}`]: "$borderRadius",
[`borderWidth-menu-${COMP}`]: "1px",
[`borderColor-menu-${COMP}`]: "$borderColor",
[`backgroundColor-${COMP}-badge`]: "$color-primary-500",
[`fontSize-${COMP}-badge`]: "$fontSize-sm",
[`paddingHorizontal-${COMP}-badge`]: "$space-2_5",
[`paddingVertical-${COMP}-badge`]: "$space-0_5",
[`borderRadius-${COMP}-badge`]: "$borderRadius",
[`paddingHorizontal-item-${COMP}`]: "$space-2",
[`paddingVertical-item-${COMP}`]: "$space-2",
[`paddingHorizontal-${COMP}`]: "$space-2",
[`paddingVertical-${COMP}`]: "$space-2",
[`opacity-text-item-${COMP}--disabled`]: "0.5",
[`opacity-${COMP}--disabled`]: "0.5",
[`backgroundColor-${COMP}-badge--hover`]: "$color-primary-400",
[`backgroundColor-${COMP}-badge--active`]: "$color-primary-500",
[`textColor-item-${COMP}--disabled`]: "$color-surface-200",
[`textColor-${COMP}-badge`]: "$const-color-surface-50",
[`backgroundColor-item-${COMP}`]: "$backgroundColor-dropdown-item",
[`backgroundColor-item-${COMP}--hover`]: "$backgroundColor-dropdown-item--hover",
[`backgroundColor-item-${COMP}--active`]: "$backgroundColor-dropdown-item--active",
// Default borderColor-Input--disabled is too light so the disabled component is barely visible
[`borderColor-${COMP}--disabled`]: "initial",
},
});
export const selectComponentRenderer = createComponentRenderer(
COMP,
SelectMd,
({
node,
state,
updateState,
extractValue,
renderChild,
lookupEventHandler,
className,
registerComponentApi,
}) => {
const multiSelect = extractValue.asOptionalBoolean(node.props.multiSelect);
const searchable = extractValue.asOptionalBoolean(node.props.searchable);
const isControlled = node.props.value !== undefined;
return (
<Select
multiSelect={multiSelect}
className={className}
inProgress={extractValue.asOptionalBoolean(node.props.inProgress)}
inProgressNotificationMessage={extractValue.asOptionalString(
node.props.inProgressNotificationMessage,
)}
readOnly={extractValue.asOptionalBoolean(node.props.readOnly)}
updateState={isControlled ? undefined : updateState}
searchable={searchable}
initialValue={extractValue(node.props.initialValue)}
value={state?.value}
autoFocus={extractValue.asOptionalBoolean(node.props.autoFocus)}
enabled={extractValue.asOptionalBoolean(node.props.enabled)}
placeholder={extractValue.asOptionalString(node.props.placeholder)}
validationStatus={extractValue(node.props.validationStatus)}
onDidChange={lookupEventHandler("didChange")}
onFocus={lookupEventHandler("gotFocus")}
onBlur={lookupEventHandler("lostFocus")}
registerComponentApi={registerComponentApi}
emptyListTemplate={renderChild(node.props.emptyListTemplate)}
dropdownHeight={extractValue(node.props.dropdownHeight)}
required={extractValue.asOptionalBoolean(node.props.required)}
valueRenderer={
node.props.valueTemplate
? (item, removeItem) => {
return (
<MemoizedItem
contextVars={{
$itemContext: { removeItem },
}}
node={node.props.valueTemplate}
item={item}
renderChild={renderChild}
/>
);
}
: undefined
}
optionRenderer={
node.props.optionTemplate
? (item, val, inTrigger) => {
return (
<MemoizedItem
node={node.props.optionTemplate}
item={item}
contextVars={{
$selectedValue: val,
$inTrigger: inTrigger,
}}
renderChild={renderChild}
/>
);
}
: undefined
}
>
{renderChild(node.children)}
</Select>
);
},
);
```
--------------------------------------------------------------------------------
/docs/public/resources/files/releases.json:
--------------------------------------------------------------------------------
```json
[
{
"tag_name": "[email protected]",
"published_at": "2025-10-17T06:33:28Z",
"changes": [
{
"description": "The behavior infrastructure now uses ComponentProvider and allows adding custom behaviors",
"commit_sha": "501f60a"
},
{
"description": "Extend Tab with the tabAlignment and accordionView properties",
"commit_sha": "1020f1c"
}
],
"assets": [
{
"name": "xmlui-0.10.22.js",
"browser_download_url": "https://github.com/xmlui-org/xmlui/releases/download/xmlui%400.10.22/xmlui-0.10.22.js"
}
]
},
{
"tag_name": "[email protected]",
"published_at": "2025-10-16T11:53:23Z",
"changes": [
{
"description": "Add custom Text variant styling",
"commit_sha": "6fd4d62"
}
],
"assets": [
{
"name": "xmlui-0.10.21.js",
"browser_download_url": "https://github.com/xmlui-org/xmlui/releases/download/xmlui%400.10.21/xmlui-0.10.21.js"
}
]
},
{
"tag_name": "[email protected]",
"published_at": "2025-10-15T12:46:33Z",
"changes": [
{
"description": "fix: Autocomplete handles animations correctly",
"commit_sha": "26eac90"
},
{
"description": "Add margin-related theme variables to ContentSeparator",
"commit_sha": "f53edff"
},
{
"description": "Add applyIf property to Theme",
"commit_sha": "1840916"
},
{
"description": "fix: external animation is now correctly applied to ModalDialogs as well",
"commit_sha": "c6be7a3"
},
{
"description": "Added better error text when rendering FormItem outside of a Form.",
"commit_sha": "6aaefaf"
},
{
"description": "refactor: Select and AutoComplete components",
"commit_sha": "28d2585"
},
{
"description": "The itemLabelWidth value of Form now supports theme variables ($space-\\* values).",
"commit_sha": "e29a231"
},
{
"description": "AppState now uses a merge operation to set initialValue",
"commit_sha": "22162c0"
},
{
"description": "fix: itemWithLabel - layout issue",
"commit_sha": "e90232b"
}
],
"assets": [
{
"name": "xmlui-0.10.20.js",
"browser_download_url": "https://github.com/xmlui-org/xmlui/releases/download/xmlui%400.10.20/xmlui-0.10.20.js"
}
]
},
{
"tag_name": "[email protected]",
"published_at": "2025-10-07T09:22:39Z",
"changes": [
{
"description": "Add checkboxTolerance property to Table",
"commit_sha": "facb257"
},
{
"description": "test: review onFocus, onBlur e2e tests",
"commit_sha": "6084c14"
},
{
"description": "Renamed the following properties in DatePicker: minValue -> startDate, maxValue -> endDate. Also updated component documentation.",
"commit_sha": "e1fa9d7"
}
],
"assets": [
{
"name": "xmlui-0.10.19.js",
"browser_download_url": "https://github.com/xmlui-org/xmlui/releases/download/xmlui%400.10.19/xmlui-0.10.19.js"
}
]
},
{
"tag_name": "[email protected]",
"published_at": "2025-10-06T12:52:25Z",
"changes": [
{
"description": "refactor: use labelBehavior instead of ItemWithLabel",
"commit_sha": "202f2b2"
},
{
"description": "Add back removed RadioItem",
"commit_sha": "6650ee8"
},
{
"description": "Fixed FormItem validation indicators to use a relaxed validation indication strategy.",
"commit_sha": "da98994"
},
{
"description": "fix: labelBehavior, input components - styling issue",
"commit_sha": "8394663"
}
],
"assets": [
{
"name": "xmlui-0.10.18.js",
"browser_download_url": "https://github.com/xmlui-org/xmlui/releases/download/xmlui%400.10.18/xmlui-0.10.18.js"
}
]
},
{
"tag_name": "[email protected]",
"published_at": "2025-10-03T09:59:22Z",
"changes": [
{
"description": "Undust and improve the Tree component",
"commit_sha": "0ba6612"
},
{
"description": "Fixed Slider ranged version where only the first thumb is interactable.",
"commit_sha": "7b78052"
},
{
"description": "improve: remove cmdk from autocomple, add keywords prop to option",
"commit_sha": "314b429"
},
{
"description": "fix: NumberBox initialValue ignores non-convertible string values, minValue and maxValue now applies to typed-in input as well as to increments / decrements with spinner buttons.",
"commit_sha": "a1dea8f"
},
{
"description": "refactor: move behavior application earlier in ComponentAdapter render flow",
"commit_sha": "cff754c"
}
],
"assets": [
{
"name": "xmlui-0.10.16.js",
"browser_download_url": "https://github.com/xmlui-org/xmlui/releases/download/xmlui%400.10.16/xmlui-0.10.16.js"
}
]
},
{
"tag_name": "[email protected]",
"published_at": "2025-09-26T11:41:22Z",
"changes": [
{
"description": "Add the data property to the Pdf component",
"commit_sha": "3c8ad14"
},
{
"description": "Add the \"transform\" layout property",
"commit_sha": "5502fea"
},
{
"description": "Add syncWithAppState and initiallySelected properties to Table",
"commit_sha": "e08f0ba"
},
{
"description": "Fix MenuSeparator and SubMenuitem (forwardRef)",
"commit_sha": "5502fea"
},
{
"description": "fix: NavGroup componenet's iconVertical{Expanded,Collapsed} properties now apply based on it's 'open' state. Only the Expanded one was present before the fix.",
"commit_sha": "db618b5"
},
{
"description": "Allow event handlers to use nested action components recursively",
"commit_sha": "a795b3d"
},
{
"description": "feat: introducing behaviors - tooltip, animation, label",
"commit_sha": "5851c02"
}
],
"assets": [
{
"name": "xmlui-0.10.15.js",
"browser_download_url": "https://github.com/xmlui-org/xmlui/releases/download/xmlui%400.10.15/xmlui-0.10.15.js"
}
]
},
{
"tag_name": "[email protected]",
"published_at": "2025-09-23T12:57:26Z",
"changes": [
{
"description": "fix: Modal dialog scrolling issue",
"commit_sha": "618049b"
},
{
"description": "Allow image to accept binary data (and use it instead of src)",
"commit_sha": "215a142"
},
{
"description": "Allow user-defined components in extension packages",
"commit_sha": "65b52e1"
},
{
"description": "Fixed Slider input type, label focus, readOnly property, as well as min & max value issues.",
"commit_sha": "0cc2178"
},
{
"description": "Fixed feature to add custom icons for the Carousel prev/next page buttons.",
"commit_sha": "53d4ed9"
}
],
"assets": [
{
"name": "xmlui-0.10.14.js",
"browser_download_url": "https://github.com/xmlui-org/xmlui/releases/download/xmlui%400.10.14/xmlui-0.10.14.js"
}
]
},
{
"tag_name": "[email protected]",
"published_at": "2025-09-22T08:53:46Z",
"changes": [
{
"description": "Added short debounce to ColorPicker to make changing color values with slider a bit smoother.",
"commit_sha": "9401ee0"
},
{
"description": "fix: assigning new properties to objects in xmlui script",
"commit_sha": "eb62858"
},
{
"description": "fix: stricter empty body detection in RestApiProxy",
"commit_sha": "eb62858"
},
{
"description": "fix: TextArea autofocus",
"commit_sha": "eb62858"
},
{
"description": "fix: dropdownMenu overflow",
"commit_sha": "eb62858"
},
{
"description": "fix: ability to use user defined components in triggerTemplate (dropdownMenu)",
"commit_sha": "eb62858"
},
{
"description": "select: use focus-visible instead of focus for outline",
"commit_sha": "eb62858"
},
{
"description": "fix: modal dialog/toast issue",
"commit_sha": "243b7fa"
},
{
"description": "form: hideButtonRowUntilDirty",
"commit_sha": "eb62858"
}
],
"assets": [
{
"name": "xmlui-0.10.13.js",
"browser_download_url": "https://github.com/xmlui-org/xmlui/releases/download/xmlui%400.10.13/xmlui-0.10.13.js"
}
]
}
]
```
--------------------------------------------------------------------------------
/xmlui/scripts/generate-docs/DocsGenerator.mjs:
--------------------------------------------------------------------------------
```
import { basename, extname, join } from "path";
import { existsSync, writeFileSync } from "fs";
import { unlink, readFile, writeFile, readdir, mkdir } from "fs/promises";
import { logger, LOGGER_LEVELS, processError } from "./logger.mjs";
import { MetadataProcessor } from "./MetadataProcessor.mjs";
import { getSectionBeforeAndAfter, strBufferToLines, toHeadingPath } from "./utils.mjs";
import { buildPagesMap } from "./build-pages-map.mjs";
import { buildDownloadsMap } from "./build-downloads-map.mjs";
import { FOLDERS } from "./folders.mjs";
import {
FILE_EXTENSIONS,
OUTPUT_FILES,
ERROR_MESSAGES,
METADATA_SECTIONS
} from "./constants.mjs";
logger.setLevels(LOGGER_LEVELS.warning, LOGGER_LEVELS.error);
export class DocsGenerator {
metadata = [];
folders = {
sourceFolder: FOLDERS.pages,
outFolder: FOLDERS.pages,
examplesFolder: FOLDERS.pages,
};
constructor(metadata, folders, { excludeComponentStatuses }) {
this.metadata = metadata;
this.folders = folders;
this.expandMetadata(excludeComponentStatuses);
}
/**
* Filters provided metadata, then adds further information possibly missing.
* @param {string[]} excludeComponentStatuses The status of the component to exclude, e.g. 'in progress' can be excluded
*/
expandMetadata(excludeComponentStatuses) {
logger.info("Transforming & expanding component metadata");
this.metadata = Object.entries(this.metadata)
.filter(([_, compData]) => {
return !excludeComponentStatuses.includes(compData.status?.toLowerCase());
})
.map(([compName, compData]) => {
const displayName = compName;
let componentFolder = "";
if (compData.specializedFrom) {
const parentName = Object.keys(this.metadata).find((name) => name === compData.specializedFrom);
if (parentName) {
componentFolder = this.metadata[parentName].docFolder || compData.specializedFrom;
}
} else {
componentFolder = compData.docFolder || compName;
}
const descriptionRef = join(componentFolder, `${displayName}.md`);
const extendedComponentData = {
...compData,
displayName,
description: compData.description,
descriptionRef,
folderPath: componentFolder,
};
const entries = addDescriptionRef(extendedComponentData, [
METADATA_SECTIONS.PROPS,
METADATA_SECTIONS.EVENTS,
METADATA_SECTIONS.API,
METADATA_SECTIONS.CONTEXT_VARS,
]);
return { ...extendedComponentData, ...entries };
});
}
generateDocs() {
logger.info("Processing MDX files");
const metaProcessor = new MetadataProcessor(this.metadata, "", this.folders);
return metaProcessor.processDocfiles();
}
/**
* Writes the meta file summary to the output folder
* @param {Record<string,string>} metaFileContents The meta file contents
* @param {string} outputFolder The output folder
*/
writeMetaSummary(metaFileContents, outputFolder) {
try {
writeFileSync(join(outputFolder, FILE_EXTENSIONS.METADATA), JSON.stringify(metaFileContents, null, 2));
} catch (e) {
logger.error(ERROR_MESSAGES.WRITE_META_FILE_ERROR, e?.message || ERROR_MESSAGES.UNKNOWN_ERROR);
}
}
async exportMetadataToJson(folderName, filename) {
logger.info("Exporting metadata to JSON");
try {
const outPath = join(FOLDERS.script, "metadata", folderName ?? "");
if (!existsSync(outPath)) {
await mkdir(outPath, { recursive: true });
}
await writeFile(
join(outPath, `${filename ? `${filename}-` : ""}${OUTPUT_FILES.METADATA_JSON}`),
JSON.stringify(this.metadata, null, 2),
);
} catch (error) {
processError(error);
}
}
/**
* Creates the metadata JSON for the landing page to link components to the documentation.
* @param {string} docsUrl docs site base URL
* @param {string} pathToEndpoint the path that leads to the component articles on the site
*/
async createMetadataJsonForLanding(docsUrl, pathToEndpoint) {
logger.info("Creating metadata JSON for landing page");
try {
const dataForLanding = this.metadata.map((component) => ({
displayName: component.displayName,
description: component.description,
docFileLink: new URL(`${pathToEndpoint}/${component.displayName}`, docsUrl).href,
}));
const distMetaFolder = join(FOLDERS.xmluiDist, "metadata");
if (!existsSync(distMetaFolder)) {
await mkdir(distMetaFolder, { recursive: true });
}
await writeFile(
join(distMetaFolder, OUTPUT_FILES.LANDING_METADATA_JSON),
JSON.stringify(dataForLanding, null, 2),
);
} catch (error) {
processError(error);
}
}
/**
* Generates the package description section in a specified file for a given Extension package.
* @param {string} packageDescription The data to add to the file
* @param {string} sectionHeading Name & level of the section to (re)generate
* @param {string} fileName The name and absolute path of the file to write to
*/
async generatePackageDescription(packageDescription, sectionHeading, fileName) {
logger.info("Creating package description section in specified file");
try {
const outFile = fileName || join(FOLDERS.pages, `${basename(this.folders.sourceFolder)}.md`);
if (!existsSync(outFile)) {
await writeFile(outFile, "");
}
let buffer = await readFile(outFile, "utf8");
const { beforeSection, afterSection } = getSectionBeforeAndAfter(buffer, sectionHeading);
const section =
beforeSection + "\n" + sectionHeading + "\n\n" + packageDescription + afterSection;
await writeFile(outFile, section.trim());
} catch (error) {
processError(error);
}
}
async generatePermalinksForHeaders() {
logger.info("Generating permalinks for file headings");
const docFiles = existsSync(this.folders.outFolder)
? (await readdir(this.folders.outFolder)).filter((file) => extname(file) === FILE_EXTENSIONS.MARKDOWN[0])
: [];
for (const file of docFiles) {
const filePath = join(this.folders.outFolder, file);
if (!existsSync(filePath)) {
throw new ErrorWithSeverity(`File ${file} does not exist.`, LOGGER_LEVELS.error);
}
await generatePermalinks(filePath);
}
}
async generateArticleAndDownloadsLinks() {
try {
const pagesMapFile = join(FOLDERS.docsMeta, OUTPUT_FILES.PAGES_MAP);
if (existsSync(pagesMapFile)) {
await unlink(pagesMapFile);
await writeFile(pagesMapFile, "");
}
logger.info("Generating link IDs for article headings");
buildPagesMap(FOLDERS.pages, pagesMapFile);
const downloadsMapFile = join(FOLDERS.docsMeta, OUTPUT_FILES.DOWNLOADS_MAP);
if (existsSync(downloadsMapFile)) {
await unlink(downloadsMapFile);
await writeFile(downloadsMapFile, "");
}
logger.info("Generating link IDs for downloadable files");
buildDownloadsMap(join(FOLDERS.docsRoot, "public", "resources", "files"), downloadsMapFile);
} catch (error) {
processError(error);
}
}
}
function addDescriptionRef(component, entries = []) {
const result = {};
if (component) {
entries.forEach((entry) => {
if (component[entry]) {
result[entry] = Object.fromEntries(
Object.entries(component[entry]).map(([k, v]) => {
v.descriptionRef = `${component.displayName}.md?${k}`;
return [k, v];
}),
);
}
});
}
return result;
}
/**
* Get the ID and path of the article heading.
* @param {string} articlePath The path of the article.
*/
async function generatePermalinks(articlePath) {
try {
const content = await readFile(articlePath, { encoding: "utf8" });
const lines = strBufferToLines(content);
const newContent = appendHeadingIds(lines);
await writeFile(articlePath, newContent, { encoding: "utf8" });
} catch (error) {
processError(error);
}
// ---
function appendHeadingIds(lines) {
let newLines = [];
for (const line of lines) {
// Match Headings
const match = line.match(/^#{1,6}\s+.+?\s*(\[#[\w-]+\])?$/);
if (match && typeof match[1] === "undefined") {
newLines.push(
`${line} [#${toHeadingPath(stripApostrophies(stripParentheses(stripBackticks(match[0]))))}]`,
);
continue;
}
// Rest
newLines.push(line);
}
return newLines.join("\n");
}
function stripBackticks(str) {
return str.replace(/`/g, "");
}
function stripParentheses(str) {
return str.replace(/\(|\)/g, "");
}
function stripApostrophies(str) {
return str.replace(/"|'/g, "");
}
}
```
--------------------------------------------------------------------------------
/docs/content/components/FileInput.md:
--------------------------------------------------------------------------------
```markdown
# FileInput [#fileinput]
`FileInput` enables users to select files from their device's file system for upload or processing. It combines a text field displaying selected files with a customizable button that opens the system file browser. Use it for forms, media uploads, and document processing workflows.
**Key features:**
- **File type filtering**: Restrict selection to specific file types using `acceptsFileType`
- **Multiple file selection**: Enable users to select multiple files simultaneously
- **Directory selection**: Allow folder selection instead of individual files
- **Customizable button**: Configure button text, icons, position, and styling to match your design
## Properties [#properties]
### `acceptsFileType` [#acceptsfiletype]
An optional list of file types the input controls accepts provided as a string array.
```xmlui-pg copy display name="Example: acceptsFileType"
<App>
<FileInput acceptsFileType="{['.txt', '.jpg']}" />
</App>
```
### `autoFocus` (default: false) [#autofocus-default-false]
If this property is set to `true`, the component gets the focus automatically when displayed.
### `buttonIcon` [#buttonicon]
The ID of the icon to display in the button. You can change the default icon for all FileInput instances with the "icon.browse:FileInput" declaration in the app configuration file.
```xmlui-pg copy display name="Example: buttonIcon"
<App>
<FileInput buttonIcon="drive" buttonLabel="Let there be drive" />
<FileInput buttonIcon="drive" />
</App>
```
### `buttonIconPosition` (default: "start") [#buttoniconposition-default-start]
This optional string determines the location of the button icon.
Available values: `start` **(default)**, `end`
```xmlui-pg copy display name="Example: buttonIconPosition"
<App>
<FileInput buttonIcon="drive" buttonLabel="End" buttonIconPosition="end" />
</App>
```
### `buttonLabel` [#buttonlabel]
This property is an optional string to set a label for the button part.
This property is an optional string to set a label for the button part.
```xmlui-pg copy display name="Example: label"
<App >
<FileInput />
<FileInput buttonLabel="I am the button label" />
</App>
```
### `buttonPosition` (default: "end") [#buttonposition-default-end]
This property determines the position of the button relative to the input field.
Available values: `start`, `end` **(default)**
### `buttonSize` [#buttonsize]
The size of the button (small, medium, large)
Available values:
| Value | Description |
| --- | --- |
| `xs` | Extra small |
| `sm` | Small |
| `md` | Medium |
| `lg` | Large |
| `xl` | Extra large |
```xmlui-pg copy display name="Example: buttonSize"
<App>
<FileInput buttonSize="lg" />
</App>
```
### `buttonThemeColor` (default: "primary") [#buttonthemecolor-default-primary]
The button color scheme (primary, secondary, attention)
Available values: `attention`, `primary` **(default)**, `secondary`
```xmlui-pg copy display name="Example: buttonThemeColor"
<App>
<FileInput buttonThemeColor="secondary" />
</App>
```
### `buttonVariant` [#buttonvariant]
The button variant to use
Available values: `solid`, `outlined`, `ghost`
```xmlui-pg copy display name="Example: buttonVariant"
<App>
<FileInput buttonLabel="outlined" buttonVariant="outlined" />
</App>
```
### `directory` (default: false) [#directory-default-false]
This boolean property indicates whether the component allows selecting directories (`true`) or files only (`false`).
### `enabled` (default: true) [#enabled-default-true]
This boolean property value indicates whether the component responds to user events (`true`) or not (`false`).
### `initialValue` [#initialvalue]
This property sets the component's initial value.
### `multiple` (default: false) [#multiple-default-false]
This boolean property enables to add not just one (`false`), but multiple files to the field (`true`). This is done either by dragging onto the field or by selecting multiple files in the browser menu after clicking the input field button.
```xmlui-pg copy display name="Example: multiple"
<App>
<FileInput multiple="false" />
<FileInput multiple="true" />
</App>
```
### `placeholder` [#placeholder]
An optional placeholder text that is visible in the input field when its empty.
### `readOnly` (default: false) [#readonly-default-false]
Set this property to `true` to disallow changing the component value.
### `required` (default: false) [#required-default-false]
Set this property to `true` to indicate it must have a value before submitting the containing form.
### `validationStatus` (default: "none") [#validationstatus-default-none]
This property allows you to set the validation status of the input component.
Available values:
| Value | Description |
| --- | --- |
| `valid` | Visual indicator for an input that is accepted |
| `warning` | Visual indicator for an input that produced a warning |
| `error` | Visual indicator for an input that produced an error |
## Events [#events]
### `didChange` [#didchange]
This event is triggered when value of FileInput has changed.
Write in the input field and see how the `Text` underneath it is updated in accordingly.
```xmlui-pg copy {2} display name="Example: didChange"
<App var.field="">
<FileInput onDidChange="(file) => field = file[0]?.name" />
<Text value="{field}" />
</App>
```
### `gotFocus` [#gotfocus]
This event is triggered when the FileInput has received the focus.
Clicking on the `FileInput` in the example demo changes the label text.
Note how clicking elsewhere resets the text to the original.
```xmlui-pg copy {4-5} display name="Example: gotFocus/lostFocus"
<App>
<FileInput
buttonLabel="{focused === true ? 'I got focused!' : 'I lost focus...'}"
onGotFocus="focused = true"
onLostFocus="focused = false"
var.focused="{false}"
/>
</App>
```
### `lostFocus` [#lostfocus]
This event is triggered when the FileInput has lost the focus.
(See the example above)
## Exposed Methods [#exposed-methods]
### `focus` [#focus]
This API command focuses the input field of the component.
**Signature**: `focus(): void`
```xmlui-pg copy /fileInputComponent.focus()/ display name="Example: focus"
<App>
<HStack>
<Button label="Focus FileInput" onClick="fileInputComponent.focus()" />
<FileInput id="fileInputComponent" />
</HStack>
</App>
```
### `open` [#open]
This API command triggers the file browsing dialog to open.
**Signature**: `open(): void`
```xmlui-pg copy /fileInputComponent.open()/ display name="Example: open"
<App>
<HStack>
<Button label="Open FileInput" onClick="fileInputComponent.open()" />
<FileInput id="fileInputComponent" />
</HStack>
</App>
```
### `setValue` [#setvalue]
This method sets the current value of the component.
**Signature**: `setValue(files: File[]): void`
- `files`: An array of File objects to set as the current value of the component.
(**NOT IMPLEMENTED YET**) You can use this method to set the component's current value programmatically.
### `value` [#value]
This property holds the current value of the component, which is an array of files.
**Signature**: `get value(): File[]`
In the example below, select a file using the file browser of the `FileInput` component
and note how the `Text` component displays the selected file's name:
```xmlui-pg copy {3-4} display name="Example: value"
<App>
<HStack>
<Text value="Selected file name: {fileInputComponent.value}" />
<FileInput id="fileInputComponent" />
</HStack>
</App>
```
## Styling [#styling]
The `FileInput` component does not theme variables directly.
However, it uses the [`Button`](/components/Button) and [`TextBox`](/components/TextBox) components under the hood.
Thus, modifying the styles of both of these components affects the `FileInput`.
See [Button styling](/components/Button#styling) and [TextBox styling](/components/TextBox#styling).
### Theme Variables [#theme-variables]
| Variable | Default Value (Light) | Default Value (Dark) |
| --- | --- | --- |
| [backgroundColor](../styles-and-themes/common-units/#color)-FileInput--focus | *none* | *none* |
| [borderColor](../styles-and-themes/common-units/#color)-FileInput--focus | *none* | *none* |
| [borderRadius](../styles-and-themes/common-units/#border-rounding)-FileInput--focus | *none* | *none* |
| [boxShadow](../styles-and-themes/common-units/#boxShadow)-FileInput--focus | *none* | *none* |
| [outlineColor](../styles-and-themes/common-units/#color)-FileInput--focus | *none* | *none* |
| [outlineOffset](../styles-and-themes/common-units/#size)-FileInput--focus | *none* | *none* |
| [outlineStyle](../styles-and-themes/common-units/#border)-FileInput--focus | *none* | *none* |
| [outlineWidth](../styles-and-themes/common-units/#size)-FileInput--focus | *none* | *none* |
| [textColor](../styles-and-themes/common-units/#color)-FileInput--focus | *none* | *none* |
```
--------------------------------------------------------------------------------
/xmlui/src/components/Markdown/Markdown.module.scss:
--------------------------------------------------------------------------------
```scss
@use "../../components-core/theming/themes" as t;
// --- This code snippet is required to collect the theme variables used in this module
$themeVars: ();
@function createThemeVar($componentVariable) {
$themeVars: t.appendThemeVar($themeVars, $componentVariable) !global;
@return t.getThemeVar($themeVars, $componentVariable);
}
$themeVars: t.composeTextVars($themeVars, "Text") !global;
$paddingTop-MarkDown: createThemeVar("paddingTop-Markdown");
$paddingBottom-MarkDown: createThemeVar("paddingBottom-Markdown");
$backgroundColor-MarkDown: createThemeVar("backgroundColor-Markdown");
$themeVars: t.composePaddingVars($themeVars, "Blockquote");
$themeVars: t.composeBorderVars($themeVars, "Blockquote");
$color-accent-Blockquote: createThemeVar("color-accent-Blockquote");
$width-accent-Blockquote: createThemeVar("width-accent-Blockquote");
$backgroundColor-Blockquote: createThemeVar("backgroundColor-Blockquote");
$paddingLeft-Blockquote: createThemeVar("paddingLeft-Blockquote");
$borderRadius-Blockquote: createThemeVar("borderRadius-Blockquote");
$marginTop-Blockquote: createThemeVar("marginTop-Blockquote");
$marginBottom-Blockquote: createThemeVar("marginBottom-Blockquote");
$themeVars: t.composePaddingVars($themeVars, "Admonition");
$themeVars: t.composeBorderVars($themeVars, "Admonition");
$backgroundColor-Admonition: createThemeVar("backgroundColor-Admonition");
$borderRadius-Admonition: createThemeVar("borderRadius-Admonition");
$size-icon-Admonition: createThemeVar("size-icon-Admonition");
$marginTop-Admonition: createThemeVar("marginTop-Admonition");
$marginBottom-Admonition: createThemeVar("marginBottom-Admonition");
$marginLeft-Admonition-content: createThemeVar("marginLeft-Admonition-content");
$marginTop-HtmlVideo: createThemeVar("marginTop-HtmlVideo");
$marginBottom-HtmlVideo: createThemeVar("marginBottom-HtmlVideo");
@layer components {
.markdownContent {
padding-top: $paddingTop-MarkDown;
padding-bottom: $paddingBottom-MarkDown;
background-color: $backgroundColor-MarkDown;
min-width: 0;
width: 100%;
@include t.textVars($themeVars, "Text");
.markdown {
$component: "Text";
$variantName: "Text-markdown";
$themeVars: t.composePaddingVars($themeVars, variantName);
$themeVars: t.composeBorderVars($themeVars, $variantName);
$themeVars: t.composeTextVars($themeVars, $variantName, $component);
@include t.paddingVars($themeVars, $variantName);
@include t.borderVars($themeVars, $variantName);
@include t.textVars($themeVars, $variantName);
margin-top: createThemeVar("marginTop-#{$variantName}");
margin-bottom: createThemeVar("marginBottom-#{$variantName}");
margin-left: createThemeVar("marginLeft-#{$variantName}");
margin-right: createThemeVar("marginRight-#{$variantName}");
overflow: visible;
display: block;
}
// --- Additional Heading styles
h1 {
margin-top: createThemeVar("marginTop-H1-markdown") !important;
margin-bottom: createThemeVar("marginBottom-H1-markdown") !important;
font-size: createThemeVar("fontSize-H1-markdown") !important;
}
h2 {
margin-top: createThemeVar("marginTop-H2-markdown") !important;
margin-bottom: createThemeVar("marginBottom-H2-markdown") !important;
}
h3 {
margin-top: createThemeVar("marginTop-H3-markdown") !important;
margin-bottom: createThemeVar("marginBottom-H3-markdown") !important;
}
h4 {
margin-top: createThemeVar("marginTop-H4-markdown") !important;
margin-bottom: createThemeVar("marginBottom-H4-markdown") !important;
}
h5 {
margin-top: createThemeVar("marginTop-H5-markdown") !important;
margin-bottom: createThemeVar("marginBottom-H5-markdown") !important;
}
h6 {
margin-top: createThemeVar("marginTop-H6-markdown") !important;
margin-bottom: createThemeVar("marginBottom-H6-markdown") !important;
}
// --- Image
.block {
margin-top: createThemeVar("marginTop-Image-markdown");
margin-bottom: createThemeVar("marginBottom-Image-markdown");
margin-left: createThemeVar("marginLeft-Image-markdown");
margin-right: createThemeVar("marginRight-Image-markdown");
}
// --- Blockquote
.blockquote {
position: relative;
margin-top: $marginTop-Blockquote;
margin-bottom: $marginBottom-Blockquote;
background-color: $backgroundColor-Blockquote;
&::before {
background-color: $color-accent-Blockquote;
position: absolute;
top: 0;
left: 0;
display: block;
content: "";
height: 100%;
width: $width-accent-Blockquote;
}
}
.blockquoteContainer {
@include t.borderVars($themeVars, "Blockquote");
@include t.paddingVars($themeVars, "Blockquote");
}
.admonitionBlockquote {
margin-top: $marginTop-Admonition;
margin-bottom: $marginBottom-Admonition;
background-color: $backgroundColor-Admonition;
border-radius: $borderRadius-Admonition;
@include t.borderVars($themeVars, "Admonition");
@include t.paddingVars($themeVars, "Admonition");
&.info {
background-color: createThemeVar("backgroundColor-Admonition-info");
border-color: createThemeVar("borderColor-Admonition-info");
}
&.warning {
background-color: createThemeVar("backgroundColor-Admonition-warning");
border-color: createThemeVar("borderColor-Admonition-warning");
}
&.danger {
background-color: createThemeVar("backgroundColor-Admonition-danger");
border-color: createThemeVar("borderColor-Admonition-danger");
}
&.note {
background-color: createThemeVar("backgroundColor-Admonition-note");
border-color: createThemeVar("borderColor-Admonition-note");
}
&.tip {
background-color: createThemeVar("backgroundColor-Admonition-tip");
border-color: createThemeVar("borderColor-Admonition-tip");
}
}
.admonitionContainer {
padding: 0.5rem;
display: flex;
align-items: flex-start;
}
.admonitionIcon {
font-size: $size-icon-Admonition;
line-height: 1;
}
.admonitionContent {
margin-left: $marginLeft-Admonition-content;
flex: 1;
min-width: 0;
}
.admonitionBlockquote {
.admonitionContent {
[class*="text_"][class*="markdown_"],
ul,
ol {
margin-top: 0;
margin-bottom: 0;
}
}
}
.horizontalRule {
border-top-color: createThemeVar("borderColor-HorizontalRule");
border-top-style: createThemeVar("borderStyle-HorizontalRule");
border-top-width: createThemeVar("borderWidth-HorizontalRule");
}
li:has(> input[type="checkbox"]),
li:has(> input[type="checkbox"]) {
display: flex;
align-items: flex-start;
> input[type="checkbox"] {
margin-right: 8px;
margin-top: 4px;
flex-shrink: 0;
}
}
// First element should have no top margin
> *:first-child {
margin-top: 0;
}
// Last element should have no bottom margin
> *:last-child {
margin-bottom: 0;
}
// --- Table
.tableScrollContainer {
overflow-x: auto;
width: 100%;
}
// --- Details adornment
.detailsAdornment {
margin-top: $marginTop-Admonition;
margin-bottom: $marginBottom-Admonition;
border-radius: $borderRadius-Admonition;
background-color: $backgroundColor-Admonition;
@include t.borderVars($themeVars, "Admonition");
// Override ExpandableItem styles for better integration
:global(.summary) {
padding: 0.5rem 1rem;
font-weight: 600; // Make summary text bold
}
:global(.content) {
padding: 0 1rem 0.5rem 1rem;
}
}
}
// --- UnorderedList
/*
$paddingLeft-UnorderedList: createThemeVar("paddingLeft-UnorderedList");
// the basic <ul> and <ol> styles are the same in tabler.io too
.unorderedList {
list-style-type: revert;
list-style-position: outside;
padding-left: $paddingLeft-UnorderedList;
}
// --- OrderedList
$paddingLeft-OrderedList: createThemeVar("paddingLeft-OrderedList");
// the basic <ul> and <ol> styles are the same in tabler.io too
.orderedList {
list-style-type: revert;
list-style-position: outside;
padding-left: $paddingLeft-OrderedList;
}
// --- ListItem
$paddingLeft-ListItem: createThemeVar("paddingLeft-ListItem");
$color-marker-ListItem: createThemeVar("color-marker-ListItem");
.listItem {
padding-left: $paddingLeft-ListItem;
}
.listItem::marker {
color: $color-marker-ListItem;
}
*/
}
// --- We export the theme variables to add them to the component renderer
:export {
themeVars: t.json-stringify($themeVars);
}
```
--------------------------------------------------------------------------------
/xmlui/src/components/Input/PartialInput.tsx:
--------------------------------------------------------------------------------
```typescript
import { useCallback, useMemo } from "react";
import classnames from "classnames";
import styles from "./PartialInput.module.scss";
/**
* Direction indicator for blur events to help parent components understand
* the user's navigation intent.
*/
export type BlurDirection = "next" | "previous" | "external";
/**
* Props for PartialInput component.
* This component encapsulates common input behavior for multi-field input components,
* including auto-advance, arrow key navigation support, and enhanced blur handling.
*/
export interface PartialInputProps {
// Core functionality (varies by context)
value?: string | null;
/** Explicit placeholder override. If not provided, will be generated from emptyCharacter */
placeholder?: string;
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
onBlur?: (direction: BlurDirection, event: React.FocusEvent<HTMLInputElement>) => void;
onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement> & { target: HTMLInputElement }) => void;
// EmptyCharacter support
/** Character to use for generating placeholders. Defaults to "-" if not provided */
emptyCharacter?: string;
/** Number of characters to repeat for placeholder. Defaults to 2 */
placeholderLength?: number;
// Validation & constraints (context-dependent)
max: number;
min: number;
maxLength?: number; // PartialInput derives auto-advance logic from this
validateFn?: (value: string) => boolean;
onBeep?: () => void;
// Navigation (context-dependent)
nextInputRef?: React.RefObject<HTMLInputElement | null>;
nextButtonRef?: React.RefObject<HTMLButtonElement | null>;
// Identification & accessibility (varies)
id?: string;
name: string;
ariaLabel?: string;
className?: string;
invalidClassName?: string;
// Standard props (can vary per input)
disabled?: boolean;
readOnly?: boolean;
required?: boolean;
autoFocus?: boolean;
inputRef?: React.RefObject<HTMLInputElement | null>;
// Optional overrides (with sensible defaults)
step?: number; // Default: 1
isInvalid?: boolean; // Default: false (prevents auto-advance when true)
}
/**
* PartialInput is a specialized input component designed for multi-field input scenarios.
*
* Key features:
* - Auto-advance: Automatically moves to the next input when max length is reached
* - Enhanced blur detection: Provides direction information (next/previous/external)
* - Consistent styling: Common base styles with component-specific overrides via className
* - Accessibility: Proper ARIA labels and keyboard navigation support
* - Validation integration: Works with validation functions and provides feedback
*
* This component encapsulates common patterns found in multi-field input components,
* reducing code duplication and ensuring consistent behavior.
*
* @param props - PartialInputProps configuration
* @returns A configured input element with enhanced multi-field input behavior
*/
export function PartialInput({
value,
placeholder,
onChange,
onBlur,
onKeyDown,
emptyCharacter,
placeholderLength = 2,
max,
min,
maxLength = 2,
validateFn,
onBeep,
nextInputRef,
nextButtonRef,
id,
name,
ariaLabel,
className,
invalidClassName,
disabled,
readOnly,
required,
autoFocus,
inputRef,
step = 1,
isInvalid = false,
...restProps
}: PartialInputProps) {
/**
* Process emptyCharacter according to requirements.
* Handles null/empty values, multi-character strings, and unicode characters.
*/
const processedEmptyCharacter = useMemo(() => {
if (!emptyCharacter || emptyCharacter.length === 0) {
return "-";
}
if (emptyCharacter.length > 1) {
// Use unicode-aware character extraction
const firstChar = [...emptyCharacter][0];
return firstChar;
}
return emptyCharacter;
}, [emptyCharacter]);
/**
* Generate final placeholder value.
* Uses explicit placeholder if provided, otherwise generates from emptyCharacter.
*/
const finalPlaceholder = useMemo(() => {
if (placeholder !== undefined) {
return placeholder; // Explicit override takes precedence
}
return processedEmptyCharacter.repeat(placeholderLength);
}, [placeholder, processedEmptyCharacter, placeholderLength]);
/**
* Internal focus handler that automatically selects all text for easier editing.
* This is a common UX pattern for numeric inputs in multi-field components.
*/
const handleInternalFocus = useCallback((event: React.FocusEvent<HTMLInputElement>) => {
// Select all text when gaining focus for easier editing
event.target.select();
}, []);
/**
* Enhanced input change handler with automatic advance logic.
* When the user types the maximum number of characters and the input is valid,
* automatically moves focus to the next input or button.
*/
const handleInputChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
const newValue = event.target.value;
// Call the original onChange handler
if (onChange) {
onChange(event);
}
// Auto-advance logic: move to next input when reaching maxLength with valid numeric input
if (newValue.length === maxLength && /^\d+$/.test(newValue) && !isInvalid) {
// Check if the new value is valid before auto-tabbing
const isValueInvalid = validateFn ? validateFn(newValue) : false;
if (!isValueInvalid) {
// Small delay to ensure the current input is properly updated
setTimeout(() => {
if (nextInputRef?.current) {
// Tab to next input field
nextInputRef.current.focus();
nextInputRef.current.select();
} else if (nextButtonRef?.current) {
// Tab to next button (e.g., action button)
nextButtonRef.current.focus();
}
}, 0);
} else {
// Input is ready for auto-tab but invalid - play beep sound and fire event
onBeep?.();
}
}
},
[onChange, nextInputRef, nextButtonRef, maxLength, validateFn, onBeep, isInvalid],
);
/**
* Enhanced blur handler that detects the direction of navigation.
* This helps parent components understand whether the user is moving forward,
* backward, or clicking outside the component entirely.
*/
const handleBlur = useCallback(
(event: React.FocusEvent<HTMLInputElement>) => {
const relatedTarget = event.relatedTarget as HTMLElement;
let direction: BlurDirection = "external";
// Determine direction based on related target
if (relatedTarget) {
// Check if moving to next input
if (nextInputRef?.current === relatedTarget) {
direction = "next";
}
// Check if moving to next button
else if (nextButtonRef?.current === relatedTarget) {
direction = "next";
}
// Check if moving from a navigation event (could be previous)
// Note: This is a simplified heuristic. In practice, we might need more sophisticated detection
else if (relatedTarget.getAttribute("data-input") === "true") {
// If it's another input, we assume it could be previous navigation
// The parent component can provide more context if needed
direction = "previous";
}
}
// Call the enhanced onBlur handler with direction
onBlur?.(direction, event);
},
[onBlur, nextInputRef, nextButtonRef],
);
/**
* Enhanced key down handler that prevents space character input.
* Filters out space key presses while allowing other keys to pass through.
*/
const handleKeyDown = useCallback(
(event: React.KeyboardEvent<HTMLInputElement>) => {
// Prevent space character input
if (event.key === " " || event.code === "Space") {
event.preventDefault();
return;
}
// Call the original onKeyDown handler for other keys
onKeyDown?.(event as React.KeyboardEvent<HTMLInputElement> & { target: HTMLInputElement });
},
[onKeyDown],
);
return (
<input
{...restProps}
id={id}
aria-label={ariaLabel}
autoComplete="off"
// biome-ignore lint/a11y/noAutofocus: This is up to developers' decision
autoFocus={autoFocus}
className={classnames(styles.partialInput, className, {
[invalidClassName]: isInvalid,
})}
data-input="true"
disabled={disabled}
inputMode="numeric"
max={max}
maxLength={maxLength}
min={min}
name={name}
onChange={handleInputChange}
onBlur={handleBlur}
onFocus={handleInternalFocus}
onKeyDown={handleKeyDown}
placeholder={finalPlaceholder}
readOnly={readOnly}
ref={inputRef as React.RefObject<HTMLInputElement>}
required={required}
step={step}
type="text"
value={value !== null ? value : ""}
/>
);
}
```
--------------------------------------------------------------------------------
/xmlui/src/syntax/monaco/grammar.monacoLanguage.ts:
--------------------------------------------------------------------------------
```typescript
export const XmluiGrammar: any = {
id: "xmlui",
config: {
comments: {
blockComment: ["<!--", "-->"],
},
brackets: [["<", ">"]],
autoClosingPairs: [
{ open: "<", close: ">" },
{ open: "'", close: "'" },
{ open: '"', close: '"' },
{ open: "`", close: "`" },
],
surroundingPairs: [
{ open: "<", close: ">" },
{ open: "'", close: "'" },
{ open: '"', close: '"' },
{ open: "`", close: "`" },
],
},
language: {
defaultToken: "",
tokenPostfix: ".xmlui",
ignoreCase: false,
identifier: /[a-zA-Z$_][-\w.$]*/,
tokenizer: {
root: [
{ include: "@commentStart" },
{ include: "@helperTag" },
{ include: "@componentTagStart" },
{ include: "@escapeCharacter" },
{ include: "@textWithBindingExpr" },
{ include: "@entity" },
{ include: "@cdataStart" },
],
helperTag: [
{ include: "@scriptTagStart" },
{ include: "@eventTagStart" },
{ include: "@apiTagStart" },
{ include: "@methodTagStart" },
{ include: "@propOrVarTagStart" },
],
eventTagStart: [
[
/(<)((?:[a-zA-Z_][\w\.\-]*?:)?)(event)/,
["delimiter.angle", "namespace", { token: "tag-event", next: "@eventTag" }],
],
],
eventTag: [
{ include: "@commentStart" },
{ include: "@valueAttributeScriptInsideStart" },
{ include: "@attributeStart" },
[/\/>/, "delimiter.angle", "@pop"],
[
/(<\/)(event)(\s*>)/,
["delimiter.angle", "tag-event", { token: "delimiter.angle", next: "@pop" }],
],
[/>/, { token: "delimiter.angle", next: "@eventTagContent" }],
],
eventTagContent: [
{ include: "commentStart" },
{ include: "componentTagStart" },
[
/[^<]/,
{ token: "@rematch", next: "@eventTagScriptContent", nextEmbedded: "xmluiscript" },
],
[/<\/event\s*>/, { token: "@rematch", next: "@pop" }],
],
eventTagScriptContent: [
[/<\/event\s*>/, { token: "@rematch", next: "@pop", nextEmbedded: "@pop" }],
[/[^<]/, ""],
],
methodTagStart: [
[
/(<)((?:[a-zA-Z_][\w\.\-]*?:)?)(method)/,
["delimiter.angle", "namespace", { token: "tag-helper", next: "@methodTag" }],
],
],
methodTag: [
{ include: "@commentStart" },
{ include: "@valueAttributeScriptInsideStart" },
{ include: "@attributeStart" },
[/\/>/, "delimiter.angle", "@pop"],
[
/>/,
{
token: "delimiter.angle",
next: "@methodTagScriptContent",
nextEmbedded: "xmluiscript",
},
],
[
/(<\/)(method)(\s*>)/,
["delimiter.angle", "tag-helper", { token: "delimiter.angle", next: "@pop" }],
],
],
methodTagScriptContent: [
[/<\/method\s*>/, { token: "@rematch", next: "@pop", nextEmbedded: "@pop" }],
[/[^</]/, ""],
],
apiTagStart: [
[
/(<)((?:[a-zA-Z_][\w\.\-]*?:)?)(api)/,
["delimiter.angle", "namespace", { token: "tag-helper", next: "@apiTag" }],
],
],
apiTag: [
{ include: "@commentStart" },
{ include: "@valueAttributeScriptInsideStart" },
{ include: "@attributeStart" },
[/\/>/, "delimiter.angle", "@pop"],
[
/>/,
{ token: "delimiter.angle", next: "@apiTagScriptContent", nextEmbedded: "xmluiscript" },
],
[
/(<\/)(api)(\s*>)/,
["delimiter.angle", "tag-helper", { token: "delimiter.angle", next: "@pop" }],
],
],
apiTagScriptContent: [
[/<\/api\s*>/, { token: "@rematch", next: "@pop", nextEmbedded: "@pop" }],
[/[^</]/, ""],
],
valueAttributeScriptInsideStart: [
[
/(^|\s+)(value)(\s*=)(['\"`])/,
[
"",
"attribute",
"operators",
{
cases: {
"'": { token: "string", next: "@singleQuotedScript", nextEmbedded: "xmluiscript" },
'"': { token: "string", next: "@doubleQuotedScript", nextEmbedded: "xmluiscript" },
"`": {
token: "string",
next: "@backtickQuotedScript",
nextEmbedded: "xmluiscript",
},
},
},
],
],
],
scriptTagStart: [
[
/(<)(script\s*)(>)/,
[
"delimiter.angle",
"tag-script",
{ token: "delimiter.angle", nextEmbedded: "xmluiscript", next: "@scriptTag" },
],
],
[/(<\/)(script\s*)(>)/, ["delimiter.angle", "tag-script", "delimiter.angle"]],
],
scriptTag: [
[/<\/script>/, { token: "@rematch", next: "@pop", nextEmbedded: "@pop" }],
[/[^<]+/, ""],
],
propOrVarTagStart: [
[
/(<\/?)((?:[a-zA-Z_][\w\.\-]*?:)?)((?:property)|(?:prop)|(?:var))/,
["delimiter.angle", "namespace", { token: "tag-helper", next: "@propOrVarTag" }],
],
],
propOrVarTag: [
[/\/?>/, { token: "delimiter.angle", next: "@pop" }],
{ include: "@commentStart" },
{ include: "@attributeStart" },
],
componentTagStart: [
[
/(\s*<\/?)((?:[a-zA-Z_][\w\.\-]*?:)?)([A-Z][\w\.\-]*)/,
["delimiter.angle", "namespace", { token: "tag-component", next: "@componentTag" }],
],
],
componentTag: [
[/\/?>/, { token: "delimiter.angle", next: "@pop" }],
{ include: "@commentStart" },
{ include: "@eventHandler" },
{ include: "@attributeStart" },
],
eventHandler: [
[
/(^|\s+)(on[A-Z][-\w.]*)(\s*=)(['\"`])/,
[
"",
"attribute",
"operators",
{
cases: {
"'": { token: "string", next: "@singleQuotedScript", nextEmbedded: "xmluiscript" },
'"': { token: "string", next: "@doubleQuotedScript", nextEmbedded: "xmluiscript" },
"`": {
token: "string",
next: "@backtickQuotedScript",
nextEmbedded: "xmluiscript",
},
},
},
],
],
],
doubleQuotedScript: [
[/"/, { token: "string", next: "@pop", nextEmbedded: "@pop" }],
[/[^"]/, ""],
],
singleQuotedScript: [
[/'/, { token: "string", next: "@pop", nextEmbedded: "@pop" }],
[/[^']/, ""],
],
backtickQuotedScript: [
[/`/, { token: "string", next: "@pop", nextEmbedded: "@pop" }],
[/[^`]/, ""],
],
attributeStart: [
[
/(^|\s+)(@identifier(?::@identifier)?)(\s*=\s*)(['\"`])/,
[
"",
"attribute",
"operators",
{
cases: {
"'": { token: "string", next: "@singleQuotedString" },
'"': { token: "string", next: "@doubleQuotedString" },
"`": { token: "string", next: "@backtickQuotedString" },
},
},
],
],
[
/(^|\s+)(@identifier(?::@identifier)?)(\s*=\s*)(@identifier)/,
["", "attribute", "operators", "string"],
],
[/(^|\s+)(@identifier(?::@identifier)?)/, ["", "attribute"]],
],
singleQuotedString: [
[/'/, "string", "@pop"],
{ include: "@textWithBindingExpr" },
[/[^']/, "string"],
],
doubleQuotedString: [
[/"/, "string", "@pop"],
{ include: "@textWithBindingExpr" },
[/[^"]/, "string"],
],
backtickQuotedString: [
[/`/, "string", "@pop"],
{ include: "@textWithBindingExpr" },
[/[^`]/, "string"],
],
textWithBindingExpr: [
{ include: "@escapeCharacter" },
{ include: "@entity" },
[/{/, { token: "delimiter.curly", next: "@bindingExpr", nextEmbedded: "xmluiscript" }],
],
bindingExpr: [
[/}/, { token: "delimiter.curly", next: "@pop", nextEmbedded: "@pop" }],
[/[^}]+/, ""],
],
cdataStart: [
[
/(<!\[)(CDATA)(\[)/,
["delimiter.angle", "tag-cdata", { token: "delimiter.angle", next: "@cdata" }],
],
],
cdata: [
[/]]>/, "delimiter.angle", "@pop"],
[/./, "string"],
],
commentStart: [[/<!--/, "comment", "@comment"]],
comment: [
[/[^<\-]+/, "comment.content"],
[/-->/, { token: "comment", next: "@pop" }],
[/[<\-]/, "comment.content"],
],
escapeCharacter: [[/\\S/, "string.escape"]],
entity: [[/&(amp|lt|gt|quot|apos|nbsp);/, "string.escape"]],
},
},
};
```
--------------------------------------------------------------------------------
/xmlui/src/components/DateInput/DateInput.tsx:
--------------------------------------------------------------------------------
```typescript
import styles from "./DateInput.module.scss";
import { createComponentRenderer } from "../../components-core/renderers";
import { parseScssVar } from "../../components-core/theming/themeVars";
import {
createMetadata,
dAutoFocus,
dDidChange,
dEnabled,
dEndIcon,
dEndText,
dGotFocus,
dInitialValue,
dLostFocus,
dReadonly,
dStartIcon,
dStartText,
dValidationStatus,
} from "../metadata-helpers";
import {
dateFormats,
DateInput,
DateInputModeValues,
defaultProps,
WeekDays,
} from "./DateInputNative";
const COMP = "DateInput";
export const DateInputMd = createMetadata({
status: "experimental",
description:
"`DateInput` provides a text-based date input interface for selecting single dates " +
"or date ranges, with direct keyboard input similar to TimeInput. It offers customizable " +
"formatting and validation options without dropdown calendars.",
parts: {
day: {
description: "The day input field.",
},
month: {
description: "The month input field.",
},
year: {
description: "The year input field.",
},
clearButton: {
description: "The button to clear the date input.",
},
},
props: {
initialValue: dInitialValue(),
autoFocus: dAutoFocus(),
readOnly: dReadonly(),
enabled: dEnabled(defaultProps.enabled),
validationStatus: dValidationStatus(defaultProps.validationStatus),
mode: {
description: "The mode of the date input (single or range)",
valueType: "string",
availableValues: DateInputModeValues,
defaultValue: defaultProps.mode,
},
dateFormat: {
description: "The format of the date displayed in the input field",
valueType: "string",
defaultValue: defaultProps.dateFormat,
availableValues: dateFormats,
},
emptyCharacter: {
description: "Character used to create placeholder text for empty input fields",
valueType: "string",
defaultValue: defaultProps.emptyCharacter,
},
showWeekNumber: {
description: "Whether to show the week number (compatibility with DatePicker, not used in DateInput)",
valueType: "boolean",
defaultValue: defaultProps.showWeekNumber,
},
weekStartsOn: {
description: "The first day of the week. 0 is Sunday, 1 is Monday, etc. (compatibility with DatePicker, not used in DateInput)",
valueType: "number",
defaultValue: defaultProps.weekStartsOn,
availableValues: [
{
value: WeekDays.Sunday,
description: "Sunday",
},
{
value: WeekDays.Monday,
description: "Monday",
},
{
value: WeekDays.Tuesday,
description: "Tuesday",
},
{
value: WeekDays.Wednesday,
description: "Wednesday",
},
{
value: WeekDays.Thursday,
description: "Thursday",
},
{
value: WeekDays.Friday,
description: "Friday",
},
{
value: WeekDays.Saturday,
description: "Saturday",
},
],
},
minValue: {
description:
"The optional start date of the selectable date range. If not defined, the range " +
"allows any dates in the past.",
valueType: "string",
},
maxValue: {
description:
"The optional end date of the selectable date range. If not defined, the range allows " +
"any future dates.",
valueType: "string",
},
disabledDates: {
description: "An optional array of dates that are disabled (compatibility with DatePicker, not used in DateInput)",
valueType: "any",
},
inline: {
description: "Whether to display the date input inline (compatibility with DatePicker, always true for DateInput)",
valueType: "boolean",
defaultValue: defaultProps.inline,
},
clearable: {
description: "Whether to show a clear button to reset the input",
valueType: "boolean",
defaultValue: defaultProps.clearable,
},
clearIcon: {
description: "Icon name for the clear button",
valueType: "string",
},
clearToInitialValue: {
description: "Whether clearing resets to initial value or null",
valueType: "boolean",
defaultValue: defaultProps.clearToInitialValue,
},
gap: {
description: "The gap between input elements",
valueType: "string",
},
required: {
description: "Whether the input is required",
valueType: "boolean",
defaultValue: defaultProps.required,
},
startText: dStartText(),
startIcon: dStartIcon(),
endText: dEndText(),
endIcon: dEndIcon(),
},
events: {
didChange: dDidChange(COMP),
gotFocus: dGotFocus(COMP),
lostFocus: dLostFocus(COMP),
},
apis: {
focus: {
description: `Focus the ${COMP} component.`,
signature: "focus(): void",
},
value: {
description: `You can query the component's value. If no value is set, it will retrieve \`undefined\`.`,
signature: "get value(): any",
},
setValue: {
description: `This method sets the current value of the ${COMP}.`,
signature: "set value(value: any): void",
parameters: {
value: "The new value to set for the date input.",
},
},
isoValue: {
description: `Get the current date value formatted in ISO standard (YYYY-MM-DD) format, suitable for JSON serialization.`,
signature: "isoValue(): string | null",
},
},
themeVars: parseScssVar(styles.themeVars),
defaultThemeVars: {
// DateInput specific theme variables (matching TimeInput structure)
[`paddingHorizontal-${COMP}`]: "$space-2",
[`paddingVertical-${COMP}`]: "$space-2",
[`color-divider-${COMP}`]: "$textColor-secondary",
[`spacing-divider-${COMP}`]: "1px 0",
[`width-input-${COMP}`]: "1.8em",
[`minWidth-input-${COMP}`]: "0.54em",
[`padding-input-${COMP}`]: "0 2px",
[`textAlign-input-${COMP}`]: "center",
[`fontSize-input-${COMP}`]: "inherit",
[`borderRadius-input-${COMP}`]: "$borderRadius",
[`backgroundColor-input-${COMP}-invalid`]: "rgba(220, 53, 69, 0.15)",
[`padding-button-${COMP}`]: "4px 6px",
[`borderRadius-button-${COMP}`]: "$borderRadius",
[`hoverColor-button-${COMP}`]: "$color-surface-800",
[`disabledColor-button-${COMP}`]: "$textColor-disabled",
[`outlineColor-button-${COMP}--focused`]: "$color-accent-500",
[`outlineWidth-button-${COMP}--focused`]: "2px",
[`outlineOffset-button-${COMP}--focused`]: "2px",
[`minWidth-ampm-${COMP}`]: "2em",
[`fontSize-ampm-${COMP}`]: "inherit",
},
});
export const dateInputComponentRenderer = createComponentRenderer(
COMP,
DateInputMd,
({
node,
state,
updateState,
extractValue,
className,
lookupEventHandler,
registerComponentApi,
}) => {
return (
<DateInput
id={node.uid}
className={className}
mode={extractValue(node.props?.mode)}
value={state?.value}
initialValue={extractValue(node.props.initialValue)}
enabled={extractValue.asOptionalBoolean(node.props.enabled)}
validationStatus={extractValue(node.props.validationStatus)}
updateState={updateState}
onDidChange={lookupEventHandler("didChange")}
onFocus={lookupEventHandler("gotFocus")}
onBlur={lookupEventHandler("lostFocus")}
registerComponentApi={registerComponentApi}
dateFormat={extractValue(node.props.dateFormat)}
showWeekNumber={extractValue.asOptionalBoolean(node.props.showWeekNumber)}
weekStartsOn={extractValue.asOptionalNumber(node.props.weekStartsOn)}
minValue={extractValue.asOptionalString(node.props.minValue)}
maxValue={extractValue.asOptionalString(node.props.maxValue)}
disabledDates={extractValue(node.props.disabledDates)}
inline={extractValue.asOptionalBoolean(node.props.inline, defaultProps.inline)}
startText={extractValue.asOptionalString(node.props.startText)}
startIcon={extractValue.asOptionalString(node.props.startIcon)}
endText={extractValue.asOptionalString(node.props.endText)}
endIcon={extractValue.asOptionalString(node.props.endIcon)}
readOnly={extractValue.asOptionalBoolean(node.props.readOnly)}
autoFocus={extractValue.asOptionalBoolean(node.props.autoFocus)}
required={extractValue.asOptionalBoolean(node.props.required)}
clearable={extractValue.asOptionalBoolean(node.props.clearable, defaultProps.clearable)}
clearIcon={extractValue.asOptionalString(node.props.clearIcon)}
clearToInitialValue={extractValue.asOptionalBoolean(node.props.clearToInitialValue, defaultProps.clearToInitialValue)}
gap={extractValue.asOptionalString(node.props.gap)}
emptyCharacter={extractValue.asOptionalString(node.props.emptyCharacter)}
/>
);
},
);
```
--------------------------------------------------------------------------------
/xmlui/src/components/NestedApp/NestedApp.module.scss:
--------------------------------------------------------------------------------
```scss
@use "../../components-core/theming/themes" as t;
// --- This code snippet is required to collect the theme variables used in this module
$themeVars: (
);
@function createThemeVar($componentVariable) {
$themeVars: t.appendThemeVar($themeVars, $componentVariable) !global;
@return t.getThemeVar($themeVars, $componentVariable);
}
$component: "NestedApp";
$themeVars: t.composePaddingVars($themeVars, $component);
$themeVars: t.composeBorderVars($themeVars, $component);
$themeVars: t.composeTextVars($themeVars, "header-#{$component}");
$backgroundColor-frame-NestedApp: createThemeVar("backgroundColor-frame-#{$component}");
$gap-frame-NestedApp: createThemeVar("gap-frame-#{$component}");
$marginTop-NestedApp: createThemeVar("marginTop-#{$component}");
$marginBottom-NestedApp: createThemeVar("marginBottom-#{$component}");
$boxShadow-NestedApp: createThemeVar("boxShadow-#{$component}");
$backgroundColor-viewControls-NestedApp: createThemeVar("backgroundColor-viewControls-#{$component}"
);
$borderRadius-viewControls-NestedApp: createThemeVar("borderRadius-viewControls-#{$component}");
$padding-viewControls-NestedApp: createThemeVar("padding-viewControls-#{$component}");
$paddingVertical-viewControls-button-NestedApp: createThemeVar("paddingVertical-viewControls-button-#{$component}"
);
$paddingHorizontal-viewControls-button-NestedApp: createThemeVar("paddingHorizontal-viewControls-button-#{$component}"
);
$borderRadius-NestedApp: createThemeVar("borderRadius-#{$component}");
$borderBottom-header-NestedApp: createThemeVar("borderBottom-header-#{$component}");
$height-logo-splitView-NestedApp: createThemeVar("height-logo-splitView-#{$component}");
$width-logo-splitView-NestedApp: createThemeVar("width-logo-splitView-#{$component}");
$width-controls-NestedApp: createThemeVar("width-controls-#{$component}");
// --- Split view styles
$padding-button-splitView-NestedApp: createThemeVar("padding-button-splitView-#{$component}");
$width-button-splitView-NestedApp: createThemeVar("width-button-splitView-#{$component}");
$height-button-splitView-NestedApp: createThemeVar("height-button-splitView-#{$component}"
);
$backgroundColor-code-splitView-NestedApp: createThemeVar("backgroundColor-code-splitView-#{$component}"
);
$backgroundColor-button-splitView-NestedApp--active: createThemeVar("backgroundColor-button-splitView-#{$component}--active"
);
$color-button-splitView-NestedApp: createThemeVar("color-button-splitView-#{$component}"
);
$color-button-splitView-NestedApp--active: createThemeVar("color-button-splitView-#{$component}--active"
);
$borderRadius-button-splitView-NestedApp: createThemeVar("borderRadius-button-splitView-#{$component}"
);
$borderColor-button-splitView-NestedApp: createThemeVar("borderColor-button-splitView-#{$component}"
);
$color-loadingText-NestedApp: createThemeVar("color-loadingText-#{$component}");
$border-NestedApp: createThemeVar("border-#{$component}");
@layer components {
.nestedAppPlaceholder {
width: 100%;
height: 100%;
background-color: t.$backgroundColor;
position: relative;
border-radius: $borderRadius-NestedApp;
}
.loadingContainer {
position: absolute;
left: 50%;
top: calc(50% - 25px);
transform: translateX(-50%);
/* Center perfectly on the X axis */
display: flex;
align-items: center;
justify-content: center;
/* Center the content horizontally */
width: 300px;
height: 50px;
}
.logoWrapper {
width: 50px;
height: 50px;
transform: translateZ(0);
.animatedLogoPath {
will-change: stroke-dashoffset, stroke-dasharray;
/* The total length of the rectangle path is ~82. */
stroke-dasharray: 82;
stroke-dashoffset: 82;
/* Animation: name, duration, easing, and iteration count */
animation: draw-loop 3s ease-in-out infinite;
}
}
.loadingText {
margin-right: 12px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
font-size: 16px;
/* Increased by 20% from 16px */
font-weight: 500;
color: $color-loadingText-NestedApp;
text-align: right;
}
/* Defines the animation steps */
@keyframes draw-loop {
/* At the start, the path is not drawn */
0% {
stroke-dashoffset: 82;
}
/* Animate to fully drawn over 40% of the duration */
40% {
stroke-dashoffset: 0;
}
/* Hold the fully drawn state until the end */
100% {
stroke-dashoffset: 0;
}
}
.nestedAppRoot {
width: 100%;
height: 100%;
position: relative;
isolation: isolate;
background-color: t.$backgroundColor;
border-radius: 0 0 $borderRadius-NestedApp $borderRadius-NestedApp;
&.shouldAnimate {
transition: clip-path 1s, opacity 1s;
clip-path: inset(50% round 50%);
&:not(.initialized) {
opacity: 0;
}
&.initialized {
opacity: 1;
clip-path: inset(0% round 0%);
}
}
}
// --- End of theme variables collection
.nestedAppContainer {
@include t.paddingVars($themeVars, $component);
//https://stackoverflow.com/a/70422489
//https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_display/Containing_block#identifying_the_containing_block
//" ...If the position property is absolute or fixed, the containing block may also be formed by the edge of the padding box of the nearest ancestor element that has any of the following:
// A filter, backdrop-filter, transform, or perspective value other than none. ..."
// so we do this to make the position 'fixed' elements to be positioned relative to the nestedAppContainer
//transform: scale(1);
margin-top: $marginTop-NestedApp;
margin-bottom: $marginBottom-NestedApp;
width: 100%;
height: fit-content;
display: flex;
border: $border-NestedApp;
border-radius: $borderRadius-NestedApp;
flex-direction: column;
gap: $gap-frame-NestedApp;
background-color: $backgroundColor-frame-NestedApp;
align-content: center;
box-shadow: $boxShadow-NestedApp;
.contentContainer {
width: 100%;
flex: 1;
min-height: 0;
display: flex;
align-items: center;
.splitViewMarkdown {
height: 100%;
:global(.global-codeBlock) {
background-color: $backgroundColor-code-splitView-NestedApp;
height: 100%;
border: none;
border-radius: 0;
}
}
}
}
.hidden {
display: none;
}
:is(html[class~="dark"]) {
.preview {
border: 1px solid #33404f;
}
}
.preview {
overflow: hidden;
}
.viewControls {
display: flex;
flex-direction: row;
align-items: center;
padding: $padding-viewControls-NestedApp;
background-color: $backgroundColor-viewControls-NestedApp;
border-radius: $borderRadius-viewControls-NestedApp;
}
.logo {
width: $width-logo-splitView-NestedApp;
height: $height-logo-splitView-NestedApp;
}
.splitViewButton {
padding: $padding-button-splitView-NestedApp !important;
width: $width-button-splitView-NestedApp !important;
height: $height-button-splitView-NestedApp !important;
color: $color-button-splitView-NestedApp !important;
border-radius: $borderRadius-button-splitView-NestedApp !important;
border-color: $borderColor-button-splitView-NestedApp !important;
&.show {
background-color: $backgroundColor-button-splitView-NestedApp--active;
color: $color-button-splitView-NestedApp--active !important;
&:hover {
background-color: $backgroundColor-button-splitView-NestedApp--active !important;
}
}
&.hide {
background-color: transparent;
&:hover {
background-color: transparent !important;
}
}
}
.header {
width: 100%;
display: flex;
align-items: center;
padding: 0 t.$space-2;
border-bottom: $borderBottom-header-NestedApp;
justify-content: space-between;
.headerText {
@include t.textVars($themeVars, "header-#{$component}");
padding-left: t.$space-2;
margin-right: auto;
}
.headerButton {
padding: t.$space-2 t.$space-3;
}
.spacer {
flex: 1 1 0 !important;
place-self: stretch;
}
}
.wrapper {
width: $width-controls-NestedApp;
}
.shadowRoot {
// "My solution was to add a dummy transform property so that all elements with fixed position got attached to their parent div." see: https://stackoverflow.com/a/70422489
transform: scale(1);
width: 100%;
height: 100%;
position: relative;
isolation: isolate;
background-color: transparent;
border-radius: $borderRadius-NestedApp;
}
.content {
width: 100%;
height: 100%;
overflow: hidden;
border-radius: $borderRadius-NestedApp;
}
}
// --- We export the theme variables to add them to the component renderer
:export {
themeVars: t.json-stringify($themeVars);
}
```
--------------------------------------------------------------------------------
/xmlui/src/components/App/App.spec.ts:
--------------------------------------------------------------------------------
```typescript
import { expect, test } from "../../testing/fixtures";
// =============================================================================
// BASIC FUNCTIONALITY TESTS
// =============================================================================
test.describe("Basic Functionality", () => {
test("renders with basic props", async ({ initTestBed, page }) => {
await initTestBed(`<App name="Test App" testId="app"/>`);
await expect(page.getByTestId("app")).toBeVisible();
});
test("renders with different layout types", async ({ initTestBed, page }) => {
await initTestBed(`<App layout="horizontal" testId="app"/>`);
await expect(page.getByTestId("app")).toBeVisible();
await initTestBed(`<App layout="horizontal-sticky" testId="app"/>`);
await expect(page.getByTestId("app")).toBeVisible();
await initTestBed(`<App layout="condensed" testId="app"/>`);
await expect(page.getByTestId("app")).toBeVisible();
await initTestBed(`<App layout="condensed-sticky" testId="app"/>`);
await expect(page.getByTestId("app")).toBeVisible();
await initTestBed(`<App layout="vertical" testId="app"/>`);
await expect(page.getByTestId("app")).toBeVisible();
await initTestBed(`<App layout="vertical-sticky" testId="app"/>`);
await expect(page.getByTestId("app")).toBeVisible();
await initTestBed(`<App layout="vertical-full-header" testId="app"/>`);
await expect(page.getByTestId("app")).toBeVisible();
});
test("handles layout prop changes correctly", async ({
page,
initTestBed,
createButtonDriver,
}) => {
await initTestBed(`
<App var.lo="vertical" layout="{lo}" testId="app">
<Button testId="toggleLayout" label="Toggle" onClick="lo = 'horizontal'" />
</App>
`);
const buttonDriver = await createButtonDriver("toggleLayout");
await expect(page.getByTestId("app")).toHaveClass(/vertical/);
await buttonDriver.click();
await expect(page.getByTestId("app")).toHaveClass(/horizontal/);
});
test("sets document title from name prop", async ({ initTestBed, page }) => {
const APP_NAME = "My Test Application";
await initTestBed(`<App name="${APP_NAME}" testId="app"/>`);
await expect(page.getByTestId("app")).toBeVisible();
expect(await page.title()).toBe(APP_NAME);
});
test("handles different visual states with scrolling options", async ({ initTestBed, page }) => {
// Test with scrollWholePage=true
await initTestBed(`<App scrollWholePage="true" testId="app"/>`);
await expect(page.getByTestId("app")).toHaveClass(/scrollWholePage/);
// Test with scrollWholePage=false
await initTestBed(`<App scrollWholePage="false" testId="app"/>`);
await expect(page.getByTestId("app")).not.toHaveClass(/scrollWholePage/);
});
});
// =============================================================================
// EDGE CASE TESTS
// =============================================================================
test.describe("Edge Cases", () => {
test("handles undefined props gracefully", async ({ initTestBed, page }) => {
await initTestBed(`<App testId="app" />`);
await expect(page.getByTestId("app")).toBeVisible();
// App should use a default layout
await expect(page.getByTestId("app")).toHaveClass(/horizontal/);
});
test("works correctly with basic content structure", async ({ initTestBed, page }) => {
await initTestBed(`
<App testId="app">
<Text testId="content">Content</Text>
</App>`);
await expect(page.getByTestId("app")).toBeVisible();
await expect(page.getByTestId("content")).toBeVisible();
await expect(page.getByText("Content")).toBeVisible();
});
test("works correctly with complex content structure", async ({ initTestBed, page }) => {
await initTestBed(`
<App testId="app">
<AppHeader testId="header">Header Content</AppHeader>
<NavPanel testId="nav">
<NavLink testId="link1" to="#">Link 1</NavLink>
<NavGroup testId="group" label="Group">
<NavLink testId="nestedLink" to="#">Nested Link</NavLink>
</NavGroup>
</NavPanel>
<Pages fallbackPath="/">
<Page url="/" testId="homePage">
<Text testId="homeContent">Home Content</Text>
</Page>
</Pages>
<Footer testId="footer">Footer Content</Footer>
</App>`);
await expect(page.getByTestId("app")).toBeVisible();
// Test component structure
await expect(page.getByTestId("header")).toBeVisible();
await expect(page.getByTestId("nav")).toBeVisible();
await expect(page.getByTestId("footer")).toBeVisible();
// Test content
await expect(page.getByText("Header Content")).toBeVisible();
await expect(page.getByText("Link 1")).toBeVisible();
await expect(page.getByText("Group")).toBeVisible();
await expect(page.getByText("Home Content")).toBeVisible();
await expect(page.getByText("Footer Content")).toBeVisible();
});
});
// =============================================================================
// EVENT HANDLING TESTS
// =============================================================================
test.describe("Event Handling", () => {
test("ready event is triggered when App component finishes rendering", async ({
initTestBed,
}) => {
const { testStateDriver } = await initTestBed(`
<App
onReady="() => testState = 'app-ready'"
testId="app"
/>
`);
// Verify the ready event was fired
await expect.poll(testStateDriver.testState).toEqual("app-ready");
});
test("ready event is triggered for App with complex content", async ({ initTestBed }) => {
const { testStateDriver } = await initTestBed(`
<App
onReady="() => testState = 'complex-app-ready'"
layout="horizontal"
testId="app"
>
<AppHeader>
<property name="logoTemplate">
<Text value="Test App" />
</property>
</AppHeader>
<Pages fallbackPath="/">
<Page url="/">
<Text value="Home Page" />
</Page>
</Pages>
<Footer>
<Text value="Footer Content" />
</Footer>
</App>
`);
// Verify the ready event was fired even with complex content
await expect.poll(testStateDriver.testState).toEqual("complex-app-ready");
});
test("ready event fires only once during component lifecycle", async ({ initTestBed, page }) => {
const { testStateDriver } = await initTestBed(`
<App
var.counter="{0}"
onReady="() => { counter = counter + 1; testState = counter; }"
testId="app"
>
<Button
testId="trigger-rerender"
label="Re-render"
onClick="counter = counter"
/>
</App>
`);
// Initial ready event should fire
await expect.poll(testStateDriver.testState).toEqual(1);
// Trigger a re-render by clicking the button
await page.getByTestId("trigger-rerender").click();
// Counter should still be 1 (ready event doesn't fire again)
await expect.poll(testStateDriver.testState).toEqual(1);
});
test("messageReceived event is triggered when window receives a message", async ({
initTestBed,
page,
}) => {
const { testStateDriver } = await initTestBed(`
<App
onMessageReceived="(msg, ev) => testState = msg"
testId="app"
/>
`);
// Send a message to the window
await page.evaluate(() => {
window.postMessage("test-message", "*");
});
// Verify the event was received and handled
await expect.poll(testStateDriver.testState).toEqual("test-message");
});
test("messageReceived event receives both message data and event object", async ({
initTestBed,
page,
}) => {
const { testStateDriver } = await initTestBed(`
<App
onMessageReceived="(msg, ev) => testState = { message: msg, eventType: ev.type, origin: ev.origin }"
testId="app"
/>
`);
// Send a message to the window
await page.evaluate(() => {
window.postMessage("event-test", "*");
});
// Verify both parameters are accessible
await expect.poll(testStateDriver.testState).toMatchObject({
message: "event-test",
eventType: "message",
origin: expect.any(String),
});
});
test("messageReceived event handles complex data objects", async ({ initTestBed, page }) => {
const { testStateDriver } = await initTestBed(`
<App
onMessageReceived="(msg, ev) => testState = msg"
testId="app"
/>
`);
// Send a complex object as message data
const testData = { action: "test", payload: { id: 123, name: "test-item" } };
await page.evaluate((data) => {
window.postMessage(data, "*");
}, testData);
// Verify the complex data is received correctly
await expect.poll(testStateDriver.testState).toEqual(testData);
});
});
```