This is page 43 of 141. Use http://codebase.md/xmlui-org/xmlui/xmlui-standalone.umd.js?lines=false&page={x} to view the full context.
# Directory Structure
```
├── .changeset
│ └── config.json
├── .eslintrc.cjs
├── .github
│ ├── build-checklist.png
│ ├── ISSUE_TEMPLATE
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ └── workflows
│ ├── deploy-blog-optimized.yml
│ ├── deploy-blog-spa.yml
│ ├── deploy-blog.yml
│ ├── deploy-docs-optimized.yml
│ ├── deploy-docs.yml
│ ├── prepare-versions.yml
│ ├── release-packages.yml
│ ├── run-all-tests.yml
│ └── run-smoke-tests.yml
├── .gitignore
├── .prettierrc.js
├── .vscode
│ ├── launch.json
│ └── settings.json
├── blog
│ ├── .gitignore
│ ├── .gitkeep
│ ├── CHANGELOG.md
│ ├── extensions.ts
│ ├── index.html
│ ├── index.ts
│ ├── package.json
│ ├── public
│ │ ├── blog
│ │ │ ├── images
│ │ │ │ ├── an-advanced-codefence.gif
│ │ │ │ ├── an-advanced-codefence.mp4
│ │ │ │ ├── blog-page-component.png
│ │ │ │ ├── blog-scrabble.png
│ │ │ │ ├── codefence-runner.png
│ │ │ │ ├── integrated-blog-search.png
│ │ │ │ ├── lorem-ipsum.png
│ │ │ │ ├── playground-checkbox-source.png
│ │ │ │ ├── playground.png
│ │ │ │ ├── use-xmlui-mcp-to-find-a-howto.png
│ │ │ │ └── xmlui-demo-gallery.png
│ │ │ ├── introducing-xmlui.md
│ │ │ ├── lorem-ipsum.md
│ │ │ ├── newest-post.md
│ │ │ ├── older-post.md
│ │ │ ├── xmlui-playground.md
│ │ │ └── xmlui-powered-blog.md
│ │ ├── mockServiceWorker.js
│ │ ├── resources
│ │ │ ├── favicon.ico
│ │ │ ├── files
│ │ │ │ └── for-download
│ │ │ │ └── xmlui
│ │ │ │ └── xmlui-standalone.umd.js
│ │ │ ├── github.svg
│ │ │ ├── llms.txt
│ │ │ ├── logo-dark.svg
│ │ │ ├── logo.svg
│ │ │ ├── pg-popout.svg
│ │ │ ├── rss.svg
│ │ │ └── xmlui-logo.svg
│ │ ├── serve.json
│ │ └── web.config
│ ├── scripts
│ │ ├── download-latest-xmlui.js
│ │ ├── generate-rss.js
│ │ ├── get-releases.js
│ │ └── utils.js
│ ├── src
│ │ ├── components
│ │ │ ├── BlogOverview.xmlui
│ │ │ ├── BlogPage.xmlui
│ │ │ └── PageNotFound.xmlui
│ │ ├── config.ts
│ │ ├── Main.xmlui
│ │ └── themes
│ │ └── blog-theme.ts
│ └── tsconfig.json
├── CONTRIBUTING.md
├── docs
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── ComponentRefLinks.txt
│ ├── content
│ │ ├── _meta.json
│ │ ├── components
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── APICall.md
│ │ │ ├── App.md
│ │ │ ├── AppHeader.md
│ │ │ ├── AppState.md
│ │ │ ├── AutoComplete.md
│ │ │ ├── Avatar.md
│ │ │ ├── Backdrop.md
│ │ │ ├── Badge.md
│ │ │ ├── BarChart.md
│ │ │ ├── Bookmark.md
│ │ │ ├── Breakout.md
│ │ │ ├── Button.md
│ │ │ ├── Card.md
│ │ │ ├── Carousel.md
│ │ │ ├── ChangeListener.md
│ │ │ ├── Checkbox.md
│ │ │ ├── CHStack.md
│ │ │ ├── ColorPicker.md
│ │ │ ├── Column.md
│ │ │ ├── ContentSeparator.md
│ │ │ ├── CVStack.md
│ │ │ ├── DataSource.md
│ │ │ ├── DateInput.md
│ │ │ ├── DatePicker.md
│ │ │ ├── DonutChart.md
│ │ │ ├── DropdownMenu.md
│ │ │ ├── EmojiSelector.md
│ │ │ ├── ExpandableItem.md
│ │ │ ├── FileInput.md
│ │ │ ├── FileUploadDropZone.md
│ │ │ ├── FlowLayout.md
│ │ │ ├── Footer.md
│ │ │ ├── Form.md
│ │ │ ├── FormItem.md
│ │ │ ├── FormSection.md
│ │ │ ├── Fragment.md
│ │ │ ├── H1.md
│ │ │ ├── H2.md
│ │ │ ├── H3.md
│ │ │ ├── H4.md
│ │ │ ├── H5.md
│ │ │ ├── H6.md
│ │ │ ├── Heading.md
│ │ │ ├── HSplitter.md
│ │ │ ├── HStack.md
│ │ │ ├── Icon.md
│ │ │ ├── IFrame.md
│ │ │ ├── Image.md
│ │ │ ├── Items.md
│ │ │ ├── LabelList.md
│ │ │ ├── Legend.md
│ │ │ ├── LineChart.md
│ │ │ ├── Link.md
│ │ │ ├── List.md
│ │ │ ├── Logo.md
│ │ │ ├── Markdown.md
│ │ │ ├── MenuItem.md
│ │ │ ├── MenuSeparator.md
│ │ │ ├── ModalDialog.md
│ │ │ ├── NavGroup.md
│ │ │ ├── NavLink.md
│ │ │ ├── NavPanel.md
│ │ │ ├── NoResult.md
│ │ │ ├── NumberBox.md
│ │ │ ├── Option.md
│ │ │ ├── Page.md
│ │ │ ├── PageMetaTitle.md
│ │ │ ├── Pages.md
│ │ │ ├── Pagination.md
│ │ │ ├── PasswordInput.md
│ │ │ ├── PieChart.md
│ │ │ ├── ProgressBar.md
│ │ │ ├── Queue.md
│ │ │ ├── RadioGroup.md
│ │ │ ├── RealTimeAdapter.md
│ │ │ ├── Redirect.md
│ │ │ ├── Select.md
│ │ │ ├── Slider.md
│ │ │ ├── Slot.md
│ │ │ ├── SpaceFiller.md
│ │ │ ├── Spinner.md
│ │ │ ├── Splitter.md
│ │ │ ├── Stack.md
│ │ │ ├── StickyBox.md
│ │ │ ├── SubMenuItem.md
│ │ │ ├── Switch.md
│ │ │ ├── TabItem.md
│ │ │ ├── Table.md
│ │ │ ├── TableOfContents.md
│ │ │ ├── Tabs.md
│ │ │ ├── Text.md
│ │ │ ├── TextArea.md
│ │ │ ├── TextBox.md
│ │ │ ├── Theme.md
│ │ │ ├── TimeInput.md
│ │ │ ├── Timer.md
│ │ │ ├── ToneChangerButton.md
│ │ │ ├── ToneSwitch.md
│ │ │ ├── Tooltip.md
│ │ │ ├── Tree.md
│ │ │ ├── VSplitter.md
│ │ │ ├── VStack.md
│ │ │ ├── xmlui-animations
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── Animation.md
│ │ │ │ ├── FadeAnimation.md
│ │ │ │ ├── FadeInAnimation.md
│ │ │ │ ├── FadeOutAnimation.md
│ │ │ │ ├── ScaleAnimation.md
│ │ │ │ └── SlideInAnimation.md
│ │ │ ├── xmlui-pdf
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Pdf.md
│ │ │ ├── xmlui-spreadsheet
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Spreadsheet.md
│ │ │ └── xmlui-website-blocks
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── Carousel.md
│ │ │ ├── HelloMd.md
│ │ │ ├── HeroSection.md
│ │ │ └── ScrollToTop.md
│ │ └── extensions
│ │ ├── _meta.json
│ │ ├── xmlui-animations
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── Animation.md
│ │ │ ├── FadeAnimation.md
│ │ │ ├── FadeInAnimation.md
│ │ │ ├── FadeOutAnimation.md
│ │ │ ├── ScaleAnimation.md
│ │ │ └── SlideInAnimation.md
│ │ └── xmlui-website-blocks
│ │ ├── _meta.json
│ │ ├── _overview.md
│ │ ├── Carousel.md
│ │ ├── FancyButton.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
│ │ │ │ ├── debounce-user-input-for-api-calls.md
│ │ │ │ ├── debounce-with-changelistener.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/content/components/Link.md:
--------------------------------------------------------------------------------
```markdown
# Link [#link]
`Link` creates clickable navigation elements for internal app routes or external URLs. You can use the `label` and `icon` properties for simple text links, or embed custom components like buttons, cards, or complex layouts for rich interactive link presentations.
## Using Link [#using-link]
### `Link` Appearance [#link-appearance]
You can use the `label` and `icon` properties of a `Link` to set its text and icon to display. If you want a custom appearance, you can nest your visual representation into `Link`:
```xmlui-pg copy {3-6} display name="Example: custom Link content"
<App>
<Link to="https://docs.xmlui.org/" target="_blank">
<HStack verticalAlignment="center">
<Stack width="16px" height="16px" backgroundColor="purple" />
XMLUI introduction
</HStack>
</Link>
</App>
```
## Properties [#properties]
### `active` (default: false) [#active-default-false]
Indicates whether this link is active or not. If so, it will have a distinct visual appearance.
```xmlui-pg copy display name="Example: active" /active/
<App>
<Link>I'm an inactive link (by default)</Link>
<Link active="true">I'm an active link</Link>
<Link active="false">I'm an inactive link (explicit setting)</Link>
</App>
```
### `enabled` (default: true) [#enabled-default-true]
This boolean property value indicates whether the component responds to user events (`true`) or not (`false`).
```xmlui-pg copy display name="Example: enabled" /enabled/
<App>
<Link>I'm an enabled link (by default)</Link>
<Link enabled="false">I'm a disabled link</Link>
<Link enabled="true">I'm an enabled link (explicit setting)</Link>
</App>
```
### `icon` [#icon]
This property allows you to add an optional icon (specify the icon's name) to the link.
```xmlui-pg copy display name="Example: icon"
<App>
<Link icon="home" label="Home" />
<Link icon="drive">Drives</Link>
</App>
```
>[!INFO]
> If you want to specify paddings and gaps or put the icon to the right of the link text, use your custom link template (nest it into `Link`).
### `label` [#label]
This property sets the label of the component. If not set, the component will not display a label.
### `target` [#target]
This property specifies where to open the link represented by the `Link`. This property accepts the following values (in accordance with the HTML standard):
Available values:
| Value | Description |
| --- | --- |
| `_self` | The link will open in the same frame as it was clicked. |
| `_blank` | The link will open in a new window or tab. |
| `_parent` | The link will open in the parent frame. If no parent, behaves as _self. |
| `_top` | The topmost browsing context. The link will open in the full body of the window. If no ancestors, behaves as _self. |
| `_unfencedTop` | Allows embedded fenced frames to navigate the top-level frame, i.e. traversing beyond the root of the fenced frame. |
The following sample opens its link in a new tab:
```xmlui-pg copy display name="Example: target"
<App>
<Link to="https://docs.xmlui.org/" target="_blank">
Open XMLUI overview in a new tab
</Link>
</App>
```
### `to` [#to]
This property defines the URL of the link. If the value is not defined, the link cannot be activated.
## Events [#events]
### `click` [#click]
This event is triggered when the link is clicked.
## Exposed Methods [#exposed-methods]
This component does not expose any methods.
## Styling [#styling]
### Theme Variables [#theme-variables]
| Variable | Default Value (Light) | Default Value (Dark) |
| --- | --- | --- |
| [backgroundColor](../styles-and-themes/common-units/#color)-Link | *none* | *none* |
| [border](../styles-and-themes/common-units/#border)-Link | 0px solid $borderColor | 0px solid $borderColor |
| [borderBottom](../styles-and-themes/common-units/#border)-Link | *none* | *none* |
| [borderBottomColor](../styles-and-themes/common-units/#color)-Link | *none* | *none* |
| [borderBottomStyle](../styles-and-themes/common-units/#border-style)-Link | *none* | *none* |
| [borderBottomWidth](../styles-and-themes/common-units/#size)-Link | *none* | *none* |
| [borderColor](../styles-and-themes/common-units/#color)-Link | *none* | *none* |
| [borderEndEndRadius](../styles-and-themes/common-units/#border-rounding)-Link | *none* | *none* |
| [borderEndStartRadius](../styles-and-themes/common-units/#border-rounding)-Link | *none* | *none* |
| [borderHorizontal](../styles-and-themes/common-units/#border)-Link | *none* | *none* |
| [borderHorizontalColor](../styles-and-themes/common-units/#color)-Link | *none* | *none* |
| [borderHorizontalStyle](../styles-and-themes/common-units/#border-style)-Link | *none* | *none* |
| [borderHorizontalWidth](../styles-and-themes/common-units/#size)-Link | *none* | *none* |
| [borderLeft](../styles-and-themes/common-units/#border)-Link | *none* | *none* |
| [color](../styles-and-themes/common-units/#color)-Link | *none* | *none* |
| [borderLeftStyle](../styles-and-themes/common-units/#border-style)-Link | *none* | *none* |
| [borderLeftWidth](../styles-and-themes/common-units/#size)-Link | *none* | *none* |
| [borderRight](../styles-and-themes/common-units/#border)-Link | *none* | *none* |
| [color](../styles-and-themes/common-units/#color)-Link | *none* | *none* |
| [borderRightStyle](../styles-and-themes/common-units/#border-style)-Link | *none* | *none* |
| [borderRightWidth](../styles-and-themes/common-units/#size)-Link | *none* | *none* |
| [borderStartEndRadius](../styles-and-themes/common-units/#border-rounding)-Link | *none* | *none* |
| [borderStartStartRadius](../styles-and-themes/common-units/#border-rounding)-Link | *none* | *none* |
| [borderStyle](../styles-and-themes/common-units/#border-style)-Link | *none* | *none* |
| [borderTop](../styles-and-themes/common-units/#border)-Link | *none* | *none* |
| [borderTopColor](../styles-and-themes/common-units/#color)-Link | *none* | *none* |
| [borderTopStyle](../styles-and-themes/common-units/#border-style)-Link | *none* | *none* |
| [borderTopWidth](../styles-and-themes/common-units/#size)-Link | *none* | *none* |
| [borderHorizontal](../styles-and-themes/common-units/#border)-Link | *none* | *none* |
| [borderVerticalColor](../styles-and-themes/common-units/#color)-Link | *none* | *none* |
| [borderVerticalStyle](../styles-and-themes/common-units/#border-style)-Link | *none* | *none* |
| [borderVerticalWidth](../styles-and-themes/common-units/#size)-Link | *none* | *none* |
| [borderWidth](../styles-and-themes/common-units/#size)-Link | *none* | *none* |
| [direction](../styles-and-themes/layout-props#direction)-Link | *none* | *none* |
| [fontFamily](../styles-and-themes/common-units/#fontFamily)-Link | *none* | *none* |
| [fontSize](../styles-and-themes/common-units/#size)-Link | inherit | inherit |
| [fontStretch](../styles-and-themes/common-units/#fontStretch)-Link | *none* | *none* |
| [fontStyle](../styles-and-themes/common-units/#fontStyle)-Link | *none* | *none* |
| [fontVariant](../styles-and-themes/common-units/#font-variant)-Link | *none* | *none* |
| [fontWeight](../styles-and-themes/common-units/#fontWeight)-Link | *none* | *none* |
| [fontWeight](../styles-and-themes/common-units/#fontWeight)-Link--active | $fontWeight-bold | $fontWeight-bold |
| [gap](../styles-and-themes/common-units/#size)-icon-Link | $gap-tight | $gap-tight |
| [letterSpacing](../styles-and-themes/common-units/#size)-Link | *none* | *none* |
| [lineBreak](../styles-and-themes/common-units/#line-break)-Link | *none* | *none* |
| [lineHeight](../styles-and-themes/common-units/#size)-Link | *none* | *none* |
| [outlineColor](../styles-and-themes/common-units/#color)-Link--focus | $outlineColor--focus | $outlineColor--focus |
| [outlineOffset](../styles-and-themes/common-units/#size)-Link--focus | $outlineOffset--focus | $outlineOffset--focus |
| [outlineStyle](../styles-and-themes/common-units/#border)-Link--focus | $outlineStyle--focus | $outlineStyle--focus |
| [outlineWidth](../styles-and-themes/common-units/#size)-Link--focus | $outlineWidth--focus | $outlineWidth--focus |
| [padding](../styles-and-themes/common-units/#size)-icon-Link | $space-0_5 | $space-0_5 |
| [padding](../styles-and-themes/common-units/#size)-Link | *none* | *none* |
| [paddingBottom](../styles-and-themes/common-units/#size)-icon-Link | *none* | *none* |
| [paddingBottom](../styles-and-themes/common-units/#size)-Link | *none* | *none* |
| [paddingHorizontal](../styles-and-themes/common-units/#size)-icon-Link | *none* | *none* |
| [paddingHorizontal](../styles-and-themes/common-units/#size)-Link | *none* | *none* |
| [paddingLeft](../styles-and-themes/common-units/#size)-icon-Link | *none* | *none* |
| [paddingLeft](../styles-and-themes/common-units/#size)-Link | *none* | *none* |
| [paddingRight](../styles-and-themes/common-units/#size)-icon-Link | *none* | *none* |
| [paddingRight](../styles-and-themes/common-units/#size)-Link | *none* | *none* |
| [paddingTop](../styles-and-themes/common-units/#size)-icon-Link | *none* | *none* |
| [paddingTop](../styles-and-themes/common-units/#size)-Link | *none* | *none* |
| [paddingVertical](../styles-and-themes/common-units/#size)-icon-Link | *none* | *none* |
| [paddingVertical](../styles-and-themes/common-units/#size)-Link | *none* | *none* |
| [textAlign](../styles-and-themes/common-units/#text-align)-Link | *none* | *none* |
| [textAlignLast](../styles-and-themes/common-units/#text-align)-Link | *none* | *none* |
| [textColor](../styles-and-themes/common-units/#color)-Link | $color-primary-500 | $color-primary-600 |
| [textColor](../styles-and-themes/common-units/#color)-Link--active | $color-primary-400 | $color-primary-500 |
| [textColor](../styles-and-themes/common-units/#color)-Link--hover | $color-primary-400 | $color-primary-500 |
| [textColor](../styles-and-themes/common-units/#color)-Link--hover--active | $textColor-Link--active | $textColor-Link--active |
| [textDecorationColor](../styles-and-themes/common-units/#color)-Link | textDecorationColor-Link | textDecorationColor-Link |
| [textDecorationColor](../styles-and-themes/common-units/#color)-Link--active | textColor-Link--active | textColor-Link--active |
| [textDecorationColor](../styles-and-themes/common-units/#color)-Link--hover | textColor-Link--hover | textColor-Link--hover |
| [textDecorationLine](../styles-and-themes/common-units/#textDecoration)-Link | underline | underline |
| [textDecorationStyle](../styles-and-themes/common-units/#textDecoration)-Link | solid | solid |
| [textDecorationThickness](../styles-and-themes/common-units/#textDecoration)-Link | *none* | *none* |
| [textIndent](../styles-and-themes/common-units/#text-indent)-Link | *none* | *none* |
| [textShadow](../styles-and-themes/common-units/#text-shadow)-Link | *none* | *none* |
| [textTransform](../styles-and-themes/common-units/#textTransform)-Link | *none* | *none* |
| [textUnderlineOffset](../styles-and-themes/common-units/#size)-Link | $space-1 | $space-1 |
| [wordBreak](../styles-and-themes/common-units/#word-break)-Link | *none* | *none* |
| [wordSpacing](../styles-and-themes/common-units/#word-spacing)-Link | *none* | *none* |
| [wordWrap](../styles-and-themes/common-units/#word-wrap)-Link | *none* | *none* |
| [writingMode](../styles-and-themes/common-units/#writing-mode)-Link | *none* | *none* |
### Variable Explanations [#variable-explanations]
| Theme Variable | Description |
| --- | --- |
| **`gap-icon-Link`** | This property defines the size of the gap between the icon and the label. |
```
--------------------------------------------------------------------------------
/xmlui/src/components/NavGroup/NavGroup.spec.ts:
--------------------------------------------------------------------------------
```typescript
import { expect, test } from "../../testing/fixtures";
test.describe("smoke tests", { tag: "@smoke" }, () => {
test("displays menuitems after click", async ({ initTestBed, page }) => {
await initTestBed(`
<NavGroup label="Pages">
<NavLink label="Page 1" />
<NavGroup label="subpages">
<NavLink label="inner page 2" />
<NavLink label="inner page 3" />
</NavGroup>
<NavLink label="Page 4" />
</NavGroup>
`);
await page.getByRole("button", { name: "Pages", exact: true }).click();
await expect(page.getByRole("menuitem")).toHaveCount(3);
await expect(page.getByRole("menuitem", { name: "Page 1" })).toBeVisible();
await expect(page.getByRole("menuitem", { name: "subpages" })).toBeVisible();
await expect(page.getByRole("menuitem", { name: "Page 4" })).toBeVisible();
await expect(page.getByRole("menuitem", { name: "inner page 2" })).not.toBeVisible();
await expect(page.getByRole("menuitem", { name: "inner page 3" })).not.toBeVisible();
});
test("disabled navgroup can't open", async ({ initTestBed, page }) => {
await initTestBed(`
<NavGroup label="Pages" enabled="false">
<NavLink label="Page 1" />
<NavGroup label="subpages">
<NavLink label="inner page 2" />
<NavLink label="inner page 3" />
</NavGroup>
<NavLink label="Page 4" />
</NavGroup>
`);
const pagesBtn = page.getByRole("button", { name: "Pages", exact: true });
await expect(pagesBtn).toBeDisabled();
await pagesBtn.click({ force: true });
await expect(page.getByRole("menuitem", { name: "Page 1" })).not.toBeVisible();
await expect(page.getByRole("menuitem", { name: "inner page 2" })).not.toBeVisible();
});
test("component trigger has correct aria labels", async ({ initTestBed, page }) => {
await initTestBed(`<NavGroup testId="navGroup" label="NavGroup"/>`);
const button = page.getByTestId("navGroup");
await expect(button).toBeVisible();
await expect(button).toHaveAttribute("aria-expanded", "false");
await button.click();
await expect(button).toHaveAttribute("aria-expanded", "true");
});
test("expanded in vertical layout to show link of current page", async ({
initTestBed,
page,
}) => {
await initTestBed(`
<App layout="vertical">
<NavPanel>
<NavGroup label="Current-upper">
<NavGroup label="Current">
<NavLink label="link-to-current-page" to="/" />
</NavGroup>
</NavGroup>
</NavPanel>
</App>`);
await expect(page.getByText("link-to-current-page")).toBeVisible();
});
});
test("nested disabled navgroup can't open", async ({ initTestBed, page }) => {
await initTestBed(`
<NavGroup label="Pages" >
<NavLink label="Page 1" />
<NavGroup label="subpages" enabled="false">
<NavLink label="inner page 2" />
<NavLink label="inner page 3" />
</NavGroup>
<NavLink label="Page 4" />
</NavGroup>
`);
const pagesBtn = page.getByRole("button", { name: "Pages", exact: true });
await pagesBtn.click();
await expect(page.getByRole("menuitem", { name: "Page 1" })).toBeVisible();
const subpagesBtn = page.getByRole("menuitem", { name: "subpages" });
await expect(subpagesBtn).toBeDisabled();
await subpagesBtn.click({ force: true });
await expect(page.getByRole("menuitem", { name: "inner page 2" })).not.toBeVisible();
});
test("initiallyExpanded works", async ({ initTestBed, page }) => {
await initTestBed(`
<NavGroup label="Pages" initiallyExpanded="true">
<NavLink label="Page 1" />
<NavGroup label="subpages">
<NavLink label="inner page 2" />
<NavLink label="inner page 3" />
</NavGroup>
<NavLink label="Page 4" />
</NavGroup>
`);
await expect(page.getByRole("menuitem", { name: "Page 1" })).toBeVisible();
await expect(page.getByRole("menuitem", { name: "inner page 2" })).not.toBeVisible();
});
test("nested initiallyExpanded works", async ({ initTestBed, page }) => {
await initTestBed(`
<Stack testId="stack">
<NavGroup label="Pages" initiallyExpanded="true">
<NavLink label="Page 1" />
<NavGroup label="subpages" initiallyExpanded="true">
<NavLink label="inner page 2" />
<NavLink label="inner page 3" />
</NavGroup>
<NavLink label="Page 4" />
</NavGroup>
</Stack>
`);
const stack = page.getByTestId("stack");
await expect(stack).toBeVisible();
const items = page.getByRole("menuitem");
await expect(items).toHaveCount(3);
expect(items.nth(0)).toHaveText("Page 1");
expect(items.nth(1)).toHaveText("subpages");
expect(items.nth(2)).toHaveText("Page 4");
});
test("expands even without label", async ({ initTestBed, page }) => {
await initTestBed(`
<NavGroup >
<NavLink label="Page 1" />
<NavGroup label="subpages">
<NavLink label="inner page 2" />
<NavLink label="inner page 3" />
</NavGroup>
<NavLink label="Page 4" />
</NavGroup>
`);
await expect(page.getByRole("menuitem", { name: "Page 1" })).not.toBeVisible();
await page.getByRole("button").click();
await expect(page.getByRole("menuitem", { name: "Page 1" })).toBeVisible();
});
test.describe("icon props", () => {
test("icon appears", async ({ initTestBed, page }) => {
const { testIcons } = await initTestBed(
`<App layout="vertical">
<NavPanel>
<NavGroup icon="bell" label="NavGroup">
<NavLink label="link" to="/" />
</NavGroup>
</NavPanel>
</App>`,
);
await expect(testIcons.bellIcon).toBeVisible();
});
test("iconHorizontal shows in horizontal layout submenu", async ({ initTestBed, page }) => {
const { testIcons } = await initTestBed(
`
<App layout="horizontal">
<NavPanel>
<NavGroup label="Send To">
<NavGroup icon="users" label="Team"
iconHorizontalExpanded="bell" iconHorizontalCollapsed="eye">
<NavLink label="Jane" />
</NavGroup>
</NavGroup>
</NavPanel>
</App>
`,
);
const bell = testIcons.bellIcon;
const eye = testIcons.eyeIcon;
await expect(bell).not.toBeVisible();
await expect(eye).not.toBeVisible();
await page.getByRole("button", { name: "Send to" }).click();
await expect(bell).not.toBeVisible();
await expect(eye).toBeVisible();
await page.getByRole("menuitem", { name: "Team" }).hover();
await expect(bell).toBeVisible();
await expect(eye).not.toBeVisible();
});
test("iconVertical shows in horizontal layout top lvl navgroup", async ({
initTestBed,
page,
}) => {
const { testIcons } = await initTestBed(
`
<App layout="horizontal">
<NavPanel>
<NavGroup icon="users" label="Team"
iconVerticalExpanded="bell" iconVerticalCollapsed="eye">
<NavLink label="Jane" />
</NavGroup>
</NavPanel>
</App>
`,
);
const bell = testIcons.bellIcon;
const eye = testIcons.eyeIcon;
await expect(bell).not.toBeVisible();
await expect(eye).toBeVisible();
await page.getByText("Team").click();
await expect(bell).toBeVisible();
await expect(eye).not.toBeVisible();
});
test("iconVertical shows in vertical layout submenu", async ({ initTestBed, page }) => {
const { testIcons } = await initTestBed(
`
<App layout="vertical">
<NavPanel>
<NavGroup label="Send To">
<NavGroup icon="users" label="Team"
iconVerticalExpanded="bell" iconVerticalCollapsed="eye">
<NavLink label="Jane" />
</NavGroup>
</NavGroup>
</NavPanel>
</App>
`,
);
const bell = testIcons.bellIcon;
const eye = testIcons.eyeIcon;
await expect(bell).not.toBeVisible();
await page.getByText("Send to").click();
await expect(bell).not.toBeVisible();
await expect(eye).toBeVisible();
await page.getByText("Team").click();
await expect(bell).toBeVisible();
await expect(eye).not.toBeVisible();
});
});
// =============================================================================
// DRAWER INTERACTION TESTS
// =============================================================================
test.describe("Drawer Interaction", () => {
test("clicking NavGroup toggle in drawer does not close drawer", async ({
initTestBed,
page,
}) => {
// Set small viewport to trigger drawer mode
await page.setViewportSize({ width: 400, height: 600 });
await initTestBed(`
<App layout="condensed">
<AppHeader testId="appHeader"/>
<NavPanel>
<NavGroup label="Pages">
<NavLink label="Page 1" to="/page1"/>
<NavLink label="Page 2" to="/page2"/>
</NavGroup>
</NavPanel>
<Pages fallbackPath="/">
<Page url="/">
<Text value="Home" />
</Page>
<Page url="/page1">
<Text value="Page 1" />
</Page>
<Page url="/page2">
<Text value="Page 2" />
</Page>
</Pages>
</App>
`);
// Open drawer by clicking hamburger button
const appHeader = page.getByTestId("appHeader");
const hamburgerButton = appHeader.locator('[data-part-id="hamburger"]').first();
await hamburgerButton.click();
const dialog = page.getByRole("dialog");
await expect(dialog).toBeVisible();
// finst the first element in the dialog with a text of "Pages"
const navGroupToggle = dialog.getByRole("button", { name: "Pages" });
await navGroupToggle.click();
await page.waitForTimeout(200);
await expect(dialog).toBeVisible();
// There must be a text "Page1"
await expect(dialog).toContainText("Page 1");
await expect(dialog).toContainText("Page 2");
});
test("clicking NavLink in drawer closes drawer", async ({ initTestBed, page }) => {
// Set small viewport to trigger drawer mode
await page.setViewportSize({ width: 400, height: 600 });
await initTestBed(`
<App layout="condensed">
<AppHeader />
<NavPanel>
<NavGroup label="Pages">
<NavLink label="Page 1" to="/page1"/>
<NavLink label="Page 2" to="/page2"/>
</NavGroup>
</NavPanel>
<Pages fallbackPath="/">
<Page url="/">
<Text value="Home" />
</Page>
<Page url="/page1">
<Text value="Page 1 Content" />
</Page>
<Page url="/page2">
<Text value="Page 2" />
</Page>
</Pages>
</App>
`);
// Open drawer
const hamburgerButton = page.locator('[data-part-id="hamburger"]');
await hamburgerButton.click();
const dialog = page.getByRole("dialog");
await expect(dialog).toBeVisible();
// Expand NavGroup
const navGroupToggle = dialog.getByRole("button", { name: "Pages" });
await navGroupToggle.click();
await page.waitForTimeout(200);
// Click a NavLink to navigate
await dialog.getByRole("link", { name: "Page 1" }).click();
// Verify navigation occurred
await expect(page.getByText("Page 1 Content")).toBeVisible();
// Verify drawer is closed
await expect(dialog).not.toBeVisible();
});
});
```
--------------------------------------------------------------------------------
/tools/vscode/syntaxes/xmlui.tmLanguage.json:
--------------------------------------------------------------------------------
```json
{
"name": "xmlui",
"scopeName": "source.xmlui",
"patterns": [{ "include": "#root" }],
"repository": {
"root": {
"patterns": [
{ "include": "#comments" },
{ "include": "#helperTag" },
{ "include": "#componentTag" },
{ "include": "#entity" },
{ "include": "#textWithBindingExpr" },
{
"begin": "(<!\\[)(CDATA)(\\[)",
"beginCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "storage.xmlui" },
"3": { "name": "punctuation.definition.tag.xmlui" }
},
"end": "]]>",
"endCaptures": {
"0": { "name": "punctuation.definition.tag.xmlui" }
},
"contentName": "string.unquoted.cdata.xmlui"
}
]
},
"methodTag": {
"begin": "(<)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)(method)",
"beginCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.tag.localname.xmlui" }
},
"endCaptures": {
"0": { "name": "punctuation.definition.tag.xmlui" }
},
"applyEndPatternLast": "1",
"patterns": [
{ "include": "#comments" },
{ "include": "#valueAttributeScriptInside" },
{ "include": "#attribute" },
{
"begin": "(?<!/|(?:/\\s*(?:method))\\s*)>",
"beginCaptures": {
"0": {
"name": "punctuation.definition.tag.xmlui"
}
},
"endCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.tag.localname.xmlui" }
},
"contentName": "meta.embedded.block.javascript",
"patterns": [{ "include": "source.js" }],
"end": "(</)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)(method)(?=\\s*>)"
}
],
"end": "/?>"
},
"eventTag": {
"begin": "(<)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)(event)",
"beginCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.tag.localname.xmlui" }
},
"endCaptures": {
"0": { "name": "punctuation.definition.tag.xmlui" }
},
"applyEndPatternLast": "1",
"patterns": [
{ "include": "#comments" },
{ "include": "#valueAttributeScriptInside" },
{ "include": "#attribute" },
{
"begin": "(?<!/|(?:/\\s*event)\\s*)(>)",
"beginCaptures": {
"0": {
"name": "punctuation.definition.tag.xmlui"
}
},
"endCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.tag.localname.xmlui" }
},
"patterns": [
{ "include": "#comments" },
{ "include": "#componentTag" },
{ "include": "source.js" }
],
"end": "(</)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)(event)(?=\\s*>)"
}
],
"end": ">"
},
"fieldTag": {
"begin": "(<)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)(field)",
"beginCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.tag.localname.xmlui" }
},
"endCaptures": {
"0": { "name": "punctuation.definition.tag.xmlui" }
},
"applyEndPatternLast": "1",
"patterns": [
{ "include": "#comments" },
{ "include": "#valueAttributeScriptInside" },
{ "include": "#attribute" },
{
"begin": "(?<!/|(?:/\\s*field)\\s*)(>)",
"beginCaptures": {
"0": {
"name": "punctuation.definition.tag.xmlui"
}
},
"endCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.tag.localname.xmlui" }
},
"patterns": [
{ "include": "#comments" },
{ "include": "#componentTag" },
{ "include": "source.js" }
],
"end": "(</)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)(field)(?=\\s*>)"
}
],
"end": ">"
},
"itemTag": {
"begin": "(<)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)(item)",
"beginCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.tag.localname.xmlui" }
},
"endCaptures": {
"0": { "name": "punctuation.definition.tag.xmlui" }
},
"applyEndPatternLast": "1",
"patterns": [
{ "include": "#comments" },
{ "include": "#valueAttributeScriptInside" },
{ "include": "#attribute" },
{
"begin": "(?<!/|(?:/\\s*item)\\s*)(>)",
"beginCaptures": {
"0": {
"name": "punctuation.definition.tag.xmlui"
}
},
"endCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.tag.localname.xmlui" }
},
"patterns": [
{ "include": "#comments" },
{ "include": "#componentTag" },
{ "include": "source.js" }
],
"end": "(</)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)(item)(?=\\s*>)"
}
],
"end": ">"
},
"bindingExpr": {
"contentName": "meta.embedded.block.javascript",
"begin": "\\{",
"end": "\\}",
"beginCaptures": {
"0": { "name": "entity.name.function.xmlui punctuation.definition.block.xmlui" }
},
"endCaptures": {
"0": { "name": "entity.name.function.xmlui punctuation.definition.block.xmlui" }
},
"patterns": [{ "include": "source.js" }]
},
"helperTag": {
"patterns": [
{ "include": "#scriptTag" },
{ "include": "#eventTag" },
{ "include": "#fieldTag" },
{ "include": "#itemTag" },
{ "include": "#methodTag" },
{ "include": "#propOrVarTag" }
]
},
"valueAttributeScriptInside": {
"patterns": [
{
"captures": {
"1": { "name": "entity.other.attribute-name.localname.xmlui" },
"2": { "name": "keyword.operator.xmlui" }
},
"match": "(?:^|\\s+)(value)(\\s*=)"
},
{ "include": "#quotedStringJsInside" }
]
},
"scriptTag": {
"begin": "(\\s*<)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)(script)(\\s*>)",
"end": "(\\s*</)(\\2)(\\3)(\\s*>)",
"contentName": "meta.embedded.block.javascript",
"beginCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.function.xmlui" },
"4": { "name": "punctuation.definition.tag.xmlui" }
},
"endCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.function.xmlui" },
"4": { "name": "punctuation.definition.tag.xmlui" }
},
"patterns": [{ "include": "source.js" }]
},
"textWithBindingExpr": {
"patterns": [{ "include": "#entity" }, { "match": "\\\\{" }, { "include": "#bindingExpr" }]
},
"propOrVarTag": {
"begin": "(</?)([a-zA-Z_][\\w\\.\\-]*?:)?((?:variable)|(?:property)|(?:prop))",
"beginCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.tag.localname.xmlui" }
},
"end": "(/?>)",
"endCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" }
},
"patterns": [{ "include": "#attribute" }, { "include": "#comments" }]
},
"componentTag": {
"begin": "(\\s*</?)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)([a-zA-Z][\\w\\.\\-]*)",
"beginCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "support.class.tag.component.xmlui" }
},
"end": "(/?>)",
"endCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" }
},
"patterns": [
{ "include": "#comments" },
{ "include": "#eventHandler" },
{ "include": "#attribute" }
]
},
"quotedStringJsInside": {
"begin": "\"|'|`",
"beginCaptures": {
"0": { "name": "string.xmlui" }
},
"end": "\\0",
"endCaptures": {
"0": { "name": "string.xmlui" }
},
"contentName": "meta.embedded.block.javascript",
"patterns": [{ "include": "source.js" }]
},
"entity": {
"captures": {
"1": { "name": "punctuation.definition.constant.xmlui" },
"2": { "name": "punctuation.definition.constant.xmlui" },
"3": { "name": "punctuation.definition.constant.xmlui" }
},
"match": "(&)((?:amp)|(?:lt)|(?:gt)|(?:quot)|(?:apos))(;)"
},
"eventHandler": {
"patterns": [
{
"captures": {
"1": { "name": "entity.other.attribute-name.localname.xmlui" },
"2": { "name": "keyword.operator.xmlui" }
},
"match": "(?:^|\\s+)(on[A-Z][-\\w.:$]*)(\\s*=)"
},
{ "include": "#quotedStringJsInside" }
]
},
"attribute": {
"patterns": [
{
"begin": "(?:^|\\s+)((?:[a-zA-Z$_][-\\w.$]*:)?)([a-zA-Z$_][-\\w.$]*)(\\s*=\\s*)(['\"`])",
"end": "\\4",
"beginCaptures": {
"1": { "name": "keyword.operator.namespace.xmlui" },
"2": { "name": "entity.other.attribute-name.localname.xmlui" },
"3": { "name": "keyword.operator.xmlui" },
"4": { "name": "string.xmlui" }
},
"endCaptures": {
"0": { "name": "string.xmlui" }
},
"contentName": "string.xmlui",
"patterns": [{ "include": "#textWithBindingExpr" }]
},
{
"match": "(?:^|\\s+)((?:[a-zA-Z$_][-\\w.$]*:)?)([a-zA-Z$_][-\\w.$]*)(\\s*=\\s*)([a-zA-Z$_][-\\w.$]*)",
"captures": {
"1": { "name": "keyword.operator.namespace.xmlui" },
"2": { "name": "entity.other.attribute-name.localname.xmlui" },
"3": { "name": "keyword.operator.xmlui" },
"4": { "name": "string.xmlui" }
}
},
{
"match": "(?:^|\\s+)((?:[a-zA-Z$_][-\\w.$]*:)?)([a-zA-Z$_][-\\w.$]*)",
"name": "entity.other.attribute-name.localname.xmlui",
"captures": {
"1": { "name": "keyword.operator.namespace.xmlui" },
"2": { "name": "entity.other.attribute-name.localname.xmlui" }
}
}
]
},
"comments": {
"patterns": [
{
"begin": "<!--",
"captures": {
"0": {
"name": "punctuation.definition.comment.xmlui"
}
},
"end": "-->",
"name": "comment.block.xmlui"
}
]
}
}
}
```
--------------------------------------------------------------------------------
/xmlui/src/syntax/textMate/xmlui.tmLanguage.json:
--------------------------------------------------------------------------------
```json
{
"name": "xmlui",
"scopeName": "source.xmlui",
"patterns": [{ "include": "#root" }],
"repository": {
"root": {
"patterns": [
{ "include": "#comments" },
{ "include": "#helperTag" },
{ "include": "#componentTag" },
{ "include": "#entity" },
{ "include": "#textWithBindingExpr" },
{
"begin": "(<!\\[)(CDATA)(\\[)",
"beginCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "storage.xmlui" },
"3": { "name": "punctuation.definition.tag.xmlui" }
},
"end": "]]>",
"endCaptures": {
"0": { "name": "punctuation.definition.tag.xmlui" }
},
"contentName": "string.unquoted.cdata.xmlui"
}
]
},
"methodTag": {
"begin": "(<)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)(method)",
"beginCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.tag.localname.xmlui" }
},
"endCaptures": {
"0": { "name": "punctuation.definition.tag.xmlui" }
},
"applyEndPatternLast": "1",
"patterns": [
{ "include": "#comments" },
{ "include": "#valueAttributeScriptInside" },
{ "include": "#attribute" },
{
"begin": "(?<!/|(?:/\\s*(?:method))\\s*)>",
"beginCaptures": {
"0": {
"name": "punctuation.definition.tag.xmlui"
}
},
"endCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.tag.localname.xmlui" }
},
"contentName": "meta.embedded.block.javascript",
"patterns": [{ "include": "source.js" }],
"end": "(</)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)(method)(?=\\s*>)"
}
],
"end": "/?>"
},
"eventTag": {
"begin": "(<)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)(event)",
"beginCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.tag.localname.xmlui" }
},
"endCaptures": {
"0": { "name": "punctuation.definition.tag.xmlui" }
},
"applyEndPatternLast": "1",
"patterns": [
{ "include": "#comments" },
{ "include": "#valueAttributeScriptInside" },
{ "include": "#attribute" },
{
"begin": "(?<!/|(?:/\\s*event)\\s*)(>)",
"beginCaptures": {
"0": {
"name": "punctuation.definition.tag.xmlui"
}
},
"endCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.tag.localname.xmlui" }
},
"patterns": [
{ "include": "#comments" },
{ "include": "#componentTag" },
{ "include": "source.js" }
],
"end": "(</)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)(event)(?=\\s*>)"
}
],
"end": ">"
},
"fieldTag": {
"begin": "(<)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)(field)",
"beginCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.tag.localname.xmlui" }
},
"endCaptures": {
"0": { "name": "punctuation.definition.tag.xmlui" }
},
"applyEndPatternLast": "1",
"patterns": [
{ "include": "#comments" },
{ "include": "#valueAttributeScriptInside" },
{ "include": "#attribute" },
{
"begin": "(?<!/|(?:/\\s*field)\\s*)(>)",
"beginCaptures": {
"0": {
"name": "punctuation.definition.tag.xmlui"
}
},
"endCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.tag.localname.xmlui" }
},
"patterns": [
{ "include": "#comments" },
{ "include": "#componentTag" },
{ "include": "source.js" }
],
"end": "(</)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)(field)(?=\\s*>)"
}
],
"end": ">"
},
"itemTag": {
"begin": "(<)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)(item)",
"beginCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.tag.localname.xmlui" }
},
"endCaptures": {
"0": { "name": "punctuation.definition.tag.xmlui" }
},
"applyEndPatternLast": "1",
"patterns": [
{ "include": "#comments" },
{ "include": "#valueAttributeScriptInside" },
{ "include": "#attribute" },
{
"begin": "(?<!/|(?:/\\s*item)\\s*)(>)",
"beginCaptures": {
"0": {
"name": "punctuation.definition.tag.xmlui"
}
},
"endCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.tag.localname.xmlui" }
},
"patterns": [
{ "include": "#comments" },
{ "include": "#componentTag" },
{ "include": "source.js" }
],
"end": "(</)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)(item)(?=\\s*>)"
}
],
"end": ">"
},
"bindingExpr": {
"contentName": "meta.embedded.block.javascript",
"begin": "\\{",
"end": "\\}",
"beginCaptures": {
"0": { "name": "entity.name.function.xmlui punctuation.definition.block.xmlui" }
},
"endCaptures": {
"0": { "name": "entity.name.function.xmlui punctuation.definition.block.xmlui" }
},
"patterns": [{ "include": "source.js" }]
},
"helperTag": {
"patterns": [
{ "include": "#scriptTag" },
{ "include": "#eventTag" },
{ "include": "#fieldTag" },
{ "include": "#itemTag" },
{ "include": "#methodTag" },
{ "include": "#propOrVarTag" }
]
},
"valueAttributeScriptInside": {
"patterns": [
{
"captures": {
"1": { "name": "entity.other.attribute-name.localname.xmlui" },
"2": { "name": "keyword.operator.xmlui" }
},
"match": "(?:^|\\s+)(value)(\\s*=)"
},
{ "include": "#quotedStringJsInside" }
]
},
"scriptTag": {
"begin": "(\\s*<)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)(script)(\\s*>)",
"end": "(\\s*</)(\\2)(\\3)(\\s*>)",
"contentName": "meta.embedded.block.javascript",
"beginCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.function.xmlui" },
"4": { "name": "punctuation.definition.tag.xmlui" }
},
"endCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.function.xmlui" },
"4": { "name": "punctuation.definition.tag.xmlui" }
},
"patterns": [{ "include": "source.js" }]
},
"textWithBindingExpr": {
"patterns": [{ "include": "#entity" }, { "match": "\\\\{" }, { "include": "#bindingExpr" }]
},
"propOrVarTag": {
"begin": "(</?)([a-zA-Z_][\\w\\.\\-]*?:)?((?:variable)|(?:property)|(?:prop))",
"beginCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.tag.localname.xmlui" }
},
"end": "(/?>)",
"endCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" }
},
"patterns": [{ "include": "#attribute" }, { "include": "#comments" }]
},
"componentTag": {
"begin": "(\\s*</?)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)([a-zA-Z][\\w\\.\\-]*)",
"beginCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "support.class.tag.component.xmlui" }
},
"end": "(/?>)",
"endCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" }
},
"patterns": [
{ "include": "#comments" },
{ "include": "#eventHandler" },
{ "include": "#attribute" }
]
},
"quotedStringJsInside": {
"begin": "\"|'|`",
"beginCaptures": {
"0": { "name": "string.xmlui" }
},
"end": "\\0",
"endCaptures": {
"0": { "name": "string.xmlui" }
},
"contentName": "meta.embedded.block.javascript",
"patterns": [{ "include": "source.js" }]
},
"entity": {
"captures": {
"1": { "name": "punctuation.definition.constant.xmlui" },
"2": { "name": "punctuation.definition.constant.xmlui" },
"3": { "name": "punctuation.definition.constant.xmlui" }
},
"match": "(&)((?:amp)|(?:lt)|(?:gt)|(?:quot)|(?:apos))(;)"
},
"eventHandler": {
"patterns": [
{
"captures": {
"1": { "name": "entity.other.attribute-name.localname.xmlui" },
"2": { "name": "keyword.operator.xmlui" }
},
"match": "(?:^|\\s+)(on[A-Z][-\\w.:$]*)(\\s*=)"
},
{ "include": "#quotedStringJsInside" }
]
},
"attribute": {
"patterns": [
{
"begin": "(?:^|\\s+)((?:[a-zA-Z$_][-\\w.$]*:)?)([a-zA-Z$_][-\\w.$]*)(\\s*=\\s*)(['\"`])",
"end": "\\4",
"beginCaptures": {
"1": { "name": "keyword.operator.namespace.xmlui" },
"2": { "name": "entity.other.attribute-name.localname.xmlui" },
"3": { "name": "keyword.operator.xmlui" },
"4": { "name": "string.xmlui" }
},
"endCaptures": {
"0": { "name": "string.xmlui" }
},
"contentName": "string.xmlui",
"patterns": [{ "include": "#textWithBindingExpr" }]
},
{
"match": "(?:^|\\s+)((?:[a-zA-Z$_][-\\w.$]*:)?)([a-zA-Z$_][-\\w.$]*)(\\s*=\\s*)([a-zA-Z$_][-\\w.$]*)",
"captures": {
"1": { "name": "keyword.operator.namespace.xmlui" },
"2": { "name": "entity.other.attribute-name.localname.xmlui" },
"3": { "name": "keyword.operator.xmlui" },
"4": { "name": "string.xmlui" }
}
},
{
"match": "(?:^|\\s+)((?:[a-zA-Z$_][-\\w.$]*:)?)([a-zA-Z$_][-\\w.$]*)",
"name": "entity.other.attribute-name.localname.xmlui",
"captures": {
"1": { "name": "keyword.operator.namespace.xmlui" },
"2": { "name": "entity.other.attribute-name.localname.xmlui" }
}
}
]
},
"comments": {
"patterns": [
{
"begin": "<!--",
"captures": {
"0": {
"name": "punctuation.definition.comment.xmlui"
}
},
"end": "-->",
"name": "comment.block.xmlui"
}
]
}
}
}
```
--------------------------------------------------------------------------------
/docs/content/components/Form.md:
--------------------------------------------------------------------------------
```markdown
# Form [#form]
`Form` provides a structured container for collecting and validating user input, with built-in data binding, validation, and submission handling. It automatically manages form state and provides context for nested form controls to work together.
**Key features:**
- **Automatic data binding**: Form controls automatically sync with form data using `bindTo` properties
- **Built-in validation**: Validates individual fields and overall form state before submission
- **Context sharing**: Provides `$data` and other context values accessible to all nested components
- **Submission handling**: Manages form submission workflow and prevents invalid submissions
See [this guide](/forms) for details.
**Context variables available during execution:**
- `$data`: This property represents the value of the form data. You can access the fields of the form using the IDs in the `bindTo` property of nested `FormItem` instances. `$data` also provides an `update` method as a shortcut to the Form's exposed `update` method.
## Properties [#properties]
### `buttonRowTemplate` [#buttonrowtemplate]
This property allows defining a custom component to display the buttons at the bottom of the form.
The following example demonstrates using it:
```xmlui-pg copy display name="Example: buttonRowTemplate"
---app copy display {10-19}
<App>
<Form id="searchForm" padding="0.5rem"
data="{{ search: 'Seattle', caseSensitive: false }}"
onSubmit="() => {isSearching = true; delay(1000); isSearching = false; }"
saveLabel="Search"
var.isSearching="{false}">
<Text>Please specify the name to include in the search:</Text>
<FormItem bindTo="search" width="280px" />
<FormItem type="checkbox" label="Case sensitive?" bindTo="caseSensitive" />
<property name="buttonRowTemplate">
<HStack gap="0.5rem" borderTop="1px solid #ddd" paddingVertical="1rem">
<Button label="Test Search Server" type="button"
themeColor="secondary" variant="outlined"
onClick="toast('Search server is ok.')"/>
<SpaceFiller/>
<Button type="submit" enabled="{!isSearching}" icon="search"
label="{isSearching ? 'Searching...' : 'Search'}"/>
</HStack>
</property>
</Form>
</App>
---desc
This example mimics a one-second search and turns off the submit button during the operation. Also, it adds a Test Search Server button:
```
### `cancelLabel` (default: "Cancel") [#cancellabel-default-cancel]
This property defines the label of the Cancel button.
### `completedNotificationMessage` [#completednotificationmessage]
This property sets the message to display when the form is submitted successfully.
### `data` [#data]
This property sets the initial value of the form's data structure. The form infrastructure uses this value to set the initial state of form items within the form. If this property isnot set, the form does not have an initial value.
### `enabled` (default: true) [#enabled-default-true]
This boolean property value indicates whether the component responds to user events (`true`) or not (`false`).
### `enableSubmit` (default: true) [#enablesubmit-default-true]
This property controls whether the submit button is enabled. When set to false, the submit button is disabled and the form cannot be submitted.
### `errorNotificationMessage` [#errornotificationmessage]
This property sets the message to display when the form submission fails.
### `hideButtonRow` (default: false) [#hidebuttonrow-default-false]
This property hides the button row entirely when set to true.
### `hideButtonRowUntilDirty` (default: false) [#hidebuttonrowuntildirty-default-false]
This property hides the button row until the form data is modified (dirty).
### `inProgressNotificationMessage` [#inprogressnotificationmessage]
This property sets the message to display when the form is being submitted.
### `itemLabelBreak` (default: true) [#itemlabelbreak-default-true]
This boolean value indicates if form item labels can be split into multiple lines if it would overflow the available label width. Individual `FormItem` instances can override this property.
### `itemLabelPosition` (default: "top") [#itemlabelposition-default-top]
This property sets the position of the item labels within the form.Individual `FormItem` instances can override this property.
Available values:
| Value | Description |
| --- | --- |
| `start` | The left side of the input (left-to-right) or the right side of the input (right-to-left) |
| `end` | The right side of the input (left-to-right) or the left side of the input (right-to-left) |
| `top` | The top of the input **(default)** |
| `bottom` | The bottom of the input |
### `itemLabelWidth` [#itemlabelwidth]
This property sets the width of the item labels within the form. Individual `FormItem` instances can override this property. If this property is not set, each form item nested in the form uses its calculated label width. These widths may be different for each item.
### `keepModalOpenOnSubmit` (default: false) [#keepmodalopenonsubmit-default-false]
This property prevents the modal from closing when the form is submitted.
### `saveInProgressLabel` (default: "Saving...") [#saveinprogresslabel-default-saving-]
This property defines the label of the Save button to display during the form data submit (save) operation.
### `saveLabel` (default: "Save") [#savelabel-default-save]
This property defines the label of the Save button.
### `submitMethod` [#submitmethod]
This property sets the HTTP method to use when submitting the form data. If not defined, `put` is used when the form has initial data; otherwise, `post`.
### `submitUrl` [#submiturl]
URL to submit the form data.
### `swapCancelAndSave` (default: false) [#swapcancelandsave-default-false]
By default, the Cancel button is to the left of the Save button. Set this property to `true` to swap them or `false` to keep their original location.
## Events [#events]
### `cancel` [#cancel]
The form infrastructure fires this event when the form is canceled.
### `reset` [#reset]
The form infrastructure fires this event when the form is reset.
### `submit` [#submit]
The form infrastructure fires this event when the form is submitted. The event argument is the current `data` value to save.
```xmlui-pg copy {4} display name="Example: submit"
<App>
<Form padding="0.5rem"
data="{{ name: 'Joe', age: 43 }}"
onSubmit="(toSave) => toast(JSON.stringify(toSave))">
<FlowLayout columnGap="12px" paddingBottom="6px">
<FormItem bindTo="name" label="Customer name" width="50%" />
<FormItem bindTo="age" label="Age" type="integer" width="50%"
zeroOrPositive="true" />
</FlowLayout>
</Form>
</App>
```
### `success` [#success]
The form infrastructure fires this event when the form is submitted successfully.
### `willSubmit` [#willsubmit]
The form infrastructure fires this event just before the form is submitted. The event argument is the current `data` value to be submitted. You can cancel the submission by returning `false` from the event handler.
The following example allows saving customer data only when the age is an even number. The `willSubmit` event handler returns `false` if this condition is not met.
```xmlui-pg display copy {4-9} name="Example: willSubmit"
<App>
<Form padding="0.5rem"
data="{{ name: 'Joe', age: 43 }}"
onWillSubmit="(toSubmit) => {
if (toSubmit.age % 2) {
toast.error('Age must be an even number');
return false;
}
}"
onSubmit="(toSave) => toast(JSON.stringify(toSave))">
<FlowLayout columnGap="12px" paddingBottom="6px">
<FormItem bindTo="name" label="Customer name" width="50%" />
<FormItem bindTo="age" label="Age" type="integer" width="50%"
zeroOrPositive="true" />
</FlowLayout>
</Form>
</App>
```
## Exposed Methods [#exposed-methods]
### `reset` [#reset]
This method resets the form to its initial state, clearing all user input.
**Signature**: `reset(): void`
### `update` [#update]
You can pass a data object to update the form data. The properties in the passed data object are updated to their values accordingly. Other form properties remain intact.
**Signature**: `update(data: Record<string, any>): void`
- `data`: An object containing the form data to update.
This method updates the form data with the change passed in its parameter. The parameter is a hash object, and this method updates the Form's properties accordingly.
```xmlui-pg copy display name="Example: update"
<App>
<Form id="myForm" padding="0.5rem"
data="{{ name: 'Joe', age: 43, $update: 123 }}"
onSubmit="(toSave) => toast(JSON.stringify(toSave))">
<FlowLayout columnGap="12px" paddingBottom="6px">
<FormItem bindTo="name" label="Customer name" width="50%" />
<FormItem bindTo="age" label="Age" type="integer" width="50%"
zeroOrPositive="true" />
</FlowLayout>
<Button onClick="() => $data.update({age: $data.age + 1})" >
Increment age (1)
</Button>
<Button onClick="() => myForm.update({age: $data.age + 1})" >
Increment age (2)
</Button>
<Button onClick="() => myForm.update({name: $data.name + '!', age: $data.age + 1})" >
Update name and age
</Button>
</Form>
</App>
```
### `validate` [#validate]
This method triggers validation on all form fields without submitting the form. It displays validation errors and returns the validation result along with the cleaned form data. This is useful for implementing custom submit buttons or performing operations that require validated data without actually submitting the form.
**Signature**: `validate(): Promise<{ isValid: boolean, data: Record<string, any>, errors: ValidationResult[], warnings: ValidationResult[], validationResults: Record<string, ValidationResult> }>`
## Styling [#styling]
### Theme Variables [#theme-variables]
| Variable | Default Value (Light) | Default Value (Dark) |
| --- | --- | --- |
| [backgroundColor](../styles-and-themes/common-units/#color)-ValidationDisplay-error | $color-danger-100 | $color-danger-100 |
| [backgroundColor](../styles-and-themes/common-units/#color)-ValidationDisplay-info | $color-primary-100 | $color-primary-100 |
| [backgroundColor](../styles-and-themes/common-units/#color)-ValidationDisplay-valid | $color-success-100 | $color-success-100 |
| [backgroundColor](../styles-and-themes/common-units/#color)-ValidationDisplay-warning | $color-warn-100 | $color-warn-100 |
| [color](../styles-and-themes/common-units/#color)-accent-ValidationDisplay-error | $color-error | $color-error |
| [color](../styles-and-themes/common-units/#color)-accent-ValidationDisplay-info | $color-info | $color-info |
| [color](../styles-and-themes/common-units/#color)-accent-ValidationDisplay-valid | $color-valid | $color-valid |
| [color](../styles-and-themes/common-units/#color)-accent-ValidationDisplay-warning | $color-warning | $color-warning |
| [gap](../styles-and-themes/common-units/#size)-buttonRow-Form | $space-4 | $space-4 |
| [gap](../styles-and-themes/common-units/#size)-Form | $space-4 | $space-4 |
| [marginTop](../styles-and-themes/common-units/#size)-buttonRow-Form | $space-4 | $space-4 |
| [textColor](../styles-and-themes/common-units/#color)-ValidationDisplay-error | $color-error | $color-error |
| [textColor](../styles-and-themes/common-units/#color)-ValidationDisplay-info | $color-info | $color-info |
| [textColor](../styles-and-themes/common-units/#color)-ValidationDisplay-valid | $color-valid | $color-valid |
| [textColor](../styles-and-themes/common-units/#color)-ValidationDisplay-warning | $color-warning | $color-warning |
```
--------------------------------------------------------------------------------
/xmlui/src/components/collectedComponentMetadata.ts:
--------------------------------------------------------------------------------
```typescript
import { ButtonMd } from "./Button/Button";
import { CHStackMd, CVStackMd, HStackMd, StackMd, VStackMd } from "./Stack/Stack";
import { PasswordMd, TextBoxMd } from "./TextBox/TextBox";
import { ThemeMd } from "./Theme/Theme";
import { AppMd } from "./App/App";
import { AppHeaderMd } from "./AppHeader/AppHeader";
import { AppStateMd } from "./AppState/AppState";
import { AvatarMd } from "./Avatar/Avatar";
import { BadgeMd } from "./Badge/Badge";
import { BookmarkMd } from "./Bookmark/Bookmark";
import { CardMd } from "./Card/Card";
import { ChangeListenerMd } from "./ChangeListener/ChangeListener";
import { CheckboxMd } from "./Checkbox/Checkbox";
import { ContentSeparatorMd } from "./ContentSeparator/ContentSeparator";
import { DatePickerMd } from "./DatePicker/DatePicker";
import {
DropdownMenuMd,
MenuItemMd,
MenuSeparatorMd,
SubMenuItemMd,
} from "./DropdownMenu/DropdownMenu";
import { EmojiSelectorMd } from "./EmojiSelector/EmojiSelector";
import { FileInputMd } from "./FileInput/FileInput";
import { FileUploadDropZoneMd } from "./FileUploadDropZone/FileUploadDropZone";
import { FlowLayoutMd } from "./FlowLayout/FlowLayout";
import { FooterMd } from "./Footer/Footer";
import { FormMd } from "./Form/Form";
import { FormItemMd } from "./FormItem/FormItem";
import { H1Md, H2Md, H3Md, H4Md, H5Md, H6Md, HeadingMd } from "./Heading/Heading";
import { HoverCardMd } from "./HoverCard/HoverCard";
import { IconMd } from "./Icon/Icon";
import { IFrameMd } from "./IFrame/IFrame";
import { ImageMd } from "./Image/Image";
import { ItemsMd } from "./Items/Items";
import { LinkMd } from "./Link/Link";
import { ListMd } from "./List/List";
import { LogoMd } from "./Logo/Logo";
import { MarkdownMd } from "./Markdown/Markdown";
import { ModalDialogMd } from "./ModalDialog/ModalDialog";
import { NavGroupMd } from "./NavGroup/NavGroup";
import { NavLinkMd } from "./NavLink/NavLink";
import { NavPanelMd } from "./NavPanel/NavPanel";
import { NoResultMd } from "./NoResult/NoResult";
import { NumberBoxMd } from "./NumberBox/NumberBox";
import { PageMetaTitleMd } from "./PageMetaTitle/PageMetaTitle";
import { PageMd, PagesMd } from "./Pages/Pages";
import { PositionedContainerMd } from "./PositionedContainer/PositionedContainer";
import { ProgressBarMd } from "./ProgressBar/ProgressBar";
import { QueueMd } from "./Queue/Queue";
import { RadioGroupMd } from "./RadioGroup/RadioGroup";
import { RealTimeAdapterMd } from "./RealTimeAdapter/RealTimeAdapter";
import { RedirectMd } from "./Redirect/Redirect";
import { SelectMd } from "./Select/Select";
import { SelectionStoreMd } from "./SelectionStore/SelectionStore";
import { SpaceFillerMd } from "./SpaceFiller/SpaceFiller";
import { SpinnerMd } from "./Spinner/Spinner";
import { HSplitterMd, SplitterMd, VSplitterMd } from "./Splitter/Splitter";
import { StickyBoxMd } from "./StickyBox/StickyBox";
import { SwitchMd } from "./Switch/Switch";
import { TableMd } from "./Table/Table";
import { ColumnMd } from "./Column/Column";
import { TableOfContentsMd } from "./TableOfContents/TableOfContents";
import { TabsMd } from "./Tabs/Tabs";
import { TextMd } from "./Text/Text";
import { TextAreaMd } from "./TextArea/TextArea";
import { AccordionMd } from "./Accordion/Accordion";
import { TabItemMd } from "./Tabs/TabItem";
import { FragmentMd } from "./Fragment/Fragment";
import { TreeMd } from "./Tree/TreeComponent";
import { APICallMd } from "./APICall/APICall";
import { DataSourceMd } from "./DataSource/DataSource";
import { FormSectionMd } from "./FormSection/FormSection";
import { BreakoutMd } from "./Breakout/Breakout";
import { CarouselMd } from "./Carousel/Carousel";
import { ToneChangerButtonMd } from "./ToneChangerButton/ToneChangerButton";
import { ToneSwitchMd } from "./ToneSwitch/ToneSwitch";
import { OptionMd } from "./Option/Option";
import { AutoCompleteMd } from "./AutoComplete/AutoComplete";
import { BackdropMd } from "./Backdrop/Backdrop";
import {
HtmlAddressMd,
HtmlAMd,
HtmlAreaMd,
HtmlArticleMd,
HtmlAsideMd,
HtmlAudioMd,
HtmlBdiMd,
HtmlBdoMd,
HtmlBlockquoteMd,
HtmlBMd,
HtmlBrMd,
HtmlButtonMd,
HtmlCanvasMd,
HtmlCaptionMd,
HtmlCiteMd,
HtmlCodeMd,
HtmlColgroupMd,
HtmlColMd,
HtmlDatalistMd,
HtmlDataMd,
HtmlDdMd,
HtmlDelMd,
HtmlDetailsMd,
HtmlDfnMd,
HtmlDialogMd,
HtmlDivMd,
HtmlDlMd,
HtmlDtMd,
HtmlEmbedMd,
HtmlEMMd,
HtmlFieldsetMd,
HtmlFigcaptionMd,
HtmlFigureMd,
HtmlFooterMd,
HtmlFormMd,
HtmlH1Md,
HtmlH2Md,
HtmlH3Md,
HtmlH4Md,
HtmlH5Md,
HtmlH6Md,
HtmlHeaderMd,
HtmlHrMd,
HtmlIframeMd,
HtmlIMd,
HtmlImgMd,
HtmlInputMd,
HtmlInsMd,
HtmlKbdMd,
HtmlLabelMd,
HtmlLegendMd,
HtmlLiMd,
HtmlMainMd,
HtmlMapMd,
HtmlMarkMd,
HtmlMenuMd,
HtmlMeterMd,
HtmlNavMd,
HtmlObjectMd,
HtmlOlMd,
HtmlOptgroupMd,
HtmlOptionMd,
HtmlOutputMd,
HtmlParamMd,
HtmlPictureMd,
HtmlPMd,
HtmlPreMd,
HtmlProgressMd,
HtmlQMd,
HtmlRpMd,
HtmlRtMd,
HtmlRubyMd,
HtmlSampMd,
HtmlSectionMd,
HtmlSelectMd,
HtmlSmallMd,
HtmlSMd,
HtmlSourceMd,
HtmlSpanMd,
HtmlStrongMd,
HtmlSubMd,
HtmlSummaryMd,
HtmlSupMd,
HtmlTableMd,
HtmlTbodyMd,
HtmlTdMd,
HtmlTemplateMd,
HtmlTextareaMd,
HtmlTfootMd,
HtmlTheadMd,
HtmlThMd,
HtmlTimeMd,
HtmlTrackMd,
HtmlTrMd,
HtmlUlMd,
HtmlUMd,
HtmlVideoMd,
HtmlVarMd,
HtmlWbrMd,
} from "./HtmlTags/HtmlTags";
import { SliderMd } from "./Slider/Slider";
import { ColorPickerMd } from "./ColorPicker/ColorPicker";
import type { ThemeDefinition } from "../abstractions/ThemingDefs";
import { RootThemeDefinition } from "../components-core/theming/themes/root";
import {
XmlUiCyanThemeDefinition,
XmlUiGrayThemeDefinition,
XmlUiGreenThemeDefinition,
XmlUiOrangeThemeDefinition,
XmlUiPurpleThemeDefinition,
XmlUiRedThemeDefinition,
XmlUiThemeDefinition,
} from "../components-core/theming/themes/xmlui";
import { BarChartMd } from "./Charts/BarChart/BarChart";
import { DonutChartMd } from "./Charts/DonutChart/DonutChart";
import { LabelListMd } from "./Charts/LabelList/LabelList";
import { LegendMd } from "./Charts/Legend/Legend";
import { LineChartMd } from "./Charts/LineChart/LineChart";
import { PieChartMd } from "./Charts/PieChart/PieChart";
import { ExpandableItemMd } from "./ExpandableItem/ExpandableItem";
import { SlotMd } from "./Slot/Slot";
import { TooltipMd } from "./Tooltip/Tooltip";
import { TimeInputMd } from "./TimeInput/TimeInput";
import { TimerMd } from "./Timer/Timer";
import { DateInput } from "./DateInput/DateInputNative";
import { DateInputMd } from "./DateInput/DateInput";
import { PaginationMd } from "./Pagination/Pagination";
export const collectedComponentMetadata = {
// --- HTML tags
a: HtmlAMd,
address: HtmlAddressMd,
area: HtmlAreaMd,
article: HtmlArticleMd,
aside: HtmlAsideMd,
audio: HtmlAudioMd,
b: HtmlBMd,
bdi: HtmlBdiMd,
bdo: HtmlBdoMd,
blockquote: HtmlBlockquoteMd,
br: HtmlBrMd,
button: HtmlButtonMd,
canvas: HtmlCanvasMd,
caption: HtmlCaptionMd,
cite: HtmlCiteMd,
code: HtmlCodeMd,
col: HtmlColMd,
colgroup: HtmlColgroupMd,
data: HtmlDataMd,
datalist: HtmlDatalistMd,
dd: HtmlDdMd,
del: HtmlDelMd,
details: HtmlDetailsMd,
dfn: HtmlDfnMd,
dialog: HtmlDialogMd,
div: HtmlDivMd,
dl: HtmlDlMd,
dt: HtmlDtMd,
em: HtmlEMMd,
embed: HtmlEmbedMd,
fieldset: HtmlFieldsetMd,
figcaption: HtmlFigcaptionMd,
figure: HtmlFigureMd,
footer: HtmlFooterMd,
form: HtmlFormMd,
h1: HtmlH1Md,
h2: HtmlH2Md,
h3: HtmlH3Md,
h4: HtmlH4Md,
h5: HtmlH5Md,
h6: HtmlH6Md,
header: HtmlHeaderMd,
hr: HtmlHrMd,
i: HtmlIMd,
iframe: HtmlIframeMd,
img: HtmlImgMd,
input: HtmlInputMd,
ins: HtmlInsMd,
kbd: HtmlKbdMd,
label: HtmlLabelMd,
legend: HtmlLegendMd,
li: HtmlLiMd,
main: HtmlMainMd,
map: HtmlMapMd,
mark: HtmlMarkMd,
menu: HtmlMenuMd,
meter: HtmlMeterMd,
nav: HtmlNavMd,
object: HtmlObjectMd,
ol: HtmlOlMd,
optgroup: HtmlOptgroupMd,
option: HtmlOptionMd,
output: HtmlOutputMd,
p: HtmlPMd,
param: HtmlParamMd,
picture: HtmlPictureMd,
pre: HtmlPreMd,
progress: HtmlProgressMd,
q: HtmlQMd,
rp: HtmlRpMd,
rt: HtmlRtMd,
ruby: HtmlRubyMd,
s: HtmlSMd,
samp: HtmlSampMd,
section: HtmlSectionMd,
select: HtmlSelectMd,
small: HtmlSmallMd,
source: HtmlSourceMd,
span: HtmlSpanMd,
strong: HtmlStrongMd,
sub: HtmlSubMd,
summary: HtmlSummaryMd,
sup: HtmlSupMd,
table: HtmlTableMd,
tbody: HtmlTbodyMd,
td: HtmlTdMd,
template: HtmlTemplateMd,
textarea: HtmlTextareaMd,
tfoot: HtmlTfootMd,
th: HtmlThMd,
thead: HtmlTheadMd,
time: HtmlTimeMd,
tr: HtmlTrMd,
track: HtmlTrackMd,
u: HtmlUMd,
ul: HtmlUlMd,
var: HtmlVarMd,
video: HtmlVideoMd,
wbr: HtmlWbrMd,
// --- Heavy xmlui components
Accordion: AccordionMd,
APICall: APICallMd,
App: AppMd,
AppHeader: AppHeaderMd,
AppState: AppStateMd,
AutoComplete: AutoCompleteMd,
Avatar: AvatarMd,
Backdrop: BackdropMd,
Badge: BadgeMd,
Bookmark: BookmarkMd,
Breakout: BreakoutMd,
Button: ButtonMd,
Card: CardMd,
Carousel: CarouselMd,
ChangeListener: ChangeListenerMd,
Checkbox: CheckboxMd,
CODE: HtmlCodeMd,
ColorPicker: ColorPickerMd,
Column: ColumnMd,
ContentSeparator: ContentSeparatorMd,
DataSource: DataSourceMd,
DatePicker: DatePickerMd,
DateInput: DateInputMd,
DropdownMenu: DropdownMenuMd,
EM: HtmlEMMd,
Fragment: FragmentMd,
MenuItem: MenuItemMd,
SubMenuItem: SubMenuItemMd,
EmojiSelector: EmojiSelectorMd,
ExpandableItem: ExpandableItemMd,
FileInput: FileInputMd,
FileUploadDropZone: FileUploadDropZoneMd,
FlowLayout: FlowLayoutMd,
Footer: FooterMd,
Form: FormMd,
FormItem: FormItemMd,
FormSection: FormSectionMd,
Heading: HeadingMd,
H1: H1Md,
H2: H2Md,
H3: H3Md,
H4: H4Md,
H5: H5Md,
H6: H6Md,
HoverCard: HoverCardMd,
Icon: IconMd,
IFrame: IFrameMd,
Image: ImageMd,
Items: ItemsMd,
Link: LinkMd,
List: ListMd,
Logo: LogoMd,
Markdown: MarkdownMd,
MenuSeparator: MenuSeparatorMd,
ModalDialog: ModalDialogMd,
NavGroup: NavGroupMd,
NavLink: NavLinkMd,
NavPanel: NavPanelMd,
NoResult: NoResultMd,
NumberBox: NumberBoxMd,
Option: OptionMd,
PageMetaTitle: PageMetaTitleMd,
Page: PageMd,
Pages: PagesMd,
Pagination: PaginationMd,
PositionedContainer: PositionedContainerMd,
ProgressBar: ProgressBarMd,
Queue: QueueMd,
RadioGroup: RadioGroupMd,
RealTimeAdapter: RealTimeAdapterMd,
Redirect: RedirectMd,
Select: SelectMd,
SelectionStore: SelectionStoreMd,
Slider: SliderMd,
Slot: SlotMd,
SpaceFiller: SpaceFillerMd,
Spinner: SpinnerMd,
Splitter: SplitterMd,
Tooltip: TooltipMd,
HSplitter: HSplitterMd,
VSplitter: VSplitterMd,
Stack: StackMd,
CHStack: CHStackMd,
CVStack: CVStackMd,
HStack: HStackMd,
VStack: VStackMd,
StickyBox: StickyBoxMd,
Switch: SwitchMd,
Table: TableMd,
TableOfContents: TableOfContentsMd,
TabItem: TabItemMd,
Tabs: TabsMd,
Text: TextMd,
TextArea: TextAreaMd,
TextBox: TextBoxMd,
PasswordInput: PasswordMd,
Theme: ThemeMd,
TimeInput: TimeInputMd,
Timer: TimerMd,
ToneChangerButton: ToneChangerButtonMd,
ToneSwitch: ToneSwitchMd,
Tree: TreeMd,
BarChart: BarChartMd,
DonutChart: DonutChartMd,
LabelList: LabelListMd,
Legend: LegendMd,
LineChart: LineChartMd,
PieChart: PieChartMd,
};
export const collectedThemes: Record<string, ThemeDefinition> = {
root: RootThemeDefinition,
xmlui: XmlUiThemeDefinition,
xmluiGreen: XmlUiGreenThemeDefinition,
xmluiGray: XmlUiGrayThemeDefinition,
xmluiOrange: XmlUiOrangeThemeDefinition,
xmluiPurple: XmlUiPurpleThemeDefinition,
xmluiCyan: XmlUiCyanThemeDefinition,
xmluiRed: XmlUiRedThemeDefinition,
};
```
--------------------------------------------------------------------------------
/xmlui/src/components-core/utils/extractParam.ts:
--------------------------------------------------------------------------------
```typescript
import React, { type CSSProperties } from "react";
import { isPlainObject } from "lodash-es";
import type { ContainerState } from "../rendering/ContainerWrapper";
import type { AppContextObject } from "../../abstractions/AppContextDefs";
import { parseParameterString } from "../script-runner/ParameterParser";
import { evalBinding } from "../script-runner/eval-tree-sync";
import { LRUCache } from "../utils/LruCache";
import type { ValueExtractor } from "../../abstractions/RendererDefs";
import { layoutOptionKeys } from "../descriptorHelper";
import { asOptionalBoolean } from "../rendering/valueExtractor";
/**
* Extract the value of the specified parameter from the given view container state
* @param state The state of the view container
* @param param Parameter to extract
* @param appContext Application context to use
* @param strict Strict evaluation?
* @param extractContext
* @returns
*/
export function extractParam(
state: ContainerState,
param: any,
appContext: AppContextObject | undefined = undefined,
strict: boolean = false, // --- In this case we only allow string binding expression
extractContext: { didResolve: boolean } = { didResolve: false },
): any {
if (typeof param === "string") {
const paramSegments = parseParameterString(param);
if (paramSegments.length === 0) {
// --- The param is an empty string, retrieve it
return param;
}
// --- Cut the first segment, if it is whitespace-only
if (paramSegments[0].type === "literal" && paramSegments[0].value.trim() === "") {
paramSegments.shift();
}
if (paramSegments.length === 0) {
// --- The param is an empty string, retrieve it
return param;
}
// --- Cut the last segment, if it is whitespace-only
const lastSegment = paramSegments[paramSegments.length - 1];
if (lastSegment.type === "literal" && lastSegment.value.trim() === "") {
paramSegments.pop();
}
if (paramSegments.length === 0) {
// --- The param is an empty string, retrieve it
return param;
}
if (paramSegments.length === 1) {
// --- We have a single string literal or expression
if (paramSegments[0].type === "literal") {
// --- No expression to evaluate
return paramSegments[0].value;
} else {
// --- We have a single expression to evaluate
extractContext.didResolve = true;
return evalBinding(paramSegments[0].value, {
localContext: state,
appContext,
options: {
defaultToOptionalMemberAccess: true,
},
});
}
}
// --- At this point, we have multiple segments. Evaluate all expressions and convert them to strings
let result = "";
paramSegments.forEach((ps) => {
if (ps.type === "literal") {
result += ps.value;
} else {
extractContext.didResolve = true;
const exprValue = evalBinding(ps.value, {
localContext: state,
appContext,
options: {
defaultToOptionalMemberAccess: true,
},
});
if (exprValue?.toString) {
result += exprValue.toString();
}
}
});
return result;
}
if (strict) {
// --- As we allow only string parameters as binding expressions, we return with the provided
// --- *not string* parameter without transforming it
return param;
}
// --- Resolve each array item
if (Array.isArray(param)) {
const arrayExtractContext = { didResolve: false };
let resolvedChildren = param.map((childParam) =>
extractParam(state, childParam, appContext, false, arrayExtractContext),
);
if (arrayExtractContext.didResolve) {
extractContext.didResolve = true;
return resolvedChildren;
}
return param;
}
// --- Resolve each object property
if (isPlainObject(param)) {
const objectExtractContext = { didResolve: false };
const substitutedObject: Record<string, any> = {};
Object.entries(param).forEach(([key, value]) => {
substitutedObject[key] = extractParam(state, value, appContext, false, objectExtractContext);
});
if (objectExtractContext.didResolve) {
extractContext.didResolve = true;
return substitutedObject;
}
return param;
}
// --- The param itself is the extracted value
return param;
}
// --- Store stable object references for extracted parameter values
const extractedObjectCache = new LRUCache(1024 * 10);
/**
* Get a stable object reference from an LRU cache
* @param object Object to get the stable reference for
*
* We are doing this to prevent creating new objects with new references when the data hasn't changed this way we
* can use these as dependencies for useEffect
*/
export function withStableObjectReference(object: any) {
if (typeof object === "function") {
return object;
}
if (React.isValidElement(object) || (Array.isArray(object) && React.isValidElement(object[0]))) {
//here could be some gnarly circular object references, JSON.stringify would blow up
return object;
}
if (object?._ARROW_EXPR_) {
//here could be some gnarly circular object references, JSON.stringify would blow up
return object;
}
try {
const stringObject = JSON.stringify(object);
const cachedObject = extractedObjectCache.get(stringObject);
if (cachedObject) {
return cachedObject;
}
extractedObjectCache.set(stringObject, object);
} catch (e) {
console.log(object);
console.warn("couldn't cache result", e);
}
return object;
}
export function shouldKeep(
when: string | boolean | undefined,
componentState: ContainerState,
appContext?: AppContextObject,
) {
if (when === undefined) {
return true;
}
return asOptionalBoolean(extractParam(componentState, when, appContext, true));
}
/**
* Resolves props that can either be regular properties or URL resources.
* It also removes layoutCss props from regular properties.
* @param props Component (rest) props
* @param extractValue Value extractor function
* @param layoutCss Component styles
* @param resourceExtraction URL resource extractor function and array that specifies which props are URL resources
* @returns properties that are resolved and cleaned of CSS styles
*/
export function resolveAndCleanProps<T extends Record<string, any>>(
props: Record<string, any>,
extractValue: ValueExtractor,
resourceExtraction?: {
extractResourceUrl: (url?: string) => string | undefined;
resourceProps?: string[];
},
): T {
const { extractResourceUrl, resourceProps } = resourceExtraction ?? {};
const cleanedProps = removeStylesFromProps(props);
const resultProps: Record<string, any> = {} as any;
const result = Object.keys(cleanedProps).reduce((acc, propName) => {
if (resourceProps && extractResourceUrl && resourceProps.includes(propName)) {
acc[propName] = extractResourceUrl(cleanedProps[propName]);
} else {
acc[propName] = extractValue(cleanedProps[propName]);
}
return acc;
}, resultProps);
// --- Remove aliased CSS properties
delete resultProps.canShrink;
delete resultProps.radiusTopLeft;
delete resultProps.radiusTopRight;
delete resultProps.radiusBottomLeft;
delete resultProps.radiusBottomRight;
// --- Delete pseudo CSS properties
delete resultProps.paddingHorizontal;
delete resultProps.paddingVertical;
delete resultProps.marginHorizontal;
delete resultProps.marginVertical;
delete resultProps.borderHorizontal;
delete resultProps.borderVertical;
return result as T;
}
/**
* Removes unnecessary style related properties so only layoutCss contains them.
* @param nodeProps properties to clean
* @returns only component-specific properties
*/
export function removeStylesFromProps(
nodeProps: Record<string, any>,
) {
if (nodeProps.hasOwnProperty("style")) {
delete nodeProps["style"];
}
if (nodeProps.hasOwnProperty("class")) {
delete nodeProps["class"];
}
const filterKeys = layoutOptionKeys;
return Object.fromEntries(
Object.entries(nodeProps).filter(([key]) => !filterKeys.includes(key)),
);
}
type NodeProps = Record<string, any>;
type ResourceUrlExtractor = (url?: string) => string | undefined;
/**
* Extracts props that can either be regular properties or URL resources.
* It also removes layoutCss props from regular properties fed into it.
* @param extractValue Value extractor function
* @param extractResourceUrl URL resource extractor function that specifies which props are URL resources
* @param layoutCss Component styles
* @param props Component props
* @returns properties that are resolved and cleaned of CSS styles
*/
export class PropsTrasform<T extends NodeProps> {
private nodeProps: T;
private extractValue: ValueExtractor;
private extractResourceUrl: ResourceUrlExtractor;
private usedKeys: (keyof T)[] = [];
constructor(
extractValue: ValueExtractor,
extractResourceUrl: ResourceUrlExtractor,
props: T,
) {
this.extractValue = extractValue;
this.extractResourceUrl = extractResourceUrl;
this.nodeProps = removeStylesFromProps(props) as T;
}
private mapValues(keys: (keyof T)[], fn: (value: any) => any) {
this.usedKeys = Array.from(new Set(this.usedKeys.concat(keys)));
return Object.fromEntries(keys.map((key) => [key, fn(this.nodeProps[key])]));
}
asValue<K extends keyof T>(...key: K[]) {
return this.mapValues(key, this.extractValue) as T;
}
asUrlResource<K extends keyof T>(...key: K[]) {
return this.mapValues(key, this.extractResourceUrl) as Record<K, string | undefined>;
}
asBoolean<K extends keyof T>(...key: K[]) {
return this.mapValues(key, this.extractValue.asBoolean) as Record<
string,
ReturnType<ValueExtractor["asBoolean"]>
>;
}
asOptionalBoolean<K extends keyof T>(...key: K[]) {
return this.mapValues(key, this.extractValue.asOptionalBoolean) as Record<
K,
ReturnType<ValueExtractor["asOptionalBoolean"]>
>;
}
asString<K extends keyof T>(...key: K[]) {
return this.mapValues(key, this.extractValue.asString) as Record<
string,
ReturnType<ValueExtractor["asString"]>
>;
}
asOptionalString<K extends keyof T>(...key: K[]) {
return this.mapValues(key, this.extractValue.asOptionalString) as Record<
string,
ReturnType<ValueExtractor["asOptionalString"]>
>;
}
asOptionalStringArray<K extends keyof T>(...key: K[]) {
return this.mapValues(key, this.extractValue.asOptionalString) as Record<
string,
ReturnType<ValueExtractor["asOptionalStringArray"]>
>;
}
asDisplayText<K extends keyof T>(...key: K[]) {
return this.mapValues(key, this.extractValue.asDisplayText) as Record<
string,
ReturnType<ValueExtractor["asDisplayText"]>
>;
}
asNumber<K extends keyof T>(...key: K[]) {
return this.mapValues(key, this.extractValue.asNumber) as Record<
string,
ReturnType<ValueExtractor["asNumber"]>
>;
}
asOptionalNumber<K extends keyof T>(...key: K[]) {
return this.mapValues(key, this.extractValue.asOptionalNumber) as Record<
K,
ReturnType<ValueExtractor["asOptionalNumber"]>
>;
}
asSize<K extends keyof T>(...key: K[]) {
return this.mapValues(key, this.extractValue.asSize) as Record<
K,
ReturnType<ValueExtractor["asSize"]>
>;
}
/**
* Resolves props which have not been used yet.
* If all keys have been referenced before this function is called, an empty object is returned.
*
* @returns props that have not been used yet
*/
asRest(): T {
const filteredKeys = Object.keys(this.nodeProps).filter(
(propKey) => !this.usedKeys.includes(propKey as keyof T),
);
return this.mapValues(filteredKeys, this.extractValue) as T;
}
}
```