This is page 39 of 141. Use http://codebase.md/xmlui-org/xmlui/xmlui-standalone.umd.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
│ ├── package.json
│ ├── public
│ │ ├── blog
│ │ │ ├── images
│ │ │ │ ├── an-advanced-codefence.gif
│ │ │ │ ├── an-advanced-codefence.mp4
│ │ │ │ ├── blog-page-component.png
│ │ │ │ ├── blog-scrabble.png
│ │ │ │ ├── codefence-runner.png
│ │ │ │ ├── integrated-blog-search.png
│ │ │ │ ├── lorem-ipsum.png
│ │ │ │ ├── playground-checkbox-source.png
│ │ │ │ ├── playground.png
│ │ │ │ ├── use-xmlui-mcp-to-find-a-howto.png
│ │ │ │ └── xmlui-demo-gallery.png
│ │ │ ├── introducing-xmlui.md
│ │ │ ├── lorem-ipsum.md
│ │ │ ├── newest-post.md
│ │ │ ├── older-post.md
│ │ │ ├── xmlui-playground.md
│ │ │ └── xmlui-powered-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
│ │ │ │ ├── control-cache-invalidation.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
│ ├── tsconfig.json
│ ├── 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
│ ├── 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
│ │ └── 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
│ ├── 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
│ ├── 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
│ ├── 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
│ ├── 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
│ ├── xmlui-spreadsheet
│ │ ├── .gitignore
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── index.tsx
│ │ ├── Spreadsheet.tsx
│ │ └── SpreadsheetNative.tsx
│ └── 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.spec.ts
│ │ ├── HeroSection.tsx
│ │ └── HeroSectionNative.tsx
│ ├── index.tsx
│ ├── ScrollToTop
│ │ ├── ScrollToTop.module.scss
│ │ ├── ScrollToTop.tsx
│ │ └── ScrollToTopNative.tsx
│ └── vite-env.d.ts
├── playwright.config.ts
├── 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.cjs
│ ├── 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
├── 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.js
│ ├── 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.js
│ ├── 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
│ │ │ ├── index.ts
│ │ │ ├── ModalDialogDriver.ts
│ │ │ ├── NumberBoxDriver.ts
│ │ │ ├── TextBoxDriver.ts
│ │ │ ├── TimeInputDriver.ts
│ │ │ ├── TimerDriver.ts
│ │ │ └── TreeDriver.ts
│ │ ├── fixtures.ts
│ │ ├── index.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/Text/Text.tsx:
--------------------------------------------------------------------------------
```typescript
import styles from "./Text.module.scss";
import { createComponentRenderer } from "../../components-core/renderers";
import { parseScssVar } from "../../components-core/theming/themeVars";
import {
variantOptionsMd,
type VariantProps,
VariantPropsKeys,
type OverflowMode,
type BreakMode,
} from "../abstractions";
import { Text, defaultProps } from "./TextNative";
import { createMetadata, d } from "../metadata-helpers";
const COMP = "Text";
export const TextMd = createMetadata({
status: "stable",
description:
`The \`${COMP}\` component displays textual information in a number of optional ` +
`styles and variants.`,
props: {
value: d(
`The text to be displayed. This value can also be set via nesting the text into ` +
`the \`${COMP}\` component.`,
),
variant: {
description:
"An optional string value that provides named presets for text variants with a " +
"unique combination of font style, weight, size, color, and other parameters. " +
"If not defined, the text uses the current style of its context. " +
"In addition to predefined variants, you can specify custom variant names and style them " +
"using theme variables with the pattern `{cssProperty}-Text-{variantName}` " +
"(e.g., `textColor-Text-brandTitle`, `fontSize-Text-highlight`). " +
"See the documentation for a complete list of supported CSS properties.",
availableValues: variantOptionsMd,
},
maxLines: d(
"This property determines the maximum number of lines the component can wrap to. " +
"If there is no space to display all the contents, the component displays up to as " +
"many lines as specified in this property. When the value is not defined, there is " +
"no limit on the displayed lines.",
),
preserveLinebreaks: {
description: `This property indicates if linebreaks should be preserved when displaying text.`,
valueType: "boolean",
defaultValue: defaultProps.preserveLinebreaks,
},
ellipses: {
description:
"This property indicates whether ellipses should be displayed when the text is " +
"cropped (\`true\`) or not (\`false\`).",
valueType: "boolean",
defaultValue: defaultProps.ellipses,
},
breakMode: {
description:
"This property controls how text breaks into multiple lines. " +
"`normal` uses standard word boundaries, `word` breaks long words to prevent overflow, " +
"`anywhere` breaks at any character, `keep` prevents word breaking, " +
"and `hyphenate` uses automatic hyphenation. When not specified, uses the default browser behavior or theme variables.",
valueType: "string",
defaultValue: "normal",
availableValues: [
{ value: "normal", description: "Uses standard word boundaries for breaking" },
{ value: "word", description: "Breaks long words when necessary to prevent overflow" },
{ value: "anywhere", description: "Breaks at any character if needed to fit content" },
{ value: "keep", description: "Prevents breaking within words entirely" },
{ value: "hyphenate", description: "Uses automatic hyphenation when breaking words" },
],
},
overflowMode: {
description:
"This property controls how text overflow is handled. " +
"`none` prevents wrapping and shows no overflow indicator, " +
"`ellipsis` shows ellipses when text is truncated, `scroll` forces single line with horizontal scrolling, " +
"and `flow` allows multi-line wrapping with vertical scrolling when needed (ignores maxLines). " +
"When not specified, uses the default text behavior.",
valueType: "string",
defaultValue: "not specified",
availableValues: [
{
value: "none",
description: "No wrapping, text stays on a single line with no overflow indicator",
},
{ value: "ellipsis", description: "Truncates with an ellipsis (default)" },
{
value: "scroll",
description: "Forces single line with horizontal scrolling when content overflows",
},
{
value: "flow",
description:
"Allows text to wrap into multiple lines with vertical scrolling when container height is constrained (ignores maxLines)",
},
],
},
},
apis: {
hasOverflow: {
description: "Returns true when the displayed text overflows its container boundaries.",
signature: "hasOverflow(): boolean",
},
},
themeVars: parseScssVar(styles.themeVars),
defaultThemeVars: {
[`borderRadius-${COMP}`]: "$borderRadius",
[`borderStyle-${COMP}`]: "solid",
[`fontSize-${COMP}`]: "$fontSize-sm",
[`borderWidth-${COMP}`]: "$space-0",
[`lineHeight-${COMP}-codefence`]: "1.5",
[`fontWeight-${COMP}-abbr`]: "$fontWeight-bold",
[`textTransform-${COMP}-abbr`]: "uppercase",
[`fontSize-${COMP}-secondary`]: "$fontSize-sm",
[`fontStyle-${COMP}-cite`]: "italic",
[`textColor-${COMP}`]: "$textColor-primary",
[`fontFamily-${COMP}`]: "$fontFamily",
[`fontWeight-${COMP}`]: "$fontWeight-normal",
[`fontSize-${COMP}-codefence`]: "$fontSize-code",
[`fontFamily-${COMP}-code`]: "$fontFamily-monospace",
[`fontSize-${COMP}-code`]: "$fontSize-sm",
[`borderWidth-${COMP}-code`]: "1px",
[`borderStyle-${COMP}-code`]: "solid",
[`borderRadius-${COMP}-code`]: "4px",
[`paddingHorizontal-${COMP}-code`]: "$space-0_5",
[`paddingBottom-${COMP}-code`]: "2px",
[`textDecorationLine-${COMP}-deleted`]: "line-through",
[`textDecorationLine-${COMP}-inserted`]: "underline",
[`fontFamily-${COMP}-keyboard`]: "$fontFamily-monospace",
[`fontSize-${COMP}-keyboard`]: "$fontSize-sm",
[`fontWeight-${COMP}-keyboard`]: "$fontWeight-bold",
[`borderWidth-${COMP}-keyboard`]: "1px",
[`paddingHorizontal-${COMP}-keyboard`]: "$space-1",
[`fontFamily-${COMP}-sample`]: "$fontFamily-monospace",
[`fontSize-${COMP}-sample`]: "$fontSize-sm",
[`fontSize-${COMP}-sup`]: "$fontSize-xs",
[`verticalAlignment-${COMP}-sup`]: "super",
[`fontSize-${COMP}-sub`]: "$fontSize-xs",
[`verticalAlignment-${COMP}-sub`]: "sub",
[`fontStyle-${COMP}-var`]: "italic",
[`fontStyle-${COMP}-em`]: "italic",
[`fontFamily-${COMP}-mono`]: "$fontFamily-monospace",
[`fontSize-${COMP}-title`]: "$fontSize-2xl",
[`fontSize-${COMP}-subtitle`]: "$fontSize-xl",
[`fontSize-${COMP}-small`]: "$fontSize-sm",
[`letterSpacing-${COMP}-caption`]: "0.05rem",
[`fontSize-${COMP}-placeholder`]: "$fontSize-xs",
[`fontFamily-${COMP}-codefence`]: "$fontFamily-monospace",
[`paddingHorizontal-${COMP}-codefence`]: "$space-4",
[`paddingVertical-${COMP}-codefence`]: "$space-3",
[`paddingVertical-${COMP}-paragraph`]: "$space-1",
[`fontSize-${COMP}-subheading`]: "$fontSize-H6",
[`fontWeight-${COMP}-subheading`]: "$fontWeight-bold",
[`letterSpacing-${COMP}-subheading`]: "0.04em",
[`textTransform-${COMP}-subheading`]: "uppercase",
[`marginTop-${COMP}-tableheading`]: "$space-1",
[`marginBottom-${COMP}-tableheading`]: "$space-4",
[`paddingHorizontal-${COMP}-tableheading`]: "$space-1",
[`fontSize-${COMP}-tableheading`]: "$fontSize-H6",
[`fontWeight-${COMP}-tableheading`]: "$fontWeight-bold",
[`backgroundColor-${COMP}-code`]: "rgb(from $color-surface-100 r g b / 0.4)",
[`borderColor-${COMP}-code`]: "$color-surface-100",
[`backgroundColor-${COMP}-keyboard`]: "rgb(from $color-surface-100 r g b / 0.4)",
[`borderColor-${COMP}-keyboard`]: "$color-surface-300",
[`backgroundColor-${COMP}-marked`]: "rgb(from $color-primary-200 r g b / 0.4)",
[`textColor-${COMP}-placeholder`]: "$color-surface-500",
[`textColor-${COMP}-codefence`]: "$color-surface-900",
[`textColor-${COMP}-subheading`]: "$textColor-secondary",
[`textColor-${COMP}-secondary`]: "$textColor-secondary",
dark: {
[`backgroundColor-${COMP}-marked`]: "rgb(from $color-primary-400 r g b / 0.4)",
},
},
});
export const textComponentRenderer = createComponentRenderer(
COMP,
TextMd,
({ node, extractValue, className, renderChild, registerComponentApi }) => {
const {
variant,
maxLines,
preserveLinebreaks,
ellipses,
overflowMode,
breakMode,
value,
...variantSpecific
} = node.props;
const variantSpecificProps: VariantProps = Object.fromEntries(
Object.entries(variantSpecific)
.filter(([key, _]) => VariantPropsKeys.includes(key as any))
.map(([key, value]) => [key, extractValue(value)]),
);
return (
<Text
variant={extractValue(variant)}
maxLines={extractValue.asOptionalNumber(maxLines)}
className={className}
preserveLinebreaks={extractValue.asOptionalBoolean(
preserveLinebreaks,
defaultProps.preserveLinebreaks,
)}
ellipses={extractValue.asOptionalBoolean(ellipses, defaultProps.ellipses)}
overflowMode={extractValue(overflowMode) as OverflowMode | undefined}
breakMode={extractValue(breakMode) as BreakMode | undefined}
registerComponentApi={registerComponentApi}
{...variantSpecificProps}
>
{extractValue.asDisplayText(value) || renderChild(node.children)}
</Text>
);
},
);
```
--------------------------------------------------------------------------------
/docs/content/components/Switch.md:
--------------------------------------------------------------------------------
```markdown
# Switch [#switch]
`Switch` enables users to toggle between two states: on and off.
## Switch Values [#switch-values]
The `initialValue` and `value` properties of the switch are transformed to a Boolean value to display the on (`true`) or off (`false`) state with this logic:
- `null` and `undefined` go to `false`.
- If the property is Boolean, the property value is used as is.
- If it is a number, `NaN` and `0` result in `false`; other values represent `true`.
- If the property is a string, the empty string and the literal "false" string result in `false`; others result in `true`.
- The empty array value goes to `false`; other array values result in `true`.
- Object values with no properties result in `false`; other values represent `true`.
## Properties [#properties]
### `autoFocus` (default: false) [#autofocus-default-false]
If this property is set to `true`, the component gets the focus automatically when displayed.
### `enabled` (default: true) [#enabled-default-true]
This boolean property value indicates whether the component responds to user events (`true`) or not (`false`).
This boolean property indicates whether the checkbox responds to user events (i.e. clicks);
it is `true` by default.
```xmlui-pg copy {4-5, 9-10} display name="Example: enabled"
<App>
Enabled switches:
<HStack>
<Switch initialValue="true" enabled="true" />
<Switch initialValue="false" enabled="true" />
</HStack>
Disabled switches:
<HStack>
<Switch initialValue="true" enabled="false" />
<Switch initilaValue="false" enabled="false" />
</HStack>
</App>
```
### `initialValue` (default: false) [#initialvalue-default-false]
This property sets the component's initial value.
### `readOnly` (default: false) [#readonly-default-false]
Set this property to `true` to disallow changing the component value.
If true, the value of the component cannot be modified.
```xmlui-pg copy display name="Example: readOnly"
<App>
<Switch readOnly="true" label="Checked" initialValue="true" />
<Switch readOnly="true" label="Unchecked" intialValue="false" />
</App>
```
### `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]
### `click` [#click]
This event is triggered when the Switch is clicked.
### `didChange` [#didchange]
This event is triggered when value of Switch has changed.
This event is triggered when the `Switch` is toggled due to user interaction.
A read-only switch never fires this event, and it won't fire if the switch's value is set programmatically.
```xmlui-pg copy display name="Example: didChange"
<App verticalAlignment="center" var.changes="">
<Switch label="Changeable" onDidChange="changes += '+'" />
<Switch label="Readonly" readOnly="true" onDidChange="changes += '-'" />
<Text value="Changes: {changes}" />
</App>
```
### `gotFocus` [#gotfocus]
This event is triggered when the Switch has received the focus.
This event is triggered when the `Switch` receives focus.
Click the `Switch` in the example demo to change the label text. Note how clicking elsewhere resets the text to the original.
```xmlui-pg copy {4,5} display name="Example: gotFocus"
<App var.focused="{false}" verticalAlignment="center">
<Switch
value="true"
onGotFocus="focused = true"
onLostFocus="focused = false"
/>
<Text value="{focused === true ? 'I am focused!' : 'I have lost the focus!'}" />
</App>
```
### `lostFocus` [#lostfocus]
This event is triggered when the Switch has lost the focus.
## Exposed Methods [#exposed-methods]
### `setValue` [#setvalue]
This API sets the value of the `Switch`. You can use it to programmatically change the value.
**Signature**: `setValue(value: boolean): void`
- `value`: The new value to set. Can be either true (on) or false (off).
```xmlui-pg copy {10,13,15} display name="Example: value and setValue"
<App var.changes="">
<Switch
id="mySwitch"
readOnly="true"
label="This switch can be set only programmatically"
onDidChange="changes += '+'" />
<HStack>
<Button
label="Check"
onClick="mySwitch.setValue(true)" />
<Button
label="Uncheck"
onClick="mySwitch.setValue(false)" />
</HStack>
<Text>The switch is {checkbox.value ? "checked" : "unchecked"}</Text>
<Text value="Changes: {changes}" />
</App>
```
### `value` [#value]
This property holds the current value of the Switch, which can be either "true" (on) or "false" (off).
**Signature**: `get value():boolean`
## Parts [#parts]
The component has some parts that can be styled through layout properties and theme variables separately:
- **`input`**: The switch input area.
- **`label`**: The label displayed for the switch.
## Styling [#styling]
### Theme Variables [#theme-variables]
| Variable | Default Value (Light) | Default Value (Dark) |
| --- | --- | --- |
| [backgroundColor](../styles-and-themes/common-units/#color)-checked-Switch | $color-primary-500 | $color-primary-500 |
| [backgroundColor](../styles-and-themes/common-units/#color)-checked-Switch | $color-primary-500 | $color-primary-500 |
| [backgroundColor](../styles-and-themes/common-units/#color)-checked-Switch-error | $borderColor-Switch-error | $borderColor-Switch-error |
| [backgroundColor](../styles-and-themes/common-units/#color)-checked-Switch-error | $borderColor-Switch-error | $borderColor-Switch-error |
| [backgroundColor](../styles-and-themes/common-units/#color)-checked-Switch-success | $borderColor-Switch-success | $borderColor-Switch-success |
| [backgroundColor](../styles-and-themes/common-units/#color)-checked-Switch-success | $borderColor-Switch-success | $borderColor-Switch-success |
| [backgroundColor](../styles-and-themes/common-units/#color)-checked-Switch-warning | $borderColor-Switch-warning | $borderColor-Switch-warning |
| [backgroundColor](../styles-and-themes/common-units/#color)-checked-Switch-warning | $borderColor-Switch-warning | $borderColor-Switch-warning |
| [backgroundColor](../styles-and-themes/common-units/#color)-indicator-checked-Switch | $backgroundColor-primary | $backgroundColor-primary |
| [backgroundColor](../styles-and-themes/common-units/#color)-indicator-Switch | $color-surface-400 | $color-surface-500 |
| [backgroundColor](../styles-and-themes/common-units/#color)-Switch | $color-surface-0 | $color-surface-0 |
| [backgroundColor](../styles-and-themes/common-units/#color)-Switch | $color-surface-0 | $color-surface-0 |
| [backgroundColor](../styles-and-themes/common-units/#color)-Switch--disabled | $color-surface-200 | $color-surface-200 |
| [backgroundColor](../styles-and-themes/common-units/#color)-Switch--disabled | $color-surface-200 | $color-surface-200 |
| [backgroundColor](../styles-and-themes/common-units/#color)-Switch-indicator--disabled | $backgroundColor-primary | $backgroundColor-primary |
| [borderColor](../styles-and-themes/common-units/#color)-checked-Switch | $color-primary-500 | $color-primary-500 |
| [borderColor](../styles-and-themes/common-units/#color)-checked-Switch | $color-primary-500 | $color-primary-500 |
| [borderColor](../styles-and-themes/common-units/#color)-checked-Switch-error | $borderColor-Switch-error | $borderColor-Switch-error |
| [borderColor](../styles-and-themes/common-units/#color)-checked-Switch-error | $borderColor-Switch-error | $borderColor-Switch-error |
| [borderColor](../styles-and-themes/common-units/#color)-checked-Switch-success | $borderColor-Switch-success | $borderColor-Switch-success |
| [borderColor](../styles-and-themes/common-units/#color)-checked-Switch-success | $borderColor-Switch-success | $borderColor-Switch-success |
| [borderColor](../styles-and-themes/common-units/#color)-checked-Switch-warning | $borderColor-Switch-warning | $borderColor-Switch-warning |
| [borderColor](../styles-and-themes/common-units/#color)-checked-Switch-warning | $borderColor-Switch-warning | $borderColor-Switch-warning |
| [borderColor](../styles-and-themes/common-units/#color)-Switch | $color-surface-200 | $color-surface-200 |
| [borderColor](../styles-and-themes/common-units/#color)-Switch | $color-surface-200 | $color-surface-200 |
| [borderColor](../styles-and-themes/common-units/#color)-Switch--disabled | *none* | *none* |
| [borderColor](../styles-and-themes/common-units/#color)-Switch-default--hover | *none* | *none* |
| [borderColor](../styles-and-themes/common-units/#color)-Switch-error | *none* | *none* |
| [borderColor](../styles-and-themes/common-units/#color)-Switch-success | *none* | *none* |
| [borderColor](../styles-and-themes/common-units/#color)-Switch-warning | *none* | *none* |
| [borderWidth](../styles-and-themes/common-units/#size)-Switch | 1px | 1px |
| [outlineColor](../styles-and-themes/common-units/#color)-Switch--focus | *none* | *none* |
| [outlineOffset](../styles-and-themes/common-units/#size)-Switch--focus | *none* | *none* |
| [outlineStyle](../styles-and-themes/common-units/#border)-Switch--focus | *none* | *none* |
| [outlineWidth](../styles-and-themes/common-units/#size)-Switch--focus | *none* | *none* |
```
--------------------------------------------------------------------------------
/xmlui/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "xmlui",
"version": "0.11.0",
"sideEffects": false,
"type": "module",
"scripts": {
"start-test-bed": "cd src/testing/infrastructure && xmlui start",
"build:xmlui-test-bed": "cd src/testing/infrastructure && xmlui build --build-mode=INLINE_ALL --withHostingMetaFiles --withMock",
"build:bin": "tsc -p tsconfig.bin.json",
"build:xmlui": "vite build --mode lib && ncc build bin/vite-xmlui-plugin.ts -o dist/lib/vite-xmlui-plugin",
"build:xmlui-standalone": "vite build --mode standalone",
"build:xmlui-metadata": "vite build --mode metadata",
"test:unit": "vitest run",
"test:e2e": "playwright test",
"test:e2e-summary": "node scripts/e2e-test-summary.js",
"lint": "eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .",
"prepublishOnly": "clean-package",
"postpublish": "clean-package restore",
"generate-docs-summaries": "node scripts/generate-docs/generate-summary-files.mjs",
"generate-docs": "node scripts/generate-docs/get-docs.mjs",
"generate-all-docs": "npm run build:xmlui-metadata && npm run generate-docs && npm run generate-docs-summaries",
"export-themes": "npm run build:xmlui-metadata && node scripts/generate-docs/create-theme-files.mjs",
"generate-docs-with-refresh": "npm run build:xmlui-metadata && npm run generate-docs && npm run generate-docs-summaries",
"gen:langserver-metadata": "node scripts/get-langserver-metadata.js > src/language-server/xmlui-metadata-generated.js"
},
"dependencies": {
"@eslint-community/regexpp": "4.10.0",
"@formkit/auto-animate": "0.7.0",
"@internationalized/number": "^3.6.0",
"@modyfi/vite-plugin-yaml": "1.1.0",
"@monaco-editor/loader": "^1.5.0",
"@popperjs/core": "2.11.6",
"@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-alert-dialog": "^1.1.2",
"@radix-ui/react-dialog": "1.0.5",
"@radix-ui/react-dropdown-menu": "2.1.16",
"@radix-ui/react-focus-scope": "1.0.4",
"@radix-ui/react-hover-card": "1.0.7",
"@radix-ui/react-popover": "^1.1.2",
"@radix-ui/react-radio-group": "1.1.3",
"@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-slider": "^1.2.3",
"@radix-ui/react-tabs": "1.1.0",
"@radix-ui/react-tooltip": "^1.2.4",
"@radix-ui/react-visually-hidden": "1.0.3",
"@react-spring/web": "^10.0.2",
"@remix-run/react": "2.12.1",
"@tanstack/react-query": "^4.36.1",
"@tanstack/react-query-devtools": "^4.36.1",
"@tanstack/react-table": "8.17.3",
"@tanstack/react-virtual": "3.10.8",
"@types/chroma-js": "^3.1.1",
"@types/color": "3.0.6",
"@vitejs/plugin-react": "4.3.0",
"adm-zip": "0.5.10",
"axios": "1.12.0",
"chroma-js": "^3.1.2",
"classnames": "2.5.1",
"cmdk": "^1.0.4",
"color": "4.2.3",
"date-fns": "2.30.0",
"dexie": "3.2.4",
"dotenv": "16.3.1",
"embla-carousel-autoplay": "^8.3.0",
"embla-carousel-react": "^8.3.0",
"emoji-picker-react": "4.4.10",
"file-saver": "^2.0.5",
"framer-motion": "^12.18.1",
"glob": "7.2.0",
"immer": "9.0.16",
"js-yaml": "^4.1.0",
"lodash-es": "4.17.21",
"memoize-one": "6.0.0",
"msw": "2.8.4",
"oidc-client-ts": "2.1.0",
"papaparse": "^5.5.2",
"react": "18.2.0",
"react-currency-input-field": "3.6.9",
"react-datepicker": "4.25.0",
"react-day-picker": "9.7.0",
"react-dom": "18.2.0",
"react-dropzone": "14.3.8",
"react-helmet-async": "1.3.0",
"react-hot-toast": "2.4.1",
"react-icons": "4.12.0",
"react-imask": "7.1.3",
"react-markdown": "^9.0.1",
"react-measure": "2.5.2",
"react-popper": "2.3.0",
"react-resizable-panels": "2.0.19",
"react-rnd": "^10.5.2",
"react-router-dom": "6.26.2",
"react-select": "5.7.4",
"react-sticky-el": "^2.1.1",
"react-textarea-autosize": "8.5.3",
"react-time-picker": "^8.0.2",
"recharts": "^2.15.1",
"rehype-raw": "^7.0.0",
"remark-gfm": "^4.0.1",
"sass": "1.55.0",
"scroll-into-view-if-needed": "^3.1.0",
"ts-node": "10.9.1",
"turndown": "^7.2.0",
"unist-util-visit": "^5.0.0",
"use-context-selector": "1.4.1",
"virtua": "^0.40.0",
"vite-plugin-lib-inject-css": "1.3.0",
"vite-plugin-svgr": "4.2.0",
"vscode-languageserver": "^9.0.1",
"vscode-languageserver-textdocument": "^1.0.11",
"yargs": "17.7.2"
},
"devDependencies": {
"@babel/core": "7.19.6",
"@babel/preset-env": "7.19.4",
"@babel/preset-typescript": "7.18.6",
"@remix-run/dev": "2.12.1",
"@remix-run/node": "2.12.1",
"@remix-run/serve": "2.12.1",
"@rollup/pluginutils": "5.1.0",
"@types/adm-zip": "0.5.4",
"@types/glob": "7.2.0",
"@types/js-yaml": "^4.0.9",
"@types/lodash-es": "4.17.6",
"@types/node": "18.11.5",
"@types/react": "18.2.23",
"@types/react-datepicker": "4.19.5",
"@types/react-dom": "18.2.8",
"@types/react-measure": "^2.0.8",
"@types/yargs": "17.0.31",
"@typescript-eslint/eslint-plugin": "8.15.0",
"@typescript-eslint/parser": "8.15.0",
"babel-loader": "8.2.5",
"clean-package": "2.2.0",
"eslint": "^8.57.0",
"eslint-import-resolver-typescript": "3.6.1",
"eslint-plugin-import": "2.28.1",
"eslint-plugin-jsx-a11y": "6.7.1",
"eslint-plugin-react": "^7.37.4",
"eslint-plugin-react-hooks": "^5.2.0",
"ncc": "^0.3.6",
"prettier": "^3.3.3",
"rimraf": "6.0.1",
"rollup-plugin-copy": "^3.5.0",
"rollup-plugin-visualizer": "5.8.3",
"serve": "14.2.0",
"terser": "^5.44.0",
"tsx": "4.20.6",
"typescript": "5.7.3",
"vite": "5.4.19",
"vite-plugin-dts": "4.5.0",
"vitest": "^3.0.3"
},
"optionalDependencies": {
"@esbuild/linux-x64": "0.25.2",
"@rollup/rollup-linux-x64-gnu": "4.20.0",
"@rollup/rollup-win32-x64-msvc": "4.20.0"
},
"resolutions": {
"@radix-ui/react-dismissable-layer": "1.1.10"
},
"files": [
"dist",
"src/styles",
"src/syntax"
],
"bin": {
"xmlui": "./bin/bootstrap.js"
},
"clean-package": {
"replace": {
"bin": {
"xmlui": "dist/scripts/bin/bootstrap.cjs"
},
"main": "./dist/standalone/xmlui-standalone.umd.js",
"module": "./dist/lib/xmlui.js",
"types": "./dist/lib/xmlui.d.ts",
"exports": {
".": {
"import": "./dist/lib/xmlui.js",
"require": "./dist/standalone/xmlui-standalone.umd.js"
},
"./language-server": {
"import": "./dist/lib/language-server.js",
"require": "./dist/lib/language-server.js"
},
"./language-server-web-worker": {
"import": "./dist/lib/language-server-web-worker.js",
"require": "./dist/lib/language-server-web-worker.js"
},
"./parser": {
"import": "./dist/lib/xmlui-parser.js",
"require": "./dist/lib/xmlui-parser.js"
},
"./*.css": {
"import": "./dist/lib/*.css",
"require": "./dist/lib/*.css"
},
"./index.scss": {
"import": "./dist/lib/scss/index.scss",
"require": "./dist/lib/scss/index.scss"
},
"./themes.scss": {
"import": "./dist/lib/scss/components-core/theming/_themes.scss",
"require": "./dist/lib/scss/components-core/theming/_themes.scss"
},
"./vite-xmlui-plugin": {
"import": "./dist/lib/vite-xmlui-plugin/index.js",
"require": "./dist/lib/vite-xmlui-plugin/index.js"
},
"./syntax/monaco": {
"import": "./dist/lib/syntax-monaco.js",
"require": "./dist/lib/syntax-monaco.js"
},
"./syntax/textmate": {
"import": "./dist/lib/syntax-textmate.js",
"require": "./dist/lib/syntax-textmate.js"
},
"./testing": {
"import": "./dist/lib/testing.js",
"require": "./dist/lib/testing.js"
}
}
}
},
"main": "./src/index.ts",
"exports": {
".": {
"import": "./src/index.ts",
"require": "./src/index.ts"
},
"./parser": {
"import": "./src/parsers/xmlui-parser/index.ts",
"require": "./src/parsers/xmlui-parser/index.ts"
},
"./index.scss": {
"import": "./src/index.scss",
"require": "./src/index.scss"
},
"./themes.scss": {
"import": "./src/components-core/theming/_themes.scss"
},
"./vite-xmlui-plugin": {
"import": "./bin/vite-xmlui-plugin.ts",
"require": "./bin/vite-xmlui-plugin.ts"
},
"./language-server": {
"import": "./src/language-server/server.ts",
"require": "./src/language-server/server.ts"
},
"./language-server-web-worker": {
"import": "./src/language-server/server-web-worker.ts",
"require": "./src/language-server/server-web-worker.ts"
},
"./syntax/monaco": {
"import": "./src/syntax/monaco/index.ts",
"require": "./src/syntax/monaco/index.ts"
},
"./syntax/textmate": {
"import": "./src/syntax/textMate/index.ts",
"require": "./src/syntax/textMate/index.ts"
},
"./testing": {
"import": "./src/testing/index.ts",
"require": "./src/testing/index.ts"
}
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"repository": {
"url": "https://github.com/xmlui-org/xmlui.git"
},
"msw": {
"workerDirectory": "src/testing/infrastructure/public"
}
}
```
--------------------------------------------------------------------------------
/docs/public/pages/tutorial-08.md:
--------------------------------------------------------------------------------
```markdown
# Create Invoice
Here's the `Create Invoice` form, using cached data for clients and products. The `Save` button is wired to a dummy API endpoint, try it as you explore the form to see how validation works when no client is selected, and/or when no line items are added.
```xmlui-pg display name="Try saving in various stages of completion"
---app
<App>
<CreateInvoice />
</App>
---comp
<Component name="CreateInvoice">
<DataSource id="clients" url="/resources/files/clients.json" method="GET" />
<DataSource id="products" url="/resources/files/products.json" method="GET" />
<Form
id="invoiceForm"
onCancel="invoiceForm.reset()">
<Card>
<H1>Create New Invoice</H1>
<FlowLayout>
<Card border="none" width="50%" padding="0">
<FormItem
type="select"
placeholder="select client"
bindTo="client"
label="Client"
required="true"
>
<Items data="{clients}">
<Option value="{$item.name}" label="{$item.name}" />
</Items>
</FormItem>
</Card>
<Card border="none" width="25%" padding="0">
<FormItem type="datePicker" dateFormat="yyyy-MM-dd" initialValue="{ formatToday() }"
bindTo="issueDate" label="Issue date" />
</Card>
<Card border="none" width="25%" padding="0">
<FormItem type="datePicker" dateFormat="yyyy-MM-dd" initialValue="{ formatToday(30) }"
bindTo="dueDate" label="Due date" />
</Card>
</FlowLayout>
<H2>Line Items</H2>
<FlowLayout fontWeight="bold" backgroundColor="$color-surface-100" padding="$space-2">
<Text width="25%">Product/Service</Text>
<Text width="25%">Description</Text>
<Text width="12%">Quantity</Text>
<Text width="12%">Price</Text>
<Text width="12%">Amount</Text>
</FlowLayout>
<FormItem
bindTo="lineItems"
type="items"
id="lineItemsForm"
required="true"
onValidate="(value) => Array.isArray(value) && value.length > 0 && value.every(item => item.product)"
requiredInvalidMessage="At least one line item is required."
>
<FlowLayout
width="100%"
verticalAlignment="center"
gap="$space-2"
>
<DataSource
id="productDetails"
url="/resources/files/products.json"
when="{$item.product != null}"
method="GET"
transformResult="{(data) => data.filter(product => product.name === $item.product)}"
/>
<FormItem
bindTo="product"
type="select"
placeholder="select product"
width="25%"
>
<Items data="{products}">
<Option value="{$item.name}" label="{$item.name}" />
</Items>
</FormItem>
<Text width="25%">{ productDetails.value[0].description }</Text>
<FormItem width="12%" bindTo="quantity" type="number" initialValue="1" minValue="1" />
<FormItem width="12%" bindTo="price" startText="$" initialValue="{ productDetails.value[0].price }" />
<FormItem width="12%" bindTo="amount" startText="$" enabled="false" initialValue="{ $item.price ? $item.quantity * $item.price : '' } " />
<Button width="2rem" onClick="lineItemsForm.removeItem($itemIndex)">X</Button>
</FlowLayout>
</FormItem>
<HStack>
<Button onClick="lineItemsForm.addItem()">
Add Item
</Button>
<SpaceFiller />
<Text>
Total: ${ window.lineItemTotal($data.lineItems) }
</Text>
</HStack>
</Card>
<event name="submit">
<APICall
url="https://httpbin.org/post"
method="POST"
inProgressNotificationMessage="Saving invoice..."
completedNotificationMessage="Invoice saved successfully"
body="{
{
client: $param.client,
issueDate: $param.issueDate,
dueDate: $param.dueDate,
total: window.lineItemTotal($param.lineItems),
items: JSON.stringify($param.lineItems || [])
}
}"
onSuccess="Actions.navigate('/invoices')"
/>
</event>
</Form>
</Component>
```
The `CreateInvoice` component encapsulates all the form logic. Let's review the key points.
## The Form tag
This [Form](/components/Form) contains a dropdown menu of products, two date pickers, and an expandable set of lineitems. These parts separately feed into the form's `$data` [context variable](/context-variables) which accumulates the JSON payload sent to the server on submit with successful validation.
```xmlui
<Form
id="invoiceForm"
onCancel="invoiceForm.reset()">
```
## The payload
A valid payload looks like this.
```json
{
"issueDate": "2025-06-10",
"dueDate": "2025-07-10",
"client": "Abstergo Industries",
"lineItems": [
{
"quantity": "1",
"amount": 105,
"product": "API Integration",
"price": 105
},
{
"quantity": "1",
"amount": 115,
"product": "Brand Strategy Consulting",
"price": 115
}
]
}
```
The form's `Cancel` button resets all of its components and empties this data structure.
## Nested FormItems
There is a top-level `FormItem` for `client`, `issue_date`, `due_date`, and `lineItems`. Their `bindTo` attributes name and populate corresponding fields in `$data`.
Nested within `lineItems` there is a `FormItem` for `product`, `quantity`, `price`, and `amount`. Their `bindTo` attributes name and define slots in an array of `lineItems` that's dynamically built on each click of the `Add Item` button.
```xmlui /<FormItem/
<Card title="Create New Invoice">
<FlowLayout>
<FormItem
width="50%"
type="select"
placeholder="select client"
bindTo="client"
label="Client"
required="true"
>
<Items data="/api/clients">
<Option value="{$item.name}" label="{$item.name}"/>
</Items>
</FormItem>
<FormItem
type="datePicker"
dateFormat="yyyy-MM-dd"
initialValue="{ formatToday() }"
bindTo="issueDate"
label="Issue date" width="25%"
/>
<FormItem
type="datePicker"
dateFormat="yyyy-MM-dd"
initialValue="{ formatToday(30) }"
bindTo="dueDate"
label="Due date"
width="25%" />
</FlowLayout>
<H2>Line Items</H2>
<FlowLayout
fontWeight="bold"
backgroundColor="$color-surface-100"
padding="$space-2"
>
<Text width="20%">Product/Service</Text>
<Text width="20%">Description</Text>
<Text width="20%">Quantity</Text>
<Text width="20%">Price</Text>
<Text width="20%">Amount</Text>
</FlowLayout>
<FormItem
bindTo="lineItems"
type="items"
id="lineItemsForm"
required="true"
requiredInvalidMessage="At least one line item is required."
>
<FlowLayout width="100%">
<DataSource
id="productDetails"
url="/api/products/byname/{$item.product}"
when="{$item.product != null}"
method="GET"
/>
<FormItem
bindTo="product"
type="select"
placeholder="select product"
width="20%"
required="true"
>
<Items data="/api/products">
<Option value="{$item.name}" label="{$item.name}"/>
</Items>
</FormItem>
<Text width="20%">{ productDetails.value[0].description }</Text>
<FormItem
width="20%"
bindTo="quantity"
type="number"
initialValue="1"
minValue="1"
/>
<FormItem
width="20%"
bindTo="price"
startText="$"
initialValue="{ productDetails.value[0].price }"
/>
<FormItem
width="13%"
bindTo="amount"
startText="$"
enabled="false"
initialValue="{ $item.price ? $item.quantity * $item.price : '' } "
/>
<Button
width="2rem"
onClick="lineItemsForm.removeItem($itemIndex)">
X
</Button>
</FlowLayout>
</FormItem>
<HStack>
<Button onClick="lineItemsForm.addItem()">
Add Item
</Button>
<SpaceFiller/>
<Text>
Total: ${ window.lineItemTotal($data.lineItems) }
</Text>
</HStack>
</Card>
```
## Total for lineItems
The `Add Item` button invokes the `addItem` method of [FormItem](/components/FormItem) to add a new empty row to the array. (Above we see the corresponding `removeItem` used when clicking the button at the end of a row.)
The app defines a function, `lineItemTotal`, to receive the `lineItems` array and add up the amounts.
```xmlui /lineItemTotal/
<HStack>
<Button onClick="lineItemsForm.addItem()">
Add Item
</Button>
<SpaceFiller />
<Text>
Total: ${ window.lineItemTotal($data.lineItems) }
</Text>
</HStack>
```
The same function runs when the [APICall](/components/APICall) runs on form submission.
```xmlui /lineItemTotal/
<Form>
<!-- ... -->
<event name="submit">
<APICall
url="https://httpbin.org/post"
method="POST"
inProgressNotificationMessage="Saving invoice..."
completedNotificationMessage="Invoice saved successfully"
body="{
{
client: $param.client,
issueDate: $param.issueDate,
dueDate: $param.dueDate,
total: window.lineItemTotal($data.lineItems),
items: JSON.stringify($param.lineItems || [])
}
}"
onSuccess="Actions.navigate('/invoices')"
/>
</event>
<!-- ... -->
</Form>
```
```
--------------------------------------------------------------------------------
/xmlui/src/components/Image/Image.spec.ts:
--------------------------------------------------------------------------------
```typescript
import { SKIP_REASON } from "../../testing/component-test-helpers";
import { expect, test } from "../../testing/fixtures";
test.describe("Basic Functionality", () => {
test("renders", async ({ page, initTestBed }) => {
await initTestBed(`<Image testId="img" />`);
await expect(page.getByTestId("img")).toBeVisible();
});
test("displays image with valid src", async ({ page, initTestBed }) => {
await initTestBed(`<Image testId="img" src="/resources/test-image-100x100.jpg" />`);
await expect(page.getByTestId("img")).toHaveAttribute(
"src",
"/resources/test-image-100x100.jpg",
);
});
test("displays image with valid data property", async ({ page, initTestBed }) => {
await initTestBed(`<Image testId="img" data="/resources/test-image-100x100.jpg" />`);
await expect(page.getByTestId("img")).toBeVisible();
// Check that the src attribute contains a blob URL when using data property
// The Image component fetches the binary data and passes it as a Blob to ImageNative,
// which creates a blob URL and sets it as the src attribute
const img = page.getByTestId("img");
await expect(img).toHaveAttribute("src", /blob:/);
});
test("handles invalid src gracefully", async ({ page, initTestBed }) => {
await initTestBed(`<Image testId="img" src="/non/existent/bad/url.jpg" />`);
await expect(page.getByTestId("img")).toBeVisible();
await expect(page.getByTestId("img")).toHaveAttribute("src", "/non/existent/bad/url.jpg");
});
test("alt text", async ({ page, initTestBed }) => {
await initTestBed(`<Image alt="test text for alt" src="/non/existent/bad/url.jpg" />`);
await expect(page.getByAltText("test text for alt")).toBeVisible();
});
test("handles undefined alt", async ({ page, initTestBed }) => {
await initTestBed(`<Image testId="img" src="/resources/test-image-100x100.jpg" />`);
await expect(page.getByTestId("img")).not.toHaveAttribute("alt");
});
test("handles long unicode alt text", async ({ page, initTestBed }) => {
await initTestBed(`
<Image
testId="img" alt="👨👩👧👦 Family photo with unicode characters 中文测试"
src="/resources/test-image-100x100.jpg"
/>
`);
await expect(page.getByTestId("img")).toHaveAttribute(
"alt",
"👨👩👧👦 Family photo with unicode characters 中文测试",
);
});
test(`fit="contain"`, async ({ page, initTestBed }) => {
await initTestBed(`
<HStack width="300px" height="200px" >
<Image testId="img" src="/resources/test-image-100x100.jpg" fit="contain" />
</HStack>
`);
await expect(page.getByTestId("img")).toHaveCSS("object-fit", "contain");
});
test(`fit="cover"`, async ({ page, initTestBed }) => {
await initTestBed(`
<HStack width="300px" height="200px" >
<Image testId="img" src="/resources/test-image-100x100.jpg" fit="cover" />
</HStack>
`);
await expect(page.getByTestId("img")).toHaveCSS("object-fit", "cover");
});
test("handles invalid fit value", async ({ page, initTestBed }) => {
await initTestBed(
`<Image testId="img" src="/resources/test-image-100x100.jpg" fit="invalid" />`,
);
await expect(page.getByTestId("img")).toHaveCSS("object-fit", "contain");
});
test("lazy loading enabled", async ({ page, initTestBed }) => {
await initTestBed(
`<Image testId="img" src="/resources/test-image-100x100.jpg" lazyLoad="true" />`,
);
await expect(page.getByTestId("img")).toHaveAttribute("loading", "lazy");
});
test("lazy loading disabled (default)", async ({ page, initTestBed }) => {
await initTestBed(`<Image testId="img" src="/resources/test-image-100x100.jpg" />`);
await expect(page.getByTestId("img")).toHaveAttribute("loading", "eager");
});
test("lazy loading explicitly disabled", async ({ page, initTestBed }) => {
await initTestBed(
`<Image testId="img" src="/resources/test-image-100x100.jpg" lazyLoad="false" />`,
);
await expect(page.getByTestId("img")).toHaveAttribute("loading", "eager");
});
test("applies aspect ratio as number", async ({ page, initTestBed }) => {
await initTestBed(
`<Image testId="img" src="/resources/test-image-100x100.jpg" aspectRatio="1.5" />`,
);
await expect(page.getByTestId("img")).toHaveCSS("aspect-ratio", "1.5 / 1");
});
test("applies aspect ratio as string fraction", async ({ page, initTestBed }) => {
await initTestBed(
`<Image testId="img" src="/resources/test-image-100x100.jpg" aspectRatio="16/9" />`,
);
await expect(page.getByTestId("img")).toHaveCSS("aspect-ratio", "16 / 9");
});
test("inline display when true", async ({ page, initTestBed }) => {
await initTestBed(
`<Image testId="img" src="/resources/test-image-100x100.jpg" inline="true" />`,
);
await expect(page.getByTestId("img")).toHaveCSS("display", "inline");
});
test("block display when false (default)", async ({ page, initTestBed }) => {
await initTestBed(
`<Image testId="img" src="/resources/test-image-100x100.jpg" inline="false" />`,
);
await expect(page.getByTestId("img")).not.toHaveCSS("display", "inline");
});
});
test.describe("Accessibility", () => {
test("has correct role when clickable", async ({ page, initTestBed }) => {
await initTestBed(`
<Image testId="img" src="/resources/test-image-100x100.jpg" onClick="console.log('clicked')" alt="Clickable image" />
`);
const img = page.getByTestId("img");
await expect(img).toHaveAttribute("alt", "Clickable image");
});
});
test.describe("Events", () => {
test(`onClick event`, async ({ page, initTestBed }) => {
await initTestBed(`
<HStack width="100px" gap="1rem" verticalAlignment="center">
<variable name="showTestText" value="{false}" />
<Image onClick="showTestText = true" testId="img" src="/resources/test-image-100x100.jpg" />
<Text when="{showTestText}">this is a test text</Text>
</HStack>
`);
await page.getByTestId("img").click();
await expect(page.getByText("this is a test text")).toBeVisible();
});
});
test.describe("Theme Variables", () => {
test(`custom themeVar borderRadius`, async ({ page, initTestBed }) => {
await initTestBed(`<Image testId="img" src="/resources/test-image-100x100.jpg" />`, {
testThemeVars: {
"borderRadius-Image": "25px",
},
});
await expect(page.getByTestId("img")).toHaveCSS("border-radius", "25px");
});
test(`custom themeVar borderColor`, async ({ page, initTestBed }) => {
await initTestBed(`<Image testId="img" src="/resources/test-image-100x100.jpg" />`, {
testThemeVars: {
"borderColor-Image": "rgb(255, 0, 0)",
},
});
await expect(page.getByTestId("img")).toHaveCSS("border-color", "rgb(255, 0, 0)");
});
});
test.describe("Other Edge Cases", () => {
[
{ label: "null", value: null },
{ label: "undefined", value: undefined },
{ label: "number", value: 123 },
{ label: "boolean", value: true },
{ label: "empty object", value: "{}" },
{ label: "empty array", value: "[]" },
{ label: "array", value: "['/resources/test-image-100x100.jpg']" },
{ label: "function", value: "() => '/resources/test-image-100x100.jpg'" },
{ label: "object", value: "{ a: '/resources/test-image-100x100.jpg' }" },
].forEach(({ label, value }) => {
test(`src prop handles ${label} by rendering broken image`, async ({ page, initTestBed }) => {
await initTestBed(`<Image testId="img" src="{${value}}" />`);
const img = page.getByTestId("img");
await expect(img).toBeVisible();
const naturalWidth = await img.evaluate((el: HTMLImageElement) => el.naturalWidth);
const naturalHeight = await img.evaluate((el: HTMLImageElement) => el.naturalHeight);
expect(naturalWidth).toBe(0);
expect(naturalHeight).toBe(0);
});
});
[
{ label: "empty string", prop: "alt=\"\"", expected: undefined },
{ label: "string", prop: 'alt="some text"', expected: "some text" },
{ label: "number", prop: 'alt="{123}"', expected: "123" },
{ label: "boolean", prop: 'alt="{true}"', expected: "true" },
].forEach(({ label, prop, expected }) => {
test(`alt attribute appears if value is ${label}`, async ({ page, initTestBed }) => {
await initTestBed(`<Image testId="img" ${prop} />`);
const img = page.getByTestId("img");
await expect(img).toBeVisible();
await expect(img).toHaveAttribute("alt", expected);
});
});
[
{ label: "null", prop: 'alt="{null}"' },
{ label: "undefined", prop: 'alt="{undefined}"' },
{ label: "empty object", prop: 'alt="{{}}"' },
{ label: "empty array", prop: 'alt="{[]}"' },
{ label: "array", prop: 'alt="{[\'/resources/test-image-100x100.jpg\']}"' },
{ label: "function", prop: 'alt="{() => \'/resources/test-image-100x100.jpg\'}"' },
{ label: "object", prop: 'alt="{{ a: \'/resources/test-image-100x100.jpg\' }}"' },
].forEach(({ label, prop }) => {
test(`alt attr not shown if value is ${label}`, async ({ page, initTestBed }) => {
await initTestBed(`<Image testId="img" ${prop} />`);
const img = page.getByTestId("img");
await expect(img).toBeVisible();
await expect(img).not.toHaveAttribute("alt");
});
});
test("handles very long src URL", async ({ page, initTestBed }) => {
const longUrl = "/very/long/path/".repeat(20) + "image.jpg";
await initTestBed(`<Image testId="img" src="${longUrl}" />`);
await expect(page.getByTestId("img")).toHaveAttribute("src", longUrl);
});
});
```
--------------------------------------------------------------------------------
/blog/public/blog/xmlui-powered-blog.md:
--------------------------------------------------------------------------------
```markdown
In this post we'll explore the development of the blog engine we're using on this site. Our tagline is *Practical User Interfaces Built Simply* and creating this blog couldn't have been simpler. It's an XMLUI app built with a handful of core components (including [NavPanel](https://docs.xmlui.org/components/NavPanel), [NavLink](https://docs.xmlui.org/components/NavLink), [Pages](https://docs.xmlui.org/components/Pages), [Page](https://docs.xmlui.org/components/Page), and [Markdown](https://docs.xmlui.org/components/Markdown)) and a couple of [user-defined components](https://docs.xmlui.org/user-defined-components).
## The simplest possible thing
We started with the simplest possible approach: post metadata and data as literal strings.
```xmlui-pg name="XMLUI blog v1" height="200px"
---app
<App layout="vertical">
<NavPanel>
<NavGroup label="Blog">
<NavLink label="Newest post" to="/newest-post" />
<NavLink label="Older píost" to="/older-post" />
</NavGroup>
</NavPanel>
<Pages>
<Page url="/newest-post">
<BlogPage
content="This is the latest post"
title="Newest post"
author="Jon Udell"
date="2025-09-01" />
</Page>
<Page url="/older-post">
<BlogPage
content="This is an older post"
title="Older post"
author="Istvan Novak"
date="2025-08-30" />
</Page>
</Pages>
</App>
---comp copy
<Component name="BlogPage">
<VStack width="{$props.width ? $props.width : '85%'}">
<VStack>
<H1>{$props.title}</H1>
<HStack gap="$space-2">
<Text>{$props.date}</Text>
<Text>-</Text>
<Text>{$props.author}</Text>
</HStack>
</VStack>
<Markdown content="{$props.content}" />
</VStack>
</Component>
```
You can use it right here or you can click the  icon to open a playground where you can make live changes.
This is a pretty good start! We can write posts, arrange them in reverse chronological order, and hey, it's the essence of a blog. The live playground is a nice bonus that any XMLUI app might put to good use. When you build user interfaces with XMLUI you'll want to document them, it's useful to do that with working examples as well as images, text, and video.
Let's unpack how this works, there isn't much to it. The `App` declared in `Main.xmlui` sets up navigation.
```xmlui copy
<App>
<NavPanel>
<NavGroup label="Blog">
<NavLink label="Newest post" to="/newest-post" />
<NavLink label="Older post" to="/older-post" />
</NavGroup>
</NavPanel>
<!-- Pages section... -->
</App>
```
Each Page contains a user-defined component called `BlogPage` that receives the properties `content`, `title`, `author`, and `date`.
```xmlui
<Pages>
<Page url="/newest-post">
<BlogPage
content="This is the latest post"
title="Newest post"
author="Jon Udell"
date="2025-09-01" />
</Page>
</Pages>
```
Here's how `BlogPage` assembles data and metadata to create a post.
```xmlui /$props/
<Component name="BlogPage">
<VStack gap="0">
<H1>{$props.post.title}</H1>
<Text>{$props.post.date} • {$props.post.author}</Text>
</VStack>
<Image src="/blog/images/{$props.post.image}" />
<Markdown marginTop="$space-4" data="/blog/{$props.post.slug}.md" />
</Component>
```
## Use Markdown files
So far the post content exists only as the `content` property passed to the `BlogPage` component. For the real blog we'll want to manage it as a set of Markdown files. This version enables that.
```xmlui-pg name="XMLUI blog v2" height="200px"
---app
<App
layout="vertical"
var.posts = `{[
{
title: "Welcome to the XMLUI blog!",
slug: "xmlui-powered-blog",
author: "Jon Udell",
date: "2025-09-01",
image: "blog-scrabble.png"
},
{
title: "Lorem Ipsum!",
slug: "lorem-ipsum",
author: "H. Rackham",
date: "1914-06-03",
image: "lorem-ipsum.png"
}
]}`
>
<NavPanel>
<NavGroup label="Blog">
<NavLink label="Newest post" to="/blog/{posts[0].slug}" />
<NavLink label="Older post" to="/blog/{posts[1].slug}" />
</NavGroup>
</NavPanel>
<Pages>
<Page url="/blog/{posts[0].slug}">
<BlogPage post="{posts[0]}" />
</Page>
<Page url="/blog/{posts[1].slug}">
<BlogPage post="{posts[1]}" />
</Page>
</Pages>
</App>
---comp
<Component name="BlogPage">
<VStack width="{$props.width ? $props.width : '85%'}">
<VStack gap="0">
<H1>{$props.post.title}</H1>
<Text>{$props.post.date} - {$props.post.author}</Text>
</VStack>
<Image src="/blog/images/{$props.post.image}" />
<Markdown marginTop="$space-4" data="/blog/{$props.post.slug}.md" />
</VStack>
</Component>
```
Now we write post metadata as an App-level variable, and create Markdown files corresponding to the slugs. In this case the files are `xmlui-powered-blog.md` (this post) and `lorem-ipsum.md` (a dummy older post). We also add a hero image for each post.
```xmlui copy
<App
layout="vertical"
var.posts = `{[
{
title: "Welcome to the XMLUI blog!",
slug: "xmlui-powered-blog",
author: "Jon Udell",
date: "2025-09-01",
image: "blog-scrabble.png"
},
{
title: "Lorem Ipsum!",
slug: "lorem-ipsum",
author: "H. Rackham",
date: "1914-06-03",
image: "lorem-ipsum.png"
}
]}`
>
```
The blog's `NavGroup` now looks like this. We'll maintain reverse chronology by just writing the `NavLink`s in that order.
```xmlui copy
<NavGroup label="Blog">
<NavLink label="{posts[0].title}" to="/blog/{posts[0].slug}" />
<NavLink label="{posts[1].title}" to="/blog/{posts[1].slug}" />
</NavGroup>
```
The `NavLink` uses the post's slug to bind to its corresponding `Page`.
```xmlui copy
<Page url="/blog/{posts[0].slug}">
<BlogPage post="{posts[0]}" />
</Page>
```
And the `Page` passes the complete post object to `BlogPage`. In v1 we used the `content` property of the `Markdown` component to pass a string. In v2 we use the `data` property to pass a URL constructed from the post slug.
```xmlui copy {11}
<Component name="BlogPage">
<VStack width="{$props.width ? $props.width : '85%'}">
<VStack>
<H1>{$props.post.title}</H1>
<HStack gap="$space-2">
<Text>{$props.post.date}</Text>
<Text> - </Text>
<Text>{$props.post.author}</Text>
</HStack>
</VStack>
<Markdown data="/blog/{$props.post.slug}.md" />
</VStack>
</Component>
```
## Create the overview page
Although we have a `NavGroup` to list the posts, a blog should really have an overview page. Let's add another user-defined component for that.
```xmlui copy
<Component name="BlogOverview">
<CVStack>
<VStack width="100%">
<H1>XMLUI Blog</H1>
<Text>Latest updates, tutorials, and insights for building with XMLUI</Text>
<List data="{
$props.posts.sort(function(a, b) {
return new Date(b.date) - new Date(a.date);
})
}">
<VStack gap="$space-2">
<Link to="/blog/{$item.slug}">
<Text fontSize="larger">
{$item.title}
</Text>
</Link>
<Text>
{$item.date} • {$item.author}
</Text>
<Link to="/blog/{$item.slug}">
<Image src="/blog/images/{$item.image}" />
</Link>
<Stack height="$space-8" />
</VStack>
</List>
</VStack>
</CVStack>
</Component>
```
The `NavGroup` now just becomes a `NavLink`.
```xmlui copy
<NavLink label="Blog" to="/blog" />
```
We refer to the overview in `Pages` along with the same `Page` used for the intro post.
```xmlui copy
<Page url="/blog">
<BlogOverview posts="{posts}" />
</Page>
<Page url="/blog/{posts[0].slug}">
<BlogPage post="{posts[0]}" />
</Page>
```
## Create an RSS feed
We can't call it a blog unless it provides an RSS feed. For that we've added a simple feed generator that reads the metadata and writes `/feed.rss` which is then served statically by the webserver that hosts the site. So we've added a RSS icon to the template and feed autodiscovery to the site's `index.html`.
## Deploy standalone
Our blog lives in the XMLUI monorepo where it coordinates with the landing page and docs.
But it can exist standalone, you only need a folder with a handful of files.
```
├── Main.xmlui
├── blog
│ ├── images
│ │ ├── blog-scrabble.png
│ │ └── lorem-ipsum.png
│ ├── lorem-ipsum.md
│ └── xmlui-powered-blog.md
├── components
│ ├── BlogOverview.xmlui
│ ├── BlogPage.xmlui
├── index.html
└── xmlui
└── 0.10.24.js
```
Here's the `index.html`.
```xmlui copy
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>XMLUI blog test</title>
<script src="xmlui/0.10.24.js"></script>
</head>
<body>
</body>
</html>
```
I dragged the folder containing the standalone app onto Netlify's drop target. Check it out!
[https://test-xmlui-blog.netlify.app/](https://test-xmlui-blog.netlify.app/)
## XMLUI for publishing
We get it, blog engines are a dime a dozen. We made this one because XMLUI was already a strong publishing system that we use for the [docs](https://docs.xmlui.org), [demo](https://demo.xmlui.org), and [landing page](https://xmlui.org). The `Markdown` component, with its support for playgrounds, works really well and it made sense to leverage that for our blog. We're not saying that you *should* build a blog engine with XMLUI but it's clearly something you *could* do. We think it's pretty easy to create a competent engine that makes life easy for authors and readers.
```
--------------------------------------------------------------------------------
/xmlui/src/components/App/App.md:
--------------------------------------------------------------------------------
```markdown
%-DESC-START
**Essential features:**
- **Layout templates**: Choose from 7 predefined layouts (horizontal, vertical, condensed, etc.) with sticky navigation options
- **Routing**: Built-in page routing via the [Pages](/components/Pages) component
%-DESC-END
%-PROP-START layout
Here are a few samples demonstrating the usage of the `layout` property. All samples use this markup, except the value of `App`'s layout and a few marked code snippets:
```xmlui
<App layout="(specific layout value)">
<!-- AppHeader omitted for "vertical" and "vertical-sticky" -->
<AppHeader>
<property name="logoTemplate">
<Heading level="h3" value="Example App"/>
</property>
</AppHeader>
<NavPanel>
<NavLink label="Home" to="/" icon="home"/>
<NavLink label="Page 1" to="/page1"/>
<NavLink label="Page 2" to="/page2"/>
</NavPanel>
<Pages fallbackPath="/">
<Page url="/">
<List data="https://api.spacexdata.com/v3/history">
<property name="itemTemplate">
<Card title="{$item.title}" subtitle="{$item.details}"/>
</property>
</List>
</Page>
<Page url="/page1">
<Text value="Page 1" />
</Page>
<Page url="/page2">
<Text value="Page 2" />
</Page>
</Pages>
<Footer>Powered by XMLUI</Footer>
</App>
```
#### `horizontal`
```xmlui-pg copy name="Example: 'horizontal' layout" height="350px"
<App layout="horizontal">
<AppHeader>
<property name="logoTemplate">
<Heading level="h3" value="Example App"/>
</property>
</AppHeader>
<NavPanel>
<NavLink label="Home" to="/" icon="home"/>
<NavLink label="Page 1" to="/page1"/>
<NavLink label="Page 2" to="/page2"/>
</NavPanel>
<Pages fallbackPath="/">
<Page url="/">
<List data="https://api.spacexdata.com/v3/history">
<property name="itemTemplate">
<Card title="{$item.title}" subtitle="{$item.details}"/>
</property>
</List>
</Page>
<Page url="/page1">
<Text value="Page 1" />
</Page>
<Page url="/page2">
<Text value="Page 2" />
</Page>
</Pages>
<Footer>Powered by XMLUI</Footer>
</App>
```
#### `horizontal-sticky`
```xmlui-pg copy name="Example: 'horizontal-sticky' layout" height="350px"
<App layout="horizontal-sticky">
<AppHeader>
<property name="logoTemplate">
<Heading level="h3" value="Example App"/>
</property>
</AppHeader>
<NavPanel>
<NavLink label="Home" to="/" icon="home"/>
<NavLink label="Page 1" to="/page1"/>
<NavLink label="Page 2" to="/page2"/>
</NavPanel>
<Pages fallbackPath="/">
<Page url="/">
<List data="https://api.spacexdata.com/v3/history">
<property name="itemTemplate">
<Card title="{$item.title}" subtitle="{$item.details}"/>
</property>
</List>
</Page>
<Page url="/page1">
<Text value="Page 1" />
</Page>
<Page url="/page2">
<Text value="Page 2" />
</Page>
</Pages>
<Footer>Powered by XMLUI</Footer>
</App>
```
#### `condensed`
```xmlui-pg copy name="Example: 'condensed' layout" height="350px"
<App layout="condensed">
<property name="logoTemplate">
<Heading level="h3" value="Example App"/>
</property>
<NavPanel>
<NavLink label="Home" to="/" icon="home"/>
<NavLink label="Page 1" to="/page1"/>
<NavLink label="Page 2" to="/page2"/>
</NavPanel>
<Pages fallbackPath="/">
<Page url="/">
<List data="https://api.spacexdata.com/v3/history">
<property name="itemTemplate">
<Card title="{$item.title}" subtitle="{$item.details}"/>
</property>
</List>
</Page>
<Page url="/page1">
<Text value="Page 1" />
</Page>
<Page url="/page2">
<Text value="Page 2" />
</Page>
</Pages>
<Footer>Powered by XMLUI</Footer>
</App>
```
#### `condensed-sticky`
```xmlui-pg copy name="Example: 'condensed-sticky' layout" height="350px"
<App layout="condensed-sticky">
<property name="logoTemplate">
<Heading level="h3" value="Example App"/>
</property>
<NavPanel>
<NavLink label="Home" to="/" icon="home"/>
<NavLink label="Page 1" to="/page1"/>
<NavLink label="Page 2" to="/page2"/>
</NavPanel>
<Pages fallbackPath="/">
<Page url="/">
<List data="https://api.spacexdata.com/v3/history">
<property name="itemTemplate">
<Card title="{$item.title}" subtitle="{$item.details}"/>
</property>
</List>
</Page>
<Page url="/page1">
<Text value="Page 1" />
</Page>
<Page url="/page2">
<Text value="Page 2" />
</Page>
</Pages>
<Footer>Powered by XMLUI</Footer>
</App>
```
#### `vertical`
```xmlui-pg copy name="Example: 'vertical' layout" height="300px"
<App layout="vertical">
<property name="logoTemplate">
<Heading level="h3" value="Example App"/>
</property>
<NavPanel>
<NavLink label="Home" to="/" icon="home"/>
<NavLink label="Page 1" to="/page1"/>
<NavLink label="Page 2" to="/page2"/>
</NavPanel>
<Pages fallbackPath="/">
<Page url="/">
<List data="https://api.spacexdata.com/v3/history">
<property name="itemTemplate">
<Card title="{$item.title}" subtitle="{$item.details}"/>
</property>
</List>
</Page>
<Page url="/page1">
<Text value="Page 1" />
</Page>
<Page url="/page2">
<Text value="Page 2" />
</Page>
</Pages>
<Footer>Powered by XMLUI</Footer>
</App>
```
#### `vertical-sticky`
```xmlui-pg copy name="Example: 'vertical-sticky' layout" height="300px"
<App layout="vertical-sticky">
<property name="logoTemplate">
<Heading level="h3" value="Example App"/>
</property>
<NavPanel>
<NavLink label="Home" to="/" icon="home"/>
<NavLink label="Page 1" to="/page1"/>
<NavLink label="Page 2" to="/page2"/>
</NavPanel>
<Pages fallbackPath="/">
<Page url="/">
<List data="https://api.spacexdata.com/v3/history">
<property name="itemTemplate">
<Card title="{$item.title}" subtitle="{$item.details}"/>
</property>
</List>
</Page>
<Page url="/page1">
<Text value="Page 1" />
</Page>
<Page url="/page2">
<Text value="Page 2" />
</Page>
</Pages>
<Footer>Powered by XMLUI</Footer>
</App>
```
#### `vertical-full-header`
```xmlui-pg copy name="Example: 'vertical-full-header' layout" height="300px"
<App layout="vertical-full-header">
<AppHeader>
<property name="logoTemplate">
<Heading level="h3" value="Example App"/>
</property>
</AppHeader>
<NavPanel>
<NavLink label="Home" to="/" icon="home"/>
<NavLink label="Page 1" to="/page1"/>
<NavLink label="Page 2" to="/page2"/>
</NavPanel>
<Pages fallbackPath="/">
<Page url="/">
<List data="https://api.spacexdata.com/v3/history">
<property name="itemTemplate">
<Card title="{$item.title}" subtitle="{$item.details}"/>
</property>
</List>
</Page>
<Page url="/page1">
<Text value="Page 1" />
</Page>
<Page url="/page2">
<Text value="Page 2" />
</Page>
</Pages>
<Footer>Powered by XMLUI</Footer>
</App>
```
%-PROP-END
%-PROP-START scrollWholePage
This boolean property specifies whether the whole page should scroll (true) or just the content area (false).
The default value is `true`.
```xmlui-pg copy display name="Example: scrollWholePage" height="150px"
<App scrollWholePage="false">
<NavPanel>
<NavLink label="Home" to="/" icon="home"/>
</NavPanel>
<Pages fallbackPath="/">
<Page url="/">
<Text>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</Text>
</Page>
</Pages>
</App>
```
%-PROP-END
%-PROP-START loggedInUser
Stores information about the currently logged in user.
Currently, there is no restriction on what the user data must look like.
```xmlui-pg copy display name="Example: loggedInUser" height="180px"
<App loggedInUser="{{ name: 'Joe', token: '1234' }}">
<NavPanel>
<NavLink label="Home" to="/" icon="home"/>
</NavPanel>
<Pages fallbackPath="/">
<Page url="/">
<Text value="User name: {loggedInUser.name}" />
<Text value="User token: {loggedInUser.token}" />
</Page>
</Pages>
</App>
```
%-PROP-END
%-EVENT-START ready
This event fires when the `App` component finishes rendering on the page.
Use it as `onReady` when inlining it on the component.
```xmlui-pg copy display name="Example: ready"
<App onReady="isAppReady = true">
<variable name="isAppReady" value="{false}"/>
<Text value="{isAppReady ? 'App is ready' : 'Sadly, App is not ready'}" />
</App>
```
%-EVENT-END
%-EVENT-START messageReceived
The event handler method has two parameters. The first is the message sent; the second is the entire native event object.
```xmlui-pg copy display name="Example: messageReceived" /onMessageReceived/ /window.postMessage/
<App
var.message = "<none>"
onMessageReceived="(msg, ev) => {
message = JSON.stringify(msg);
console.log('Message event received:', ev);
}">
<Button label="Send a message"
onClick="window.postMessage({type: 'message', messages:'Here you are!'})" />
<Text>Message received: {message}</Text>
</App>
```
%-EVENT-END
```
--------------------------------------------------------------------------------
/xmlui/src/components/Markdown/Markdown.spec.ts:
--------------------------------------------------------------------------------
```typescript
import { SKIP_REASON } from "../../testing/component-test-helpers";
import { expect, test } from "../../testing/fixtures";
import type { HeadingLevel } from "../Heading/abstractions";
// --- Testing
test.describe("smoke tests", { tag: "@smoke" }, () => {
test("Markdown renders", async ({ initTestBed, createMarkdownDriver }) => {
await initTestBed(`<Markdown />`);
await expect((await createMarkdownDriver()).component).toBeAttached();
});
test("handles empty binding expression", async ({ initTestBed, createMarkdownDriver }) => {
await initTestBed(`<Markdown><![CDATA[\@{}]]></Markdown>`);
await expect((await createMarkdownDriver()).component).toHaveText("");
});
test("does not detect escaped empty expression #1", async ({
initTestBed,
createMarkdownDriver,
}) => {
await initTestBed(`<Markdown><![CDATA[\\@{}]]></Markdown>`);
await expect((await createMarkdownDriver()).component).toHaveText("@{}");
});
test("does not detect escaped empty expression #2", async ({
initTestBed,
createMarkdownDriver,
}) => {
await initTestBed(`<Markdown><![CDATA[\@\\{}]]></Markdown>`);
await expect((await createMarkdownDriver()).component).toHaveText("@{}");
});
test("does not detect escaped expression #1", async ({ initTestBed, createMarkdownDriver }) => {
await initTestBed(`<Markdown><![CDATA[\\@{1}]]></Markdown>`);
await expect((await createMarkdownDriver()).component).toHaveText("@{1}");
});
test("does not detect escaped expression #2", async ({ initTestBed, createMarkdownDriver }) => {
await initTestBed(`<Markdown><![CDATA[\@\\{1}]]></Markdown>`);
await expect((await createMarkdownDriver()).component).toHaveText("@{1}");
});
test("handles only spaces binding expression", async ({ initTestBed, createMarkdownDriver }) => {
await initTestBed(`<Markdown><![CDATA[\@{ }]]></Markdown>`);
await expect((await createMarkdownDriver()).component).toHaveText("");
});
test("handles binding expression", async ({ initTestBed, createMarkdownDriver }) => {
await initTestBed(`<Markdown><![CDATA[\@{1+1}]]></Markdown>`);
await expect((await createMarkdownDriver()).component).toHaveText("2");
});
test("handles objects in binding expressions", async ({ initTestBed, createMarkdownDriver }) => {
const expected = "{ a : 1, b: 'c' }";
await initTestBed(`<Markdown><![CDATA[\@{${expected}}]]></Markdown>`);
await expect((await createMarkdownDriver()).component).toHaveText(`{"a":1,"b":"c"}`);
});
test("handles arrays in binding expressions", async ({ initTestBed, createMarkdownDriver }) => {
const expected = "[ 1, 2, 3 ]";
await initTestBed(`<Markdown><![CDATA[\@{${expected}}]]></Markdown>`);
await expect((await createMarkdownDriver()).component).toHaveText(`[1,2,3]`);
});
test("handles functions in binding expressions", async ({
initTestBed,
createMarkdownDriver,
}) => {
const SOURCE = "() => { const x = 1; console.log(x); return null; }";
const EXPECTED = "[xmlui function]";
await initTestBed(`<Markdown><![CDATA[\@{${SOURCE}}]]></Markdown>`);
await expect((await createMarkdownDriver()).component).toHaveText(EXPECTED);
});
test("handles nested objects in binding expressions", async ({
initTestBed,
createMarkdownDriver,
}) => {
const expected = "{ a : 1, b: { c: 1 } }";
await initTestBed(`<Markdown><![CDATA[\@{${expected}}]]></Markdown>`);
await expect((await createMarkdownDriver()).component).toHaveText(`{"a":1,"b":{"c":1}}`);
});
test("handles functions nested in objects in binding expressions", async ({
initTestBed,
createMarkdownDriver,
}) => {
const SOURCE = "{ a: () => { const x = 1; console.log(x); return null; } }";
const EXPECTED = '{"a":"[xmlui function]"}';
await initTestBed(`<Markdown><![CDATA[\@{${SOURCE}}]]></Markdown>`);
await expect((await createMarkdownDriver()).component).toHaveText(EXPECTED);
});
test("handles arrays nested in objects in binding expressions", async ({
initTestBed,
createMarkdownDriver,
}) => {
const expected = "{ a: [1, 2, 3] }";
await initTestBed(`<Markdown><![CDATA[\@{${expected}}]]></Markdown>`);
await expect((await createMarkdownDriver()).component).toHaveText(`{"a":[1,2,3]}`);
});
test("handles arrays nested in functions in binding expressions", async ({
initTestBed,
createMarkdownDriver,
}) => {
const SOURCE = "() => { return [1, 2, 3]; }";
const EXPECTED = "[xmlui function]";
await initTestBed(`<Markdown><![CDATA[\@{${SOURCE}}]]></Markdown>`);
await expect((await createMarkdownDriver()).component).toHaveText(EXPECTED);
});
test("handles complex expressions", async ({ initTestBed, createMarkdownDriver }) => {
const SOURCE =
"Hello there @{ {a : () => {}, x: null, b: { c: 3, d: 'asdadsda', e: () => {return null;} } } } How are you @{true || undefined || []}";
const EXPECTED =
'Hello there {"a":"[xmlui function]","x":null,"b":{"c":3,"d":"asdadsda","e":"[xmlui function]"}} How are you true';
await initTestBed(`<Markdown><![CDATA[${SOURCE}]]></Markdown>`);
await expect((await createMarkdownDriver()).component).toHaveText(EXPECTED);
});
const headingLevelsWithMarkdown: Array<{ level: HeadingLevel; md: string }> = [
{ level: "h1", md: "# Heading" },
{ level: "h2", md: "## Heading" },
{ level: "h3", md: "### Heading" },
{ level: "h4", md: "#### Heading" },
{ level: "h5", md: "##### Heading" },
{ level: "h6", md: "###### Heading" },
];
headingLevelsWithMarkdown.forEach(({ level, md }) => {
test(`can render anchor link for '${level}'`, async ({ initTestBed, createMarkdownDriver }) => {
const SOURCE = md;
await initTestBed(`<Markdown showHeadingAnchors="true"><![CDATA[${SOURCE}]]></Markdown>`);
const driver = await createMarkdownDriver();
expect(await driver.hasHtmlElement("a")).toBe(true);
});
});
test("show implicit anchor links", async ({ initTestBed, createMarkdownDriver }) => {
const SOURCE = "## Heading";
await initTestBed(`<Markdown showHeadingAnchors="true"><![CDATA[${SOURCE}]]></Markdown>`);
const driver = await createMarkdownDriver();
expect(await driver.hasHtmlElement("a")).toBe(true);
});
test("show explicit anchor links", async ({ initTestBed, createMarkdownDriver }) => {
const SOURCE = "## Heading [#heading]";
await initTestBed(`<Markdown showHeadingAnchors="true"><![CDATA[${SOURCE}]]></Markdown>`);
const driver = await createMarkdownDriver();
expect(await driver.hasHtmlElement("a")).toBe(true);
});
test("don't render implicit anchor links", async ({ initTestBed, createMarkdownDriver }) => {
const SOURCE = "## Heading";
await initTestBed(`<Markdown showHeadingAnchors="false"><![CDATA[${SOURCE}]]></Markdown>`);
const driver = await createMarkdownDriver();
expect(await driver.hasHtmlElement("a")).toBe(false);
});
test("don't render explicit anchor links", async ({ initTestBed, createMarkdownDriver }) => {
const SOURCE = "## Heading [#heading]";
await initTestBed(`<Markdown showHeadingAnchors="false"><![CDATA[${SOURCE}]]></Markdown>`);
const driver = await createMarkdownDriver();
expect(await driver.hasHtmlElement("a")).toBe(false);
});
});
test("only renders if children are strings", async ({ initTestBed, createMarkdownDriver }) => {
await initTestBed(`
<Markdown>
<Button label="Hey!" />
</Markdown>
`);
// Check if page is empty (no text)
const driver = await createMarkdownDriver();
await expect(driver.component).toHaveText("");
});
test("renders if children are provided through CDATA", async ({ initTestBed, createMarkdownDriver }) => {
await initTestBed(`
<Markdown>
<![CDATA[Hello World!]]>
</Markdown>
`);
// Check if page is empty (no text)
const driver = await createMarkdownDriver();
await expect(driver.component).toHaveText("Hello World!");
});
test("renders code block", async ({ initTestBed, createMarkdownDriver }) => {
const code = "```\n" + "I did not expect this\n" + "```";
await initTestBed(`<Markdown><![CDATA[${code}]]></Markdown>`);
const driver = await createMarkdownDriver();
await expect(driver.component).toHaveText("I did not expect this");
expect(await driver.hasHtmlElement(["pre", "code"])).toBeTruthy();
});
test("4space/1 tab indent is not code block by default", async ({
initTestBed,
createMarkdownDriver,
}) => {
// Note the formatting here: the line breaks and indentations are intentional
const code = `
_I did not expect this_
`;
await initTestBed(`<Markdown><![CDATA[${code}]]></Markdown>`);
const driver = await createMarkdownDriver();
await expect(driver.component).toHaveText("I did not expect this");
expect(await driver.hasHtmlElement("em")).toBeTruthy();
});
test("removeIndents=false: 4space/1 tab indent is accounted for", async ({
initTestBed,
createMarkdownDriver,
}) => {
// Note the formatting here: the lack of indentations is intentional
const code = `
_I did not expect this_
`;
await initTestBed(`<Markdown removeIndents="false"><![CDATA[${code}]]></Markdown>`);
const driver = await createMarkdownDriver();
await expect(driver.component).toHaveText("I did not expect this");
expect(await driver.hasHtmlElement("em")).toBeTruthy();
});
test("removeIndents=false: 4space/1 tab indent maps to a code block", async ({
initTestBed,
createMarkdownDriver,
}) => {
// Note the formatting here: the indentations are intentional
const code = `
_I did not expect this_
`;
await initTestBed(`<Markdown removeIndents="false"><![CDATA[${code}]]></Markdown>`);
const driver = await createMarkdownDriver();
await expect(driver.component).toHaveText("_I did not expect this_");
expect(await driver.hasHtmlElement(["pre", "code"])).toBeTruthy();
});
```
--------------------------------------------------------------------------------
/xmlui/src/components/Tabs/Tabs.md:
--------------------------------------------------------------------------------
```markdown
%-DESC-START
**Key features:**
- **Content organization**: Efficiently displays multiple content sections in a single interface area
- **Flexible orientation**: Supports both horizontal (tabs on top) and vertical (tabs on side) layouts
- **Active tab control**: Programmatically set which tab is initially selected
- **Custom header templates**: Configurable tab appearance via `headerTemplate` property
- **Navigation methods**: Built-in methods for programmatic tab switching (`next()`, `prev()`, `setActiveTabIndex()`, `setActiveTabById()`)
- **External ID support**: Optional `id` prop for TabItems with context exposure
- **Dynamic content**: Works seamlessly with `Items` for data-driven tabs
## Using Tabs
The component accepts only [TabItem](/components/TabItem) components as children. Other child components will not be displayed. Each [TabItem](/components/TabItem) has a `label` property for the tab button text, with content provided by placing child components within the [TabItem](/components/TabItem).
```xmlui-pg copy display name="Example: using Tabs" height="200px"
<App>
<Tabs>
<TabItem label="Account">
<Text>Account</Text>
</TabItem>
<TabItem label="Stream">
<Text>Stream</Text>
</TabItem>
<TabItem label="Support">
<Text>Support</Text>
</TabItem>
</Tabs>
</App>
```
## Dynamic Tabs
You can create `TabItem` children dynamically:
```xmlui-pg copy display name="Example: using Tabs with dynamic items" height="200px"
<App>
<Tabs>
<Items data="{[1, 2, 3, 4]}">
<TabItem label="Account {$item}">
<Card title="Tab Content for Account {$item}"/>
</TabItem>
</Items>
</Tabs>
</App>
```
%-DESC-END
%-PROP-START headerTemplate
```xmlui-pg copy {3-6} display name="Example: headerTemplate" /headerTemplate/ height="200px"
<App>
<Tabs>
<property name="headerTemplate">
<Icon name="chevronright" />
<Text color="green">Account {$header.index}</Text>
</property>
<Items data="{[
{id: 1, name: 'AcmeCorp'},
{id: 2, name: 'BetaLLC'},
{id: 3, name: 'GammaInc'}]
}">
<TabItem label="Account {$item}">
<Card title="Tab Content for Account {$item.name}"/>
</TabItem>
</Items>
</Tabs>
</App>
```
The `headerTemplate` property allows you to customize the appearance of tab headers. The template receives a `$header` context variable with the following properties:
- `id` (optional): External ID if provided to TabItem
- `index`: Zero-based index of the tab
- `label`: The tab's label text
- `isActive`: Boolean indicating if this tab is currently active
Individual tab items have an optional identifier, which is passed to the header template.
```xmlui-pg copy {3-5} display name="Example: headerTemplate" /headerTemplate/ height="200px"
<App>
<Tabs>
<property name="headerTemplate">
{$header.label}{$header.id ? ' | ID: ' + $header.id : ''}
</property>
<TabItem label="Home" id="home-tab">
Home content
</TabItem>
<TabItem label="Accounts" id="accounts-tab">
Accounts content
</TabItem>
<TabItem label="Settings">
Settings content
</TabItem>
</Tabs>
</App>
```
> [!INFO] Individual `TabItem` children can customize their [header templates](./TabItem#headertemplate), too.
%-PROP-END
%-PROP-START tabAlignment
The `tabAlignment` property controls how tabs are aligned within the tab header container in horizontal orientation. It accepts four values: `start`, `end`, `center`, and `stretch`.
> [!NOTE] The `tabAlignment` property is ignored when `orientation` is set to `vertical` or when `accordionView` is enabled.
**Alignment: start**
Aligns tabs to the left side of the container:
```xmlui-pg copy display name="Example: tabAlignment='start'" /tabAlignment/ height="200px"
<App>
<Tabs tabAlignment="start" style="width: 100%">
<TabItem label="Home">Home content</TabItem>
<TabItem label="Profile">Profile content</TabItem>
<TabItem label="Settings">Settings content</TabItem>
</Tabs>
</App>
```
**Alignment: center**
Centers tabs within the container:
```xmlui-pg copy display name="Example: tabAlignment='center'" /tabAlignment/ height="200px"
<App>
<Tabs tabAlignment="center" style="width: 100%">
<TabItem label="Home">Home content</TabItem>
<TabItem label="Profile">Profile content</TabItem>
<TabItem label="Settings">Settings content</TabItem>
</Tabs>
</App>
```
**Alignment: end**
Aligns tabs to the right side of the container:
```xmlui-pg copy display name="Example: tabAlignment='end'" /tabAlignment/ height="200px"
<App>
<Tabs tabAlignment="end" style="width: 100%">
<TabItem label="Home">Home content</TabItem>
<TabItem label="Profile">Profile content</TabItem>
<TabItem label="Settings">Settings content</TabItem>
</Tabs>
</App>
```
**Alignment: stretch**
Makes tabs fill the full width of the container, distributing them evenly:
```xmlui-pg copy display name="Example: tabAlignment='stretch'" /tabAlignment/ height="200px"
<App>
<Tabs tabAlignment="stretch" style="width: 100%">
<TabItem label="Home">Home content</TabItem>
<TabItem label="Profile">Profile content</TabItem>
<TabItem label="Settings">Settings content</TabItem>
</Tabs>
</App>
```
%-PROP-END
%-PROP-START accordionView
The `accordionView` property enables an accordion-like layout where tab headers are stacked vertically and only the active tab's content is visible. All tab headers remain visible, and clicking a header opens its content while closing others.
> [!NOTE] When `accordionView` is enabled, the `orientation` property is ignored.
```xmlui-pg copy display name="Example: accordionView" /accordionView/ height="480px"
<App>
<Tabs accordionView="true">
<TabItem label="Account Information">
<Card title="Account Details">
<Text>Your account is active and in good standing.</Text>
<Text>Account ID: 12345</Text>
<Text>Member since: January 2024</Text>
</Card>
</TabItem>
<TabItem label="Billing & Payments">
<Card title="Payment Methods">
<Text>Current Plan: Professional</Text>
<Text>Next billing date: November 1, 2025</Text>
<Text>Payment method: xxxx 4242</Text>
</Card>
</TabItem>
<TabItem label="Security Settings">
<Card title="Security Options">
<Text>Two-factor authentication: Enabled</Text>
<Text>Last password change: September 15, 2025</Text>
<Text>Active sessions: 2</Text>
</Card>
</TabItem>
</Tabs>
</App>
```
The accordion view is particularly useful for mobile layouts or when you need to present expandable sections in a vertical format. Each section can be opened independently by clicking its header.
```xmlui-pg copy display name="Example: accordionView with dynamic content" /accordionView/ height="450px"
<App>
<Tabs accordionView="true">
<Items data="{[
{title: 'Overview', content: 'Dashboard and quick statistics'},
{title: 'Reports', content: 'Monthly and annual reports'},
{title: 'Analytics', content: 'User behavior and metrics'},
{title: 'Export', content: 'Download data in various formats'}
]}">
<TabItem label="{$item.title}">
<Card>
<Text>{$item.content}</Text>
</Card>
</TabItem>
</Items>
</Tabs>
</App>
```
%-PROP-END
%-API-START next
```xmlui-pg copy display name="Example: next()" /next/ height="250px"
<App>
<Fragment>
<Tabs id="myTabs">
<TabItem label="Tab 1">Content 1</TabItem>
<TabItem label="Tab 2">Content 2</TabItem>
<TabItem label="Tab 3">Content 3</TabItem>
</Tabs>
<Button onClick="myTabs.next()">Next Tab</Button>
</Fragment>
</App>
```
%-API-END
%-EVENT-START didChange
The event handler gets these parameters, which refer to the active tab after the change:
- `index`: The active tab index
- `id`: The identifier of the active tab (if not defined, the framework provides an auto-generated id)
- `label`: The label of the active tab
```xmlui-pg copy display name="Example: didChange" /onDidChange/ height="200px"
<App var.lastTab="(none)">
<Tabs onDidChange="
(newIndex, id, label) =>
lastTab = newIndex + ': ' + label + ' (' + id + ')'
">
<TabItem id="account" label="Account">
<Text>Account</Text>
</TabItem>
<TabItem label="Stream">
<Text>Stream</Text>
</TabItem>
<TabItem label="Support">
<Text>Support</Text>
</TabItem>
</Tabs>
<Text>Tab index changed to {lastTab}</Text>
</App>
```
%-EVENT-END
%-API-START prev
```xmlui-pg copy display name="Example: prev()" /prev/ height="250px"
<App>
<Fragment>
<Tabs id="myTabs">
<TabItem label="Tab 1">Content 1</TabItem>
<TabItem label="Tab 2">Content 2</TabItem>
<TabItem label="Tab 3">Content 3</TabItem>
</Tabs>
<Button onClick="myTabs.prev()">Previous Tab</Button>
</Fragment>
</App>
```
%-API-END
%-API-START setActiveTabIndex
```xmlui-pg copy display name="Example: setActiveTabIndex()" /setActiveTabIndex/ height="250px"
<App>
<Fragment>
<Tabs id="myTabs">
<TabItem label="Tab 1">Content 1</TabItem>
<TabItem label="Tab 2">Content 2</TabItem>
<TabItem label="Tab 3">Content 3</TabItem>
</Tabs>
<Button onClick="myTabs.setActiveTabIndex(2)">Go to Tab 3 (by Index)</Button>
</Fragment>
</App>
```
%-API-END
%-API-START setActiveTabById
```xmlui-pg copy display name="Example: setActiveTabById()" /setActiveTabById/ height="250px"
<App>
<Fragment>
<Tabs id="myTabs">
<TabItem label="Home" id="home">Home Content</TabItem>
<TabItem label="Settings" id="settings">Settings Content</TabItem>
<TabItem label="Help" id="help">Help Content</TabItem>
</Tabs>
<Button onClick="myTabs.setActiveTabById('settings')">
Go to Settings (by ID)
</Button>
</Fragment>
</App>
```
%-API-END
```