This is page 39 of 140. Use http://codebase.md/xmlui-org/xmlui/mockApiDef.js?lines=false&page={x} to view the full context.
# Directory Structure
```
├── .changeset
│ ├── config.json
│ ├── silver-llamas-cough.md
│ └── true-jeans-agree.md
├── .eslintrc.cjs
├── .github
│ ├── build-checklist.png
│ ├── ISSUE_TEMPLATE
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ └── workflows
│ ├── deploy-blog.yml
│ ├── deploy-docs-optimized.yml
│ ├── deploy-docs.yml
│ ├── prepare-versions.yml
│ ├── release-packages.yml
│ ├── run-all-tests.yml
│ └── run-smoke-tests.yml
├── .gitignore
├── .prettierrc.js
├── .vscode
│ ├── launch.json
│ └── settings.json
├── blog
│ ├── .gitignore
│ ├── .gitkeep
│ ├── CHANGELOG.md
│ ├── extensions.ts
│ ├── index.html
│ ├── index.ts
│ ├── package.json
│ ├── public
│ │ ├── blog
│ │ │ ├── images
│ │ │ │ ├── an-advanced-codefence.mp4
│ │ │ │ ├── blog-page-component.png
│ │ │ │ ├── blog-scrabble.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
--------------------------------------------------------------------------------
/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-full-header">
<NavPanel>
<NavGroup label="Blog">
<NavLink label="Newest post" to="/newest-post" />
<NavLink label="Older post" 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.
```
--------------------------------------------------------------------------------
/tools/vscode/CHANGELOG.md:
--------------------------------------------------------------------------------
```markdown
# xmlui-vscode
## 0.10.26
### Patch Changes
- Updated dependencies [e1b8d58]
- Updated dependencies [1ad832c]
- [email protected]
## 0.10.25
### Patch Changes
- Updated dependencies [e7c503e]
- Updated dependencies [5fe3052]
- Updated dependencies [5fe3052]
- Updated dependencies [250647b]
- [email protected]
## 0.10.24
### Patch Changes
- Updated dependencies [3e361c4]
- Updated dependencies [3e361c4]
- [email protected]
## 0.10.23
### Patch Changes
- Updated dependencies [bf18444]
- Updated dependencies [6d3bb89]
- Updated dependencies [89c69af]
- Updated dependencies [4cfebf0]
- Updated dependencies [145cd68]
- [email protected]
## 0.10.22
### Patch Changes
- Updated dependencies [501f60a]
- Updated dependencies [1020f1c]
- [email protected]
## 0.10.21
### Patch Changes
- Updated dependencies [6fd4d62]
- [email protected]
## 0.10.20
### Patch Changes
- Updated dependencies [26eac90]
- Updated dependencies [f53edff]
- Updated dependencies [1840916]
- Updated dependencies [c6be7a3]
- Updated dependencies [6aaefaf]
- Updated dependencies [28d2585]
- Updated dependencies [e29a231]
- Updated dependencies [22162c0]
- Updated dependencies [e90232b]
- [email protected]
## 0.10.19
### Patch Changes
- Updated dependencies [facb257]
- Updated dependencies [6084c14]
- Updated dependencies [e1fa9d7]
- [email protected]
## 0.10.18
### Patch Changes
- Updated dependencies [202f2b2]
- Updated dependencies [6650ee8]
- Updated dependencies [da98994]
- Updated dependencies [8394663]
- [email protected]
## 0.10.17
### Patch Changes
- Updated dependencies [07dae0b]
- [email protected]
## 0.10.16
### Patch Changes
- Updated dependencies [0ba6612]
- Updated dependencies [7b78052]
- Updated dependencies [314b429]
- Updated dependencies [a1dea8f]
- Updated dependencies [cff754c]
- [email protected]
## 0.10.15
### Patch Changes
- Updated dependencies [3c8ad14]
- Updated dependencies [5502fea]
- Updated dependencies [e08f0ba]
- Updated dependencies [5502fea]
- Updated dependencies [db618b5]
- Updated dependencies [a795b3d]
- Updated dependencies [5851c02]
- [email protected]
## 0.10.14
### Patch Changes
- Updated dependencies [618049b]
- Updated dependencies [215a142]
- Updated dependencies [65b52e1]
- Updated dependencies [0cc2178]
- Updated dependencies [53d4ed9]
- [email protected]
## 0.10.13
### Patch Changes
- Updated dependencies [9401ee0]
- Updated dependencies [eb62858]
- Updated dependencies [eb62858]
- Updated dependencies [eb62858]
- Updated dependencies [eb62858]
- Updated dependencies [eb62858]
- Updated dependencies [eb62858]
- Updated dependencies [243b7fa]
- Updated dependencies [eb62858]
- [email protected]
## 0.10.12
### Patch Changes
- Updated dependencies [f12a042]
- Updated dependencies [8731eb8]
- Updated dependencies [eb6454f]
- Updated dependencies [1210852]
- [email protected]
## 0.10.11
### Patch Changes
- Updated dependencies [8c76c8d]
- Updated dependencies [d56c3e5]
- Updated dependencies [e42d367]
- Updated dependencies [f539526]
- Updated dependencies [19ce234]
- Updated dependencies [455b6c0]
- Updated dependencies [e90dc73]
- Updated dependencies [819b563]
- Updated dependencies [b57dfa2]
- Updated dependencies [9dd0f97]
- Updated dependencies [19ce234]
- Updated dependencies [898346d]
- Updated dependencies [705dd04]
- [email protected]
## 0.10.10
### Patch Changes
- Updated dependencies [fff80c5]
- [email protected]
## 0.10.9
### Patch Changes
- Updated dependencies [879c09d]
- Updated dependencies [3ad8514]
- Updated dependencies [0c69245]
- Updated dependencies [4ad31fc]
- Updated dependencies [c99f184]
- Updated dependencies [5032e4a]
- Updated dependencies [2394f36]
- [email protected]
## 0.10.8
### Patch Changes
- Updated dependencies [a4d62c4]
- Updated dependencies [7ed2918]
- [email protected]
## 0.10.7
### Patch Changes
- Updated dependencies [664ea4f]
- Updated dependencies [a739a26]
- Updated dependencies [bdb54dd]
- Updated dependencies [81724c6]
- [email protected]
## 0.10.6
### Patch Changes
- Updated dependencies [6464ec8]
- [email protected]
## 0.10.5
### Patch Changes
- Updated dependencies [d38351d]
- [email protected]
## 0.10.4
### Patch Changes
- Updated dependencies [43fd8c5]
- Updated dependencies [1df8e5d]
- Updated dependencies [0d5d9d1]
- Updated dependencies [3def673]
- Updated dependencies [428ebea]
- Updated dependencies [a12ce66]
- [email protected]
## 0.10.3
### Patch Changes
- Updated dependencies [2e512bb]
- Updated dependencies [46d1d18]
- Updated dependencies [6bc9ed1]
- Updated dependencies [0b1f983]
- Updated dependencies [a2637f3]
- Updated dependencies [eb4d592]
- [email protected]
## 0.10.2
### Patch Changes
- Updated dependencies [ff14e15]
- Updated dependencies [1451a94]
- [email protected]
## 0.10.1
### Patch Changes
- Updated dependencies [442416b]
- Updated dependencies [a018431]
- Updated dependencies [33cb547]
- Updated dependencies [b5d7537]
- [email protected]
## 0.10.0
### Patch Changes
- Updated dependencies [6d0ce52]
- Updated dependencies [8c98f33]
- Updated dependencies [ef86593]
- Updated dependencies [da5f4e7]
- Updated dependencies [47c7a2d]
- Updated dependencies [740f904]
- Updated dependencies [5009c52]
- Updated dependencies [000a311]
- Updated dependencies [eb8b958]
- Updated dependencies [2f5ec32]
- [email protected]
## 0.9.101
### Patch Changes
- Updated dependencies [791b0be]
- [email protected]
## 0.9.100
### Patch Changes
- Updated dependencies [2dbf6d2]
- [email protected]
## 0.9.99
### Patch Changes
- Updated dependencies [e5a09fb]
- Updated dependencies [36360f6]
- [email protected]
## 0.9.98
### Patch Changes
- Updated dependencies [ff781f3]
- Updated dependencies [377f0f2]
- Updated dependencies [ce0ff76]
- Updated dependencies [208768a]
- [email protected]
## 0.9.97
### Patch Changes
- Updated dependencies [f7e8019]
- [email protected]
## 0.9.96
### Patch Changes
- Updated dependencies [3196156]
- Updated dependencies [cfee78a]
- Updated dependencies [f51002a]
- Updated dependencies [3fa52d9]
- [email protected]
## 0.9.95
### Patch Changes
- Updated dependencies [af6a7a0]
- Updated dependencies [69a2a8f]
- Updated dependencies [29c68fe]
- [email protected]
## 0.9.94
### Patch Changes
- Updated dependencies [1d9365c]
- [email protected]
## 0.9.93
### Patch Changes
- Updated dependencies [af17117]
- Updated dependencies [44da3d9]
- Updated dependencies [b7a6b9a]
- Updated dependencies [bc95844]
- Updated dependencies [52d94a2]
- Updated dependencies [6629ce5]
- Updated dependencies [0254471]
- Updated dependencies [3318cfb]
- [email protected]
## 0.9.92
### Patch Changes
- Updated dependencies [347cda1]
- [email protected]
## 0.9.91
### Patch Changes
- Updated dependencies [6a7d779]
- [email protected]
## 0.9.90
### Patch Changes
- Updated dependencies [4b57f7e]
- [email protected]
## 0.9.89
### Patch Changes
- Updated dependencies [2968eb9]
- Updated dependencies [94f4eb5]
- Updated dependencies [8364c03]
- [email protected]
## 0.9.88
### Patch Changes
- d4235e9: feat: layout properties displayed later in completion list
- Updated dependencies [b79d7d8]
- [email protected]
## 0.9.87
### Patch Changes
- Updated dependencies [33846c2]
- [email protected]
## 0.9.86
### Patch Changes
- Updated dependencies [48af60d]
- [email protected]
## 0.9.85
### Patch Changes
- Updated dependencies [ee8d6ad]
- Updated dependencies [9ca7572]
- Updated dependencies [6944d2f]
- Updated dependencies [c0c10e7]
- Updated dependencies [cbe1ef2]
- [email protected]
## 0.9.84
### Patch Changes
- Updated dependencies [c54abf3]
- [email protected]
## 0.9.83
### Patch Changes
- Updated dependencies [8e3d6a3]
- Updated dependencies [8644010]
- [email protected]
## 0.9.82
### Patch Changes
- Updated dependencies [3bc29ae]
- Updated dependencies [1101bf5]
- Updated dependencies [cd8db58]
- Updated dependencies [13beb58]
- Updated dependencies [79c1d8a]
- [email protected]
## 0.9.81
### Patch Changes
- Updated dependencies [59680b7]
- [email protected]
## 0.9.80
### Patch Changes
- Updated dependencies [4598566]
- Updated dependencies [14e6a7d]
- Updated dependencies [cf05bd2]
- [email protected]
## 0.9.79
### Patch Changes
- Updated dependencies [ad21a31]
- [email protected]
## 0.9.78
### Patch Changes
- Updated dependencies [94a68f0]
- Updated dependencies [94a68f0]
- Updated dependencies [163a45c]
- Updated dependencies [7ce528b]
- Updated dependencies [c6eb9a8]
- [email protected]
## 0.9.77
### Patch Changes
- Updated dependencies [c867f38]
- [email protected]
## 0.9.76
### Patch Changes
- 15bf622: fix: add escaped \{ to textmate syntax, eliminate double extraction of props in FormItem causing bugs with escaped open curly brace being parsed as start of binding expression.
- Updated dependencies [aa08a8c]
- Updated dependencies [15bf622]
- Updated dependencies [5761868]
- [email protected]
## 0.9.75
### Patch Changes
- 2edefdd: feat: ensure xmlui-vscode is versioned with xmlui
- Updated dependencies [c876be8]
- [email protected]
## 0.1.2
### Patch Changes
- 2e808fa: Add custom xmlui formatter to better handle formatting erroneous syntax, long lines, and xmlui specific syntax, like backtick quoted strings, key-only attributes, etc...
- Updated dependencies [96273bf]
- Updated dependencies [1a81bcf]
- [email protected]
## 0.1.1
### Patch Changes
- e964286: Add formating to language server and fix error recovery when tag name is erroneous
- Updated dependencies [de8d63c]
- Updated dependencies [bd6d1b4]
- Updated dependencies [db5a5f4]
- Updated dependencies [69b4402]
- [email protected]
## 0.0.5
### Patch Changes
- 42416ba: test change for CI #2
- Updated dependencies [42416ba]
- [email protected]
## 0.0.3
### Patch Changes
- e43b92a: another test change
## 0.0.2
### Patch Changes
- 3be75a4: test release for CI
- Updated dependencies [8d662f3]
- [email protected]
```
--------------------------------------------------------------------------------
/xmlui/src/components/Charts/LineChart/LineChartNative.tsx:
--------------------------------------------------------------------------------
```typescript
import {
Line,
LineChart as RLineChart,
XAxis,
ResponsiveContainer,
Tooltip,
Legend as RLegend,
YAxis,
} from "recharts";
import type { ForwardedRef, ReactNode } from "react";
import type React from "react";
import { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from "react";
import ChartProvider, { useChartContextValue } from "../utils/ChartProvider";
import { TooltipContent } from "../Tooltip/TooltipContent";
import { useTheme } from "../../../components-core/theming/ThemeContext";
import classnames from "classnames";
import styles from './LineChart.module.scss';
export type LineChartProps = {
data: any[];
dataKeys: string[];
nameKey: string;
style?: React.CSSProperties;
hideTickX?: boolean;
hideTickY?: boolean;
className?: string;
hideX?: boolean;
hideY?: boolean;
hideTooltip?: boolean;
tickFormatterX?: (value: any) => any;
tickFormatterY?: (value: any) => any;
children?: ReactNode;
showLegend?: boolean;
marginTop?: number;
marginRight?: number;
marginBottom?: number;
marginLeft?: number;
tooltipRenderer?: (tooltipData: any) => ReactNode;
};
export const defaultProps: Pick<LineChartProps, "hideX" | "hideY" | "hideTooltip" | "showLegend" | "tickFormatterX" | "tickFormatterY" | "hideTickX" | "hideTickY"> = {
hideTickX: false,
hideTickY: false,
hideX: false,
hideY: false,
hideTooltip: false,
showLegend: false,
tickFormatterX: (value) => value,
tickFormatterY: (value) => value,
};
const defaultChartParams = {
chartWidth: 800,
};
export const LineChart = forwardRef(function LineChart({
data,
dataKeys = [],
nameKey,
style,
className,
hideX = defaultProps.hideX,
hideY = defaultProps.hideY,
hideTickX = defaultProps.hideTickX,
hideTickY = defaultProps.hideTickY,
hideTooltip = defaultProps.hideTooltip,
tickFormatterX = defaultProps.tickFormatterX,
tickFormatterY = defaultProps.tickFormatterY,
children,
showLegend = defaultProps.showLegend,
tooltipRenderer,
}: LineChartProps, forwardedRef: ForwardedRef<HTMLDivElement>) {
const { getThemeVar } = useTheme();
const colorValues = useMemo(() => {
return [
getThemeVar("color-primary-500"),
getThemeVar("color-primary-300"),
getThemeVar("color-success-500"),
getThemeVar("color-success-300"),
getThemeVar("color-warn-500"),
getThemeVar("color-warn-300"),
getThemeVar("color-danger-500"),
getThemeVar("color-danger-300"),
getThemeVar("color-info-500"),
getThemeVar("color-info-300"),
getThemeVar("color-secondary-500"),
getThemeVar("color-secondary-300"),
getThemeVar("color-primary-600"),
getThemeVar("color-primary-400"),
getThemeVar("color-success-600"),
getThemeVar("color-success-400"),
getThemeVar("color-warn-600"),
getThemeVar("color-warn-400"),
getThemeVar("color-danger-600"),
getThemeVar("color-danger-400"),
getThemeVar("color-info-600"),
getThemeVar("color-info-400"),
getThemeVar("color-secondary-600"),
getThemeVar("color-secondary-400"),
getThemeVar("color-primary-900"),
getThemeVar("color-primary-700"),
getThemeVar("color-success-900"),
getThemeVar("color-success-700"),
getThemeVar("color-warn-900"),
getThemeVar("color-warn-700"),
getThemeVar("color-danger-900"),
getThemeVar("color-danger-700"),
getThemeVar("color-info-900"),
getThemeVar("color-info-700"),
getThemeVar("color-secondary-900"),
getThemeVar("color-secondary-700"),
];
}, [getThemeVar]);
const chartContextValue = useChartContextValue({ nameKey, dataKeys });
const containerRef = useRef<HTMLDivElement>(null);
const labelsRef = useRef<HTMLDivElement>(null);
const [interval, setIntervalState] = useState(0);
const [tickAngle, setTickAngle] = useState(0);
const [tickAnchor, setTickAnchor] = useState<"end" | "middle">("middle");
const [chartMargin, setChartMargin] = useState({ left: 30, right: 30, top: 10, bottom: 60 });
const [xAxisHeight, setXAxisHeight] = useState(50);
const [miniMode, setMiniMode] = useState(false);
const fontSize = 12;
const safeData = Array.isArray(data) ? data : [];
const safeTooltipRenderer = useCallback(
(props: any) => {
if (!tooltipRenderer) return <TooltipContent {...props}/>;
const payloadArray: Array<{ label: string; value: any; color: string }> = [];
if (props.payload && props.payload.length > 0 && props.payload[0].payload) {
const originalPayload = props.payload[0].payload;
// Transform dataKeys into array of objects with label, value, and color
dataKeys.forEach((dataKey, index) => {
if (dataKey in originalPayload) {
payloadArray.push({
label: dataKey,
value: originalPayload[dataKey],
color: colorValues[index] || colorValues[0]
});
}
});
}
// Extract tooltip data from Recharts props
const tooltipData = {
label: props.label,
payload: payloadArray,
active: props.active,
};
return tooltipRenderer(tooltipData);
},
[tooltipRenderer, dataKeys, colorValues]
);
useEffect(() => {
const calc = () => {
const width = containerRef.current?.offsetWidth || defaultChartParams.chartWidth;
const spans = labelsRef.current?.querySelectorAll("span") || [];
const maxWidth = Array.from(spans).reduce((mx, s) => Math.max(mx, s.offsetWidth), 50);
let angle = 0;
let anchor: "end" | "middle" = "middle";
let rad = 0;
let minTickSpacing = maxWidth + 8;
let leftMargin = Math.max(8, Math.ceil(maxWidth / 3));
let rightMargin = Math.max(8, Math.ceil(maxWidth / 3));
let xAxisH = Math.ceil(fontSize * 1.2);
let maxTicks = Math.max(1, Math.floor(width / minTickSpacing));
let skip = Math.max(0, Math.ceil(safeData.length / maxTicks) - 1);
if (skip > 0) {
angle = -60;
anchor = "end";
rad = (Math.abs(angle) * Math.PI) / 180;
minTickSpacing = Math.ceil(maxWidth * Math.cos(rad)) + 2;
maxTicks = Math.max(1, Math.floor(width / minTickSpacing));
skip = Math.max(0, Math.ceil(safeData.length / maxTicks) - 1);
leftMargin = Math.max(8, Math.ceil((maxWidth * Math.cos(rad)) / 1.8));
rightMargin = Math.max(8, Math.ceil((maxWidth * Math.cos(rad)) / 1.8));
xAxisH = Math.ceil(Math.abs(maxWidth * Math.sin(rad)) + Math.abs(fontSize * Math.cos(rad)));
}
setIntervalState(skip);
setTickAngle(angle);
setTickAnchor(anchor);
setChartMargin({ left: leftMargin, right: rightMargin, top: 10, bottom: xAxisH });
setXAxisHeight(Math.ceil(fontSize));
const containerHeight = containerRef.current?.offsetHeight || 0;
const neededHeight = 10 + xAxisHeight + 10 + 32;
setMiniMode(neededHeight > containerHeight);
};
calc();
window.addEventListener("resize", calc);
return () => window.removeEventListener("resize", calc);
}, [data, nameKey, xAxisHeight, safeData.length, fontSize]);
// The stroke width of the lines
const strokeWidth = getThemeVar("width-line-LineChart");
return (
<ChartProvider value={chartContextValue}>
{children}
<div
ref={labelsRef}
style={{ position: "absolute", visibility: "hidden", height: 0, overflow: "hidden" }}
>
{safeData.length > 0 && nameKey
? safeData
.map((d) => d?.[nameKey])
.map((label, idx) => (
<span key={idx} style={{ fontSize: 12, whiteSpace: "nowrap" }}>
{label}
</span>
))
: null}
</div>
<div
ref={forwardedRef}
className={classnames(className, styles.wrapper)}
style={{ flexGrow: 1, ...style}}
>
<ResponsiveContainer
ref={containerRef}
width="100%"
height="100%"
minWidth={60}
minHeight={30}
debounce={100}
>
<RLineChart
accessibilityLayer
data={data}
margin={miniMode ? { left: 0, right: 0, top: 0, bottom: 0 } : chartMargin}
>
<XAxis
interval={interval}
tickLine={false}
dataKey={nameKey}
angle={tickAngle}
textAnchor={tickAnchor}
tick={miniMode ? false : !hideTickX && { fill: "currentColor", fontSize }}
tickFormatter={miniMode ? undefined : tickFormatterX}
height={miniMode || hideX ? 0 : xAxisHeight}
hide={miniMode || hideX}
/>
<YAxis
hide={miniMode || hideY}
tickLine={false}
tickFormatter={miniMode ? undefined : tickFormatterY}
tick={miniMode ? false : !hideTickY && { fill: "currentColor", fontSize }}
/>
{!miniMode && !hideTooltip && <Tooltip content={safeTooltipRenderer} />}
{dataKeys.map((dataKey, i) => (
<Line
key={dataKey}
type="monotone"
dataKey={dataKey}
name={dataKey}
stroke={colorValues[i]}
strokeWidth={strokeWidth}
dot={false}
/>
))}
{showLegend && (
<RLegend
wrapperStyle={{
bottom: 0,
left: 0,
right: 0,
margin: "0 auto",
width: "100%",
textAlign: "center",
}}
/>
)}
</RLineChart>
</ResponsiveContainer>
</div>
</ChartProvider>
);
});
```
--------------------------------------------------------------------------------
/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
```
--------------------------------------------------------------------------------
/xmlui/src/components/DropdownMenu/DropdownMenu.tsx:
--------------------------------------------------------------------------------
```typescript
import styles from "./DropdownMenu.module.scss";
import { createComponentRenderer } from "../../components-core/renderers";
import { parseScssVar } from "../../components-core/theming/themeVars";
import { alignmentOptionMd, buttonThemeMd, buttonVariantMd, iconPositionMd } from "../abstractions";
import { createMetadata, d, dClick, dEnabled, dLabel, dTriggerTemplate } from "../metadata-helpers";
import { Icon } from "../Icon/IconNative";
import {
defaultDropdownMenuProps,
defaultMenuItemProps,
DropdownMenu,
MenuItem,
MenuSeparator,
SubMenuItem,
} from "./DropdownMenuNative";
const DDMCOMP = "DropdownMenu";
export const DropdownMenuMd = createMetadata({
status: "stable",
description:
"`DropdownMenu` provides a space-efficient way to present multiple options or " +
"actions through a collapsible interface. When clicked, the trigger button reveals " +
"a menu that can include items, separators, and nested submenus, making it ideal " +
"for navigation, action lists, or any situation requiring many options without " +
"permanent screen space.",
props: {
label: dLabel(),
triggerTemplate: dTriggerTemplate(DDMCOMP),
alignment: {
description:
"This property allows you to determine the alignment of the dropdown panel with " +
"the displayed menu items.",
valueType: "string",
availableValues: alignmentOptionMd,
defaultValue: defaultDropdownMenuProps.alignment,
},
enabled: dEnabled(),
triggerButtonVariant: {
description:
`This property defines the theme variant of the \`Button\` as the dropdown menu's trigger. ` +
`It has no effect when a custom trigger is defined with \`triggerTemplate\`.`,
valueType: "string",
availableValues: buttonVariantMd,
defaultValue: defaultDropdownMenuProps.triggerButtonVariant,
},
triggerButtonThemeColor: {
description:
`This property defines the theme color of the \`Button\` as the dropdown menu's trigger. ` +
`It has no effect when a custom trigger is defined with \`triggerTemplate\`.`,
valueType: "string",
availableValues: buttonThemeMd,
defaultValue: defaultDropdownMenuProps.triggerButtonThemeColor,
},
triggerButtonIcon: {
description:
`This property defines the icon to display on the trigger button. You can change the default icon ` +
`for all ${DDMCOMP} instances with the "icon.triggerButton:DropdownMenu" declaration in the app ` +
`configuration file.`,
defaultValue: defaultDropdownMenuProps.triggerButtonIcon,
valueType: "string",
},
triggerButtonIconPosition: {
description: `This property defines the position of the icon on the trigger button.`,
defaultValue: defaultDropdownMenuProps.triggerButtonIconPosition,
valueType: "string",
availableValues: iconPositionMd,
},
},
events: {
willOpen: d(
`This event fires when the \`${DDMCOMP}\` component is about to be opened. ` +
`You can prevent opening the menu by returning \`false\` from the event handler. ` +
`Otherwise, the menu will open at the end of the event handler like normal.`,
),
},
apis: {
close: {
description: `This method command closes the dropdown.`,
signature: "close(): void",
},
open: {
description: `This method command opens the dropdown.`,
signature: "open(): void",
},
},
themeVars: parseScssVar(styles.themeVars),
limitThemeVarsToComponent: true,
defaultThemeVars: {
[`backgroundColor-${DDMCOMP}`]: "$color-surface-raised",
[`minWidth-${DDMCOMP}`]: "160px",
[`boxShadow-${DDMCOMP}`]: "$boxShadow-xl",
[`borderStyle-${DDMCOMP}-content`]: "solid",
[`borderRadius-${DDMCOMP}`]: "$borderRadius",
},
});
export const dropdownMenuComponentRenderer = createComponentRenderer(
DDMCOMP,
DropdownMenuMd,
({ node, extractValue, renderChild, registerComponentApi, className, lookupEventHandler }) => {
return (
<DropdownMenu
triggerTemplate={renderChild(node.props?.triggerTemplate)}
label={extractValue(node.props?.label)}
registerComponentApi={registerComponentApi}
onWillOpen={lookupEventHandler("willOpen")}
className={className}
alignment={extractValue(node.props?.alignment)}
disabled={!extractValue.asOptionalBoolean(node.props.enabled, true)}
triggerButtonThemeColor={extractValue(node.props.triggerButtonThemeColor)}
triggerButtonVariant={extractValue(node.props.triggerButtonVariant)}
triggerButtonIcon={extractValue(node.props.triggerButtonIcon)}
triggerButtonIconPosition={extractValue(node.props.triggerButtonIconPosition)}
>
{renderChild(node.children)}
</DropdownMenu>
);
},
);
const MICOMP = "MenuItem";
export const MenuItemMd = createMetadata({
status: "stable",
description:
"`MenuItem` represents individual clickable items within dropdown menus and other " +
"menu components. Each menu item can display text, icons, and respond to clicks " +
"with either navigation or custom actions, making it the building block for " +
"interactive menu systems.",
docFolder: DDMCOMP,
props: {
iconPosition: {
description: `This property allows you to determine the position of the icon displayed in the menu item.`,
valueType: "string",
availableValues: iconPositionMd,
defaultValue: defaultMenuItemProps.iconPosition,
},
icon: {
description: `This property names an optional icon to display with the menu item. You can use component-specific icons in the format "iconName:MenuItem".`,
valueType: "string",
},
label: dLabel(),
to: {
description:
`This property defines the URL of the menu item. If this property is defined (and the \`click\` ` +
`event does not have an event handler), clicking the menu item navigates to this link.`,
valueType: "string",
},
active: {
description: `This property indicates if the specified menu item is active.`,
valueType: "boolean",
defaultValue: defaultMenuItemProps.active,
},
enabled: dEnabled(),
},
events: {
click: dClick(MICOMP),
},
themeVars: parseScssVar(styles.themeVars),
limitThemeVarsToComponent: true,
defaultThemeVars: {
[`backgroundColor-${MICOMP}`]: "$backgroundColor-dropdown-item",
[`color-${MICOMP}`]: "$textColor-primary",
[`color-${MICOMP}--disabled`]: "$textColor--disabled",
[`fontFamily-${MICOMP}`]: "$fontFamily",
[`fontSize-${MICOMP}`]: "$fontSize-sm",
[`paddingVertical-${MICOMP}`]: "$space-2",
[`paddingHorizontal-${MICOMP}`]: "$space-3",
[`backgroundColor-${MICOMP}--hover`]: "$backgroundColor-dropdown-item--hover",
[`color-${MICOMP}--hover`]: "inherit",
[`gap-${MICOMP}`]: "$space-2",
[`color-${MICOMP}--active`]: "$color-primary",
[`backgroundColor-${MICOMP}--active`]: "$backgroundColor-dropdown-item--active",
light: {},
dark: {},
},
});
export const menuItemRenderer = createComponentRenderer(
MICOMP,
MenuItemMd,
({ node, renderChild, lookupEventHandler, lookupAction, extractValue, className }) => {
const clickEventHandler = lookupEventHandler("click");
let clickHandler;
const to = extractValue(node.props.to);
if (!clickEventHandler && to?.trim()) {
const navigateAction = lookupAction("navigate", { signError: false });
clickHandler = () => {
navigateAction?.({ pathname: to });
};
}
return (
<MenuItem
onClick={clickHandler}
label={extractValue(node.props?.label)}
className={className}
iconPosition={extractValue(node.props.iconPosition)}
icon={
node.props?.icon && (
<Icon name={extractValue(node.props.icon)} fallback={extractValue(node.props.icon)} />
)
}
active={extractValue.asOptionalBoolean(node.props.active, false)}
enabled={extractValue.asOptionalBoolean(node.props.enabled, true)}
>
{renderChild(node.children)}
</MenuItem>
);
},
);
const SMCOMP = "SubMenuItem";
export const SubMenuItemMd = createMetadata({
status: "stable",
description:
"`SubMenuItem` creates hierarchical menu structures by acting as both a menu " +
"item and a container for nested menu items. When clicked or hovered, it reveals " +
"a submenu containing additional [MenuItem](/components/MenuItem), " +
"[MenuSeparator](/components/MenuSeparator), or other " +
"[SubMenuItem](/components/SubMenuItems) components, enabling complex multi-level " +
"navigation and action organization.",
docFolder: DDMCOMP,
props: {
label: dLabel(),
triggerTemplate: dTriggerTemplate(SMCOMP),
},
});
export const subMenuItemRenderer = createComponentRenderer(
SMCOMP,
SubMenuItemMd,
({ node, renderChild, extractValue }) => {
return (
<SubMenuItem
label={extractValue(node.props?.label)}
triggerTemplate={renderChild(node.props?.triggerTemplate)}
>
{renderChild(node.children)}
</SubMenuItem>
);
},
);
const MSEP = "MenuSeparator";
export const MenuSeparatorMd = createMetadata({
status: "stable",
description:
"`MenuSeparator` displays a separator line between menu items to group related " +
"menu options within `DropdownMenu`.",
docFolder: DDMCOMP,
themeVars: parseScssVar(styles.themeVars),
limitThemeVarsToComponent: true,
defaultThemeVars: {
[`marginTop-${MSEP}`]: "$space-1",
[`marginBottom-${MSEP}`]: "$space-1",
[`width-${MSEP}`]: "100%",
[`height-${MSEP}`]: "1px",
[`color-${MSEP}`]: "$borderColor-dropdown-item",
[`marginHorizontal-${MSEP}`]: "12px",
},
});
export const menuSeparatorRenderer = createComponentRenderer(MSEP, MenuSeparatorMd, () => {
return <MenuSeparator />;
});
```