This is page 20 of 140. Use http://codebase.md/xmlui-org/xmlui/cantFindIt.jpg?lines=false&page={x} to view the full context.
# Directory Structure
```
├── .changeset
│ ├── config.json
│ ├── silver-llamas-cough.md
│ └── true-jeans-agree.md
├── .eslintrc.cjs
├── .github
│ ├── build-checklist.png
│ ├── ISSUE_TEMPLATE
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ └── workflows
│ ├── deploy-blog.yml
│ ├── deploy-docs-optimized.yml
│ ├── deploy-docs.yml
│ ├── prepare-versions.yml
│ ├── release-packages.yml
│ ├── run-all-tests.yml
│ └── run-smoke-tests.yml
├── .gitignore
├── .prettierrc.js
├── .vscode
│ ├── launch.json
│ └── settings.json
├── blog
│ ├── .gitignore
│ ├── .gitkeep
│ ├── CHANGELOG.md
│ ├── extensions.ts
│ ├── index.html
│ ├── index.ts
│ ├── package.json
│ ├── public
│ │ ├── blog
│ │ │ ├── images
│ │ │ │ ├── blog-page-component.png
│ │ │ │ ├── blog-scrabble.png
│ │ │ │ ├── integrated-blog-search.png
│ │ │ │ └── lorem-ipsum.png
│ │ │ ├── lorem-ipsum.md
│ │ │ ├── newest-post.md
│ │ │ ├── older-post.md
│ │ │ └── welcome-to-the-xmlui-blog.md
│ │ ├── mockServiceWorker.js
│ │ ├── resources
│ │ │ ├── favicon.ico
│ │ │ ├── files
│ │ │ │ └── for-download
│ │ │ │ └── xmlui
│ │ │ │ └── xmlui-standalone.umd.js
│ │ │ ├── github.svg
│ │ │ ├── llms.txt
│ │ │ ├── logo-dark.svg
│ │ │ ├── logo.svg
│ │ │ ├── pg-popout.svg
│ │ │ ├── rss.svg
│ │ │ └── xmlui-logo.svg
│ │ ├── serve.json
│ │ └── web.config
│ ├── scripts
│ │ ├── download-latest-xmlui.js
│ │ ├── generate-rss.js
│ │ ├── get-releases.js
│ │ └── utils.js
│ ├── src
│ │ ├── components
│ │ │ ├── BlogOverview.xmlui
│ │ │ ├── BlogPage.xmlui
│ │ │ └── PageNotFound.xmlui
│ │ ├── config.ts
│ │ ├── Main.xmlui
│ │ └── themes
│ │ └── blog-theme.ts
│ └── tsconfig.json
├── CONTRIBUTING.md
├── docs
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── ComponentRefLinks.txt
│ ├── content
│ │ ├── _meta.json
│ │ ├── components
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── APICall.md
│ │ │ ├── App.md
│ │ │ ├── AppHeader.md
│ │ │ ├── AppState.md
│ │ │ ├── AutoComplete.md
│ │ │ ├── Avatar.md
│ │ │ ├── Backdrop.md
│ │ │ ├── Badge.md
│ │ │ ├── BarChart.md
│ │ │ ├── Bookmark.md
│ │ │ ├── Breakout.md
│ │ │ ├── Button.md
│ │ │ ├── Card.md
│ │ │ ├── Carousel.md
│ │ │ ├── ChangeListener.md
│ │ │ ├── Checkbox.md
│ │ │ ├── CHStack.md
│ │ │ ├── ColorPicker.md
│ │ │ ├── Column.md
│ │ │ ├── ContentSeparator.md
│ │ │ ├── CVStack.md
│ │ │ ├── DataSource.md
│ │ │ ├── DateInput.md
│ │ │ ├── DatePicker.md
│ │ │ ├── DonutChart.md
│ │ │ ├── DropdownMenu.md
│ │ │ ├── EmojiSelector.md
│ │ │ ├── ExpandableItem.md
│ │ │ ├── FileInput.md
│ │ │ ├── FileUploadDropZone.md
│ │ │ ├── FlowLayout.md
│ │ │ ├── Footer.md
│ │ │ ├── Form.md
│ │ │ ├── FormItem.md
│ │ │ ├── FormSection.md
│ │ │ ├── Fragment.md
│ │ │ ├── H1.md
│ │ │ ├── H2.md
│ │ │ ├── H3.md
│ │ │ ├── H4.md
│ │ │ ├── H5.md
│ │ │ ├── H6.md
│ │ │ ├── Heading.md
│ │ │ ├── HSplitter.md
│ │ │ ├── HStack.md
│ │ │ ├── Icon.md
│ │ │ ├── IFrame.md
│ │ │ ├── Image.md
│ │ │ ├── Items.md
│ │ │ ├── LabelList.md
│ │ │ ├── Legend.md
│ │ │ ├── LineChart.md
│ │ │ ├── Link.md
│ │ │ ├── List.md
│ │ │ ├── Logo.md
│ │ │ ├── Markdown.md
│ │ │ ├── MenuItem.md
│ │ │ ├── MenuSeparator.md
│ │ │ ├── ModalDialog.md
│ │ │ ├── NavGroup.md
│ │ │ ├── NavLink.md
│ │ │ ├── NavPanel.md
│ │ │ ├── NoResult.md
│ │ │ ├── NumberBox.md
│ │ │ ├── Option.md
│ │ │ ├── Page.md
│ │ │ ├── PageMetaTitle.md
│ │ │ ├── Pages.md
│ │ │ ├── Pagination.md
│ │ │ ├── PasswordInput.md
│ │ │ ├── PieChart.md
│ │ │ ├── ProgressBar.md
│ │ │ ├── Queue.md
│ │ │ ├── RadioGroup.md
│ │ │ ├── RealTimeAdapter.md
│ │ │ ├── Redirect.md
│ │ │ ├── Select.md
│ │ │ ├── Slider.md
│ │ │ ├── Slot.md
│ │ │ ├── SpaceFiller.md
│ │ │ ├── Spinner.md
│ │ │ ├── Splitter.md
│ │ │ ├── Stack.md
│ │ │ ├── StickyBox.md
│ │ │ ├── SubMenuItem.md
│ │ │ ├── Switch.md
│ │ │ ├── TabItem.md
│ │ │ ├── Table.md
│ │ │ ├── TableOfContents.md
│ │ │ ├── Tabs.md
│ │ │ ├── Text.md
│ │ │ ├── TextArea.md
│ │ │ ├── TextBox.md
│ │ │ ├── Theme.md
│ │ │ ├── TimeInput.md
│ │ │ ├── Timer.md
│ │ │ ├── ToneChangerButton.md
│ │ │ ├── ToneSwitch.md
│ │ │ ├── Tooltip.md
│ │ │ ├── Tree.md
│ │ │ ├── VSplitter.md
│ │ │ ├── VStack.md
│ │ │ ├── xmlui-animations
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── Animation.md
│ │ │ │ ├── FadeAnimation.md
│ │ │ │ ├── FadeInAnimation.md
│ │ │ │ ├── FadeOutAnimation.md
│ │ │ │ ├── ScaleAnimation.md
│ │ │ │ └── SlideInAnimation.md
│ │ │ ├── xmlui-pdf
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Pdf.md
│ │ │ ├── xmlui-spreadsheet
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Spreadsheet.md
│ │ │ └── xmlui-website-blocks
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── Carousel.md
│ │ │ ├── HelloMd.md
│ │ │ ├── HeroSection.md
│ │ │ └── ScrollToTop.md
│ │ └── extensions
│ │ ├── _meta.json
│ │ ├── xmlui-animations
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── Animation.md
│ │ │ ├── FadeAnimation.md
│ │ │ ├── FadeInAnimation.md
│ │ │ ├── FadeOutAnimation.md
│ │ │ ├── ScaleAnimation.md
│ │ │ └── SlideInAnimation.md
│ │ └── xmlui-website-blocks
│ │ ├── _meta.json
│ │ ├── _overview.md
│ │ ├── Carousel.md
│ │ ├── HelloMd.md
│ │ ├── HeroSection.md
│ │ └── ScrollToTop.md
│ ├── extensions.ts
│ ├── index.html
│ ├── index.ts
│ ├── package.json
│ ├── public
│ │ ├── feed.rss
│ │ ├── mockServiceWorker.js
│ │ ├── pages
│ │ │ ├── _meta.json
│ │ │ ├── app-structure.md
│ │ │ ├── build-editor-component.md
│ │ │ ├── build-hello-world-component.md
│ │ │ ├── components-intro.md
│ │ │ ├── context-variables.md
│ │ │ ├── forms.md
│ │ │ ├── globals.md
│ │ │ ├── glossary.md
│ │ │ ├── helper-tags.md
│ │ │ ├── hosted-deployment.md
│ │ │ ├── howto
│ │ │ │ ├── assign-a-complex-json-literal-to-a-component-variable.md
│ │ │ │ ├── chain-a-refetch.md
│ │ │ │ ├── control-cache-invalidation.md
│ │ │ │ ├── debug-a-component.md
│ │ │ │ ├── delay-a-datasource-until-another-datasource-is-ready.md
│ │ │ │ ├── delegate-a-method.md
│ │ │ │ ├── do-custom-form-validation.md
│ │ │ │ ├── expose-a-method-from-a-component.md
│ │ │ │ ├── filter-and-transform-data-from-an-api.md
│ │ │ │ ├── group-items-in-list-by-a-property.md
│ │ │ │ ├── handle-background-operations.md
│ │ │ │ ├── hide-an-element-until-its-datasource-is-ready.md
│ │ │ │ ├── make-a-set-of-equal-width-cards.md
│ │ │ │ ├── make-a-table-responsive.md
│ │ │ │ ├── make-navpanel-width-responsive.md
│ │ │ │ ├── modify-a-value-reported-in-a-column.md
│ │ │ │ ├── paginate-a-list.md
│ │ │ │ ├── pass-data-to-a-modal-dialog.md
│ │ │ │ ├── react-to-button-click-not-keystrokes.md
│ │ │ │ ├── set-the-initial-value-of-a-select-from-fetched-data.md
│ │ │ │ ├── share-a-modaldialog-across-components.md
│ │ │ │ ├── sync-selections-between-table-and-list-views.md
│ │ │ │ ├── update-ui-optimistically.md
│ │ │ │ ├── use-built-in-form-validation.md
│ │ │ │ └── use-the-same-modaldialog-to-add-or-edit.md
│ │ │ ├── howto.md
│ │ │ ├── intro.md
│ │ │ ├── layout.md
│ │ │ ├── markup.md
│ │ │ ├── mcp.md
│ │ │ ├── modal-dialogs.md
│ │ │ ├── news-and-reviews.md
│ │ │ ├── reactive-intro.md
│ │ │ ├── refactoring.md
│ │ │ ├── routing-and-links.md
│ │ │ ├── samples
│ │ │ │ ├── color-palette.xmlui
│ │ │ │ ├── color-values.xmlui
│ │ │ │ ├── shadow-sizes.xmlui
│ │ │ │ ├── spacing-sizes.xmlui
│ │ │ │ ├── swatch.xmlui
│ │ │ │ ├── theme-gallery-brief.xmlui
│ │ │ │ └── theme-gallery.xmlui
│ │ │ ├── scoping.md
│ │ │ ├── scripting.md
│ │ │ ├── styles-and-themes
│ │ │ │ ├── common-units.md
│ │ │ │ ├── layout-props.md
│ │ │ │ ├── theme-variable-defaults.md
│ │ │ │ ├── theme-variables.md
│ │ │ │ └── themes.md
│ │ │ ├── template-properties.md
│ │ │ ├── test.md
│ │ │ ├── tutorial-01.md
│ │ │ ├── tutorial-02.md
│ │ │ ├── tutorial-03.md
│ │ │ ├── tutorial-04.md
│ │ │ ├── tutorial-05.md
│ │ │ ├── tutorial-06.md
│ │ │ ├── tutorial-07.md
│ │ │ ├── tutorial-08.md
│ │ │ ├── tutorial-09.md
│ │ │ ├── tutorial-10.md
│ │ │ ├── tutorial-11.md
│ │ │ ├── tutorial-12.md
│ │ │ ├── universal-properties.md
│ │ │ ├── user-defined-components.md
│ │ │ ├── vscode.md
│ │ │ ├── working-with-markdown.md
│ │ │ ├── working-with-text.md
│ │ │ ├── xmlui-animations
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── Animation.md
│ │ │ │ ├── FadeAnimation.md
│ │ │ │ ├── FadeInAnimation.md
│ │ │ │ ├── FadeOutAnimation.md
│ │ │ │ ├── ScaleAnimation.md
│ │ │ │ └── SlideInAnimation.md
│ │ │ ├── xmlui-charts
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── BarChart.md
│ │ │ │ ├── DonutChart.md
│ │ │ │ ├── LabelList.md
│ │ │ │ ├── Legend.md
│ │ │ │ ├── LineChart.md
│ │ │ │ └── PieChart.md
│ │ │ ├── xmlui-pdf
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Pdf.md
│ │ │ └── xmlui-spreadsheet
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ └── Spreadsheet.md
│ │ ├── resources
│ │ │ ├── devdocs
│ │ │ │ ├── debug-proxy-object-2.png
│ │ │ │ ├── debug-proxy-object.png
│ │ │ │ ├── table_editor_01.png
│ │ │ │ ├── table_editor_02.png
│ │ │ │ ├── table_editor_03.png
│ │ │ │ ├── table_editor_04.png
│ │ │ │ ├── table_editor_05.png
│ │ │ │ ├── table_editor_06.png
│ │ │ │ ├── table_editor_07.png
│ │ │ │ ├── table_editor_08.png
│ │ │ │ ├── table_editor_09.png
│ │ │ │ ├── table_editor_10.png
│ │ │ │ ├── table_editor_11.png
│ │ │ │ ├── table-editor-01.png
│ │ │ │ ├── table-editor-02.png
│ │ │ │ ├── table-editor-03.png
│ │ │ │ ├── table-editor-04.png
│ │ │ │ ├── table-editor-06.png
│ │ │ │ ├── table-editor-07.png
│ │ │ │ ├── table-editor-08.png
│ │ │ │ ├── table-editor-09.png
│ │ │ │ └── xmlui-rendering-of-tiptap-markdown.png
│ │ │ ├── favicon.ico
│ │ │ ├── files
│ │ │ │ ├── clients.json
│ │ │ │ ├── daily-revenue.json
│ │ │ │ ├── dashboard-stats.json
│ │ │ │ ├── demo.xmlui
│ │ │ │ ├── demo.xmlui.xs
│ │ │ │ ├── downloads
│ │ │ │ │ └── downloads.json
│ │ │ │ ├── for-download
│ │ │ │ │ ├── index-with-api.html
│ │ │ │ │ ├── index.html
│ │ │ │ │ ├── mockApi.js
│ │ │ │ │ ├── start-darwin.sh
│ │ │ │ │ ├── start-linux.sh
│ │ │ │ │ ├── start.bat
│ │ │ │ │ └── xmlui
│ │ │ │ │ └── xmlui-standalone.umd.js
│ │ │ │ ├── getting-started
│ │ │ │ │ ├── cl-tutorial-final.zip
│ │ │ │ │ ├── cl-tutorial.zip
│ │ │ │ │ ├── cl-tutorial2.zip
│ │ │ │ │ ├── cl-tutorial3.zip
│ │ │ │ │ ├── cl-tutorial4.zip
│ │ │ │ │ ├── cl-tutorial5.zip
│ │ │ │ │ ├── cl-tutorial6.zip
│ │ │ │ │ ├── getting-started.zip
│ │ │ │ │ ├── hello-xmlui.zip
│ │ │ │ │ ├── xmlui-empty.zip
│ │ │ │ │ └── xmlui-starter.zip
│ │ │ │ ├── howto
│ │ │ │ │ └── component-icons
│ │ │ │ │ └── up-arrow.svg
│ │ │ │ ├── invoices.json
│ │ │ │ ├── monthly-status.json
│ │ │ │ ├── news-and-reviews.json
│ │ │ │ ├── products.json
│ │ │ │ ├── releases.json
│ │ │ │ ├── tutorials
│ │ │ │ │ ├── datasource
│ │ │ │ │ │ └── api.ts
│ │ │ │ │ └── p2do
│ │ │ │ │ ├── api.ts
│ │ │ │ │ └── todo-logo.svg
│ │ │ │ └── xmlui.json
│ │ │ ├── github.svg
│ │ │ ├── images
│ │ │ │ ├── apiaction-tutorial
│ │ │ │ │ ├── add-success.png
│ │ │ │ │ ├── apiaction-param.png
│ │ │ │ │ ├── change-completed.png
│ │ │ │ │ ├── change-in-progress.png
│ │ │ │ │ ├── confirm-delete.png
│ │ │ │ │ ├── data-error.png
│ │ │ │ │ ├── data-progress.png
│ │ │ │ │ ├── data-success.png
│ │ │ │ │ ├── display-1.png
│ │ │ │ │ ├── item-deleted.png
│ │ │ │ │ ├── item-updated.png
│ │ │ │ │ ├── missing-api-key.png
│ │ │ │ │ ├── new-item-added.png
│ │ │ │ │ └── test-message.png
│ │ │ │ ├── chat-api
│ │ │ │ │ └── domain-model.svg
│ │ │ │ ├── components
│ │ │ │ │ ├── image
│ │ │ │ │ │ └── breakfast.jpg
│ │ │ │ │ ├── markdown
│ │ │ │ │ │ └── colors.png
│ │ │ │ │ └── modal
│ │ │ │ │ ├── deep_link_dialog_1.jpg
│ │ │ │ │ └── deep_link_dialog_2.jpg
│ │ │ │ ├── create-apps
│ │ │ │ │ ├── collapsed-vertical.png
│ │ │ │ │ ├── using-forms-warning-dialog.png
│ │ │ │ │ └── using-forms.png
│ │ │ │ ├── datasource-tutorial
│ │ │ │ │ ├── data-with-header.png
│ │ │ │ │ ├── filtered-data.png
│ │ │ │ │ ├── filtered-items.png
│ │ │ │ │ ├── initial-page-items.png
│ │ │ │ │ ├── list-items.png
│ │ │ │ │ ├── next-page-items.png
│ │ │ │ │ ├── no-data.png
│ │ │ │ │ ├── pagination-1.jpg
│ │ │ │ │ ├── pagination-1.png
│ │ │ │ │ ├── polling-1.png
│ │ │ │ │ ├── refetch-data.png
│ │ │ │ │ ├── slow-loading.png
│ │ │ │ │ ├── test-message.png
│ │ │ │ │ ├── Thumbs.db
│ │ │ │ │ ├── unconventional-data.png
│ │ │ │ │ └── unfiltered-items.png
│ │ │ │ ├── flower.jpg
│ │ │ │ ├── get-started
│ │ │ │ │ ├── add-new-contact.png
│ │ │ │ │ ├── app-modified.png
│ │ │ │ │ ├── app-start.png
│ │ │ │ │ ├── app-with-boxes.png
│ │ │ │ │ ├── app-with-toast.png
│ │ │ │ │ ├── boilerplate-structure.png
│ │ │ │ │ ├── cl-initial.png
│ │ │ │ │ ├── cl-start.png
│ │ │ │ │ ├── contact-counts.png
│ │ │ │ │ ├── contact-dialog-title.png
│ │ │ │ │ ├── contact-dialog.png
│ │ │ │ │ ├── contact-menus.png
│ │ │ │ │ ├── contact-predicates.png
│ │ │ │ │ ├── context-menu.png
│ │ │ │ │ ├── dashboard-numbers.png
│ │ │ │ │ ├── default-contact-list.png
│ │ │ │ │ ├── delete-contact.png
│ │ │ │ │ ├── delete-task.png
│ │ │ │ │ ├── detailed-template.png
│ │ │ │ │ ├── edit-contact-details.png
│ │ │ │ │ ├── edited-contact-saved.png
│ │ │ │ │ ├── empty-sections.png
│ │ │ │ │ ├── filter-completed.png
│ │ │ │ │ ├── fullwidth-desktop.png
│ │ │ │ │ ├── fullwidth-mobile.png
│ │ │ │ │ ├── initial-table.png
│ │ │ │ │ ├── items-and-badges.png
│ │ │ │ │ ├── loading-message.png
│ │ │ │ │ ├── new-contact-button.png
│ │ │ │ │ ├── new-contact-saved.png
│ │ │ │ │ ├── no-empty-sections.png
│ │ │ │ │ ├── personal-todo-initial.png
│ │ │ │ │ ├── piechart.png
│ │ │ │ │ ├── review-today.png
│ │ │ │ │ ├── rudimentary-dashboard.png
│ │ │ │ │ ├── section-collapsed.png
│ │ │ │ │ ├── sectioned-items.png
│ │ │ │ │ ├── sections-ordered.png
│ │ │ │ │ ├── spacex-list-with-links.png
│ │ │ │ │ ├── spacex-list.png
│ │ │ │ │ ├── start-personal-todo-1.png
│ │ │ │ │ ├── submit-new-contact.png
│ │ │ │ │ ├── submit-new-task.png
│ │ │ │ │ ├── syntax-highlighting.png
│ │ │ │ │ ├── table-with-badge.png
│ │ │ │ │ ├── template-with-card.png
│ │ │ │ │ ├── test-emulated-api.png
│ │ │ │ │ ├── Thumbs.db
│ │ │ │ │ ├── todo-logo.png
│ │ │ │ │ └── xmlui-tools.png
│ │ │ │ ├── HelloApp.png
│ │ │ │ ├── HelloApp2.png
│ │ │ │ ├── logos
│ │ │ │ │ ├── xmlui1.svg
│ │ │ │ │ ├── xmlui2.svg
│ │ │ │ │ ├── xmlui3.svg
│ │ │ │ │ ├── xmlui4.svg
│ │ │ │ │ ├── xmlui5.svg
│ │ │ │ │ ├── xmlui6.svg
│ │ │ │ │ └── xmlui7.svg
│ │ │ │ ├── pdf
│ │ │ │ │ └── dummy-pdf.jpg
│ │ │ │ ├── rendering-engine
│ │ │ │ │ ├── AppEngine-flow.svg
│ │ │ │ │ ├── Component.svg
│ │ │ │ │ ├── CompoundComponent.svg
│ │ │ │ │ ├── RootComponent.svg
│ │ │ │ │ └── tree-with-containers.svg
│ │ │ │ ├── reviewers-guide
│ │ │ │ │ ├── AppEngine-flow.svg
│ │ │ │ │ └── incbutton-in-action.png
│ │ │ │ ├── tools
│ │ │ │ │ └── boilerplate-structure.png
│ │ │ │ ├── try.svg
│ │ │ │ ├── tutorial
│ │ │ │ │ ├── app-chat-history.png
│ │ │ │ │ ├── app-content-placeholder.png
│ │ │ │ │ ├── app-header-and-content.png
│ │ │ │ │ ├── app-links-channel-selected.png
│ │ │ │ │ ├── app-links-click.png
│ │ │ │ │ ├── app-navigation.png
│ │ │ │ │ ├── finished-ex01.png
│ │ │ │ │ ├── finished-ex02.png
│ │ │ │ │ ├── hello.png
│ │ │ │ │ ├── splash-screen-advanced.png
│ │ │ │ │ ├── splash-screen-after-click.png
│ │ │ │ │ ├── splash-screen-centered.png
│ │ │ │ │ ├── splash-screen-events.png
│ │ │ │ │ ├── splash-screen-expression.png
│ │ │ │ │ ├── splash-screen-reuse-after.png
│ │ │ │ │ ├── splash-screen-reuse-before.png
│ │ │ │ │ └── splash-screen.png
│ │ │ │ └── tutorial-01.png
│ │ │ ├── llms.txt
│ │ │ ├── logo-dark.svg
│ │ │ ├── logo.svg
│ │ │ ├── pg-popout.svg
│ │ │ └── xmlui-logo.svg
│ │ ├── serve.json
│ │ └── web.config
│ ├── scripts
│ │ ├── download-latest-xmlui.js
│ │ ├── generate-rss.js
│ │ ├── get-releases.js
│ │ └── utils.js
│ ├── src
│ │ ├── components
│ │ │ ├── BlogOverview.xmlui
│ │ │ ├── BlogPage.xmlui
│ │ │ ├── Boxes.xmlui
│ │ │ ├── Breadcrumb.xmlui
│ │ │ ├── ChangeLog.xmlui
│ │ │ ├── ColorPalette.xmlui
│ │ │ ├── DocumentLinks.xmlui
│ │ │ ├── DocumentPage.xmlui
│ │ │ ├── DocumentPageNoTOC.xmlui
│ │ │ ├── Icons.xmlui
│ │ │ ├── IncButton.xmlui
│ │ │ ├── IncButton2.xmlui
│ │ │ ├── NameValue.xmlui
│ │ │ ├── PageNotFound.xmlui
│ │ │ ├── PaletteItem.xmlui
│ │ │ ├── Palettes.xmlui
│ │ │ ├── SectionHeader.xmlui
│ │ │ ├── TBD.xmlui
│ │ │ ├── Test.xmlui
│ │ │ ├── ThemesIntro.xmlui
│ │ │ ├── ThousandThemes.xmlui
│ │ │ ├── TubeStops.xmlui
│ │ │ ├── TubeStops.xmlui.xs
│ │ │ └── TwoColumnCode.xmlui
│ │ ├── config.ts
│ │ ├── Main.xmlui
│ │ └── themes
│ │ ├── docs-theme.ts
│ │ ├── earthtone.ts
│ │ ├── xmlui-gray-on-default.ts
│ │ ├── xmlui-green-on-default.ts
│ │ └── xmlui-orange-on-default.ts
│ └── tsconfig.json
├── LICENSE
├── package-lock.json
├── package.json
├── packages
│ ├── tsconfig.json
│ ├── xmlui-animations
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── Animation.tsx
│ │ ├── AnimationNative.tsx
│ │ ├── FadeAnimation.tsx
│ │ ├── FadeInAnimation.tsx
│ │ ├── FadeOutAnimation.tsx
│ │ ├── index.tsx
│ │ ├── ScaleAnimation.tsx
│ │ └── SlideInAnimation.tsx
│ ├── xmlui-devtools
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ ├── src
│ │ │ ├── devtools
│ │ │ │ ├── DevTools.tsx
│ │ │ │ ├── DevToolsNative.module.scss
│ │ │ │ ├── DevToolsNative.tsx
│ │ │ │ ├── ModalDialog.module.scss
│ │ │ │ ├── ModalDialog.tsx
│ │ │ │ ├── ModalVisibilityContext.tsx
│ │ │ │ ├── Tooltip.module.scss
│ │ │ │ ├── Tooltip.tsx
│ │ │ │ └── utils.ts
│ │ │ ├── editor
│ │ │ │ └── Editor.tsx
│ │ │ └── index.tsx
│ │ └── vite.config-overrides.ts
│ ├── xmlui-hello-world
│ │ ├── .gitignore
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── HelloWorld.module.scss
│ │ ├── HelloWorld.tsx
│ │ ├── HelloWorldNative.tsx
│ │ └── index.tsx
│ ├── xmlui-os-frames
│ │ ├── .gitignore
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── index.tsx
│ │ ├── IPhoneFrame.module.scss
│ │ ├── IPhoneFrame.tsx
│ │ ├── MacOSAppFrame.module.scss
│ │ ├── MacOSAppFrame.tsx
│ │ ├── WindowsAppFrame.module.scss
│ │ └── WindowsAppFrame.tsx
│ ├── xmlui-pdf
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ ├── components
│ │ │ │ └── Pdf.xmlui
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── index.tsx
│ │ ├── LazyPdfNative.tsx
│ │ ├── Pdf.module.scss
│ │ └── Pdf.tsx
│ ├── xmlui-playground
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── hooks
│ │ │ ├── usePlayground.ts
│ │ │ └── useToast.ts
│ │ ├── index.tsx
│ │ ├── playground
│ │ │ ├── Box.module.scss
│ │ │ ├── Box.tsx
│ │ │ ├── CodeSelector.tsx
│ │ │ ├── ConfirmationDialog.module.scss
│ │ │ ├── ConfirmationDialog.tsx
│ │ │ ├── Editor.tsx
│ │ │ ├── Header.module.scss
│ │ │ ├── Header.tsx
│ │ │ ├── Playground.tsx
│ │ │ ├── PlaygroundContent.module.scss
│ │ │ ├── PlaygroundContent.tsx
│ │ │ ├── PlaygroundNative.module.scss
│ │ │ ├── PlaygroundNative.tsx
│ │ │ ├── Preview.module.scss
│ │ │ ├── Preview.tsx
│ │ │ ├── Select.module.scss
│ │ │ ├── StandalonePlayground.tsx
│ │ │ ├── StandalonePlaygroundNative.module.scss
│ │ │ ├── StandalonePlaygroundNative.tsx
│ │ │ ├── ThemeSwitcher.module.scss
│ │ │ ├── ThemeSwitcher.tsx
│ │ │ ├── ToneSwitcher.tsx
│ │ │ ├── Tooltip.module.scss
│ │ │ ├── Tooltip.tsx
│ │ │ └── utils.ts
│ │ ├── providers
│ │ │ ├── Toast.module.scss
│ │ │ └── ToastProvider.tsx
│ │ ├── state
│ │ │ └── store.ts
│ │ ├── themes
│ │ │ └── theme.ts
│ │ └── utils
│ │ └── helpers.ts
│ ├── xmlui-search
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── index.tsx
│ │ ├── Search.module.scss
│ │ └── Search.tsx
│ ├── xmlui-spreadsheet
│ │ ├── .gitignore
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── index.tsx
│ │ ├── Spreadsheet.tsx
│ │ └── SpreadsheetNative.tsx
│ └── xmlui-website-blocks
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── demo
│ │ ├── components
│ │ │ ├── HeroBackgroundBreakoutPage.xmlui
│ │ │ ├── HeroBackgroundsPage.xmlui
│ │ │ ├── HeroContentsPage.xmlui
│ │ │ ├── HeroTextAlignPage.xmlui
│ │ │ ├── HeroTextPage.xmlui
│ │ │ └── HeroTonesPage.xmlui
│ │ ├── Main.xmlui
│ │ └── themes
│ │ └── default.ts
│ ├── index.html
│ ├── index.ts
│ ├── meta
│ │ └── componentsMetadata.ts
│ ├── package.json
│ ├── public
│ │ └── resources
│ │ ├── building.jpg
│ │ └── xmlui-logo.svg
│ └── src
│ ├── Carousel
│ │ ├── Carousel.module.scss
│ │ ├── Carousel.tsx
│ │ ├── CarouselContext.tsx
│ │ └── CarouselNative.tsx
│ ├── FancyButton
│ │ ├── FancyButton.module.scss
│ │ ├── FancyButton.tsx
│ │ └── FancyButton.xmlui
│ ├── Hello
│ │ ├── Hello.tsx
│ │ ├── Hello.xmlui
│ │ └── Hello.xmlui.xs
│ ├── HeroSection
│ │ ├── HeroSection.module.scss
│ │ ├── HeroSection.spec.ts
│ │ ├── HeroSection.tsx
│ │ └── HeroSectionNative.tsx
│ ├── index.tsx
│ ├── ScrollToTop
│ │ ├── ScrollToTop.module.scss
│ │ ├── ScrollToTop.tsx
│ │ └── ScrollToTopNative.tsx
│ └── vite-env.d.ts
├── playwright.config.ts
├── README.md
├── tools
│ ├── codefence
│ │ └── xmlui-code-fence-docs.md
│ ├── create-app
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── create-app.ts
│ │ ├── helpers
│ │ │ ├── copy.ts
│ │ │ ├── get-pkg-manager.ts
│ │ │ ├── git.ts
│ │ │ ├── install.ts
│ │ │ ├── is-folder-empty.ts
│ │ │ ├── is-writeable.ts
│ │ │ ├── make-dir.ts
│ │ │ └── validate-pkg.ts
│ │ ├── index.ts
│ │ ├── package.json
│ │ ├── templates
│ │ │ ├── default
│ │ │ │ └── ts
│ │ │ │ ├── gitignore
│ │ │ │ ├── index.html
│ │ │ │ ├── index.ts
│ │ │ │ ├── public
│ │ │ │ │ ├── mockServiceWorker.js
│ │ │ │ │ ├── resources
│ │ │ │ │ │ ├── favicon.ico
│ │ │ │ │ │ └── xmlui-logo.svg
│ │ │ │ │ └── serve.json
│ │ │ │ └── src
│ │ │ │ ├── components
│ │ │ │ │ ├── ApiAware.xmlui
│ │ │ │ │ ├── Home.xmlui
│ │ │ │ │ ├── IncButton.xmlui
│ │ │ │ │ └── PagePanel.xmlui
│ │ │ │ ├── config.ts
│ │ │ │ └── Main.xmlui
│ │ │ ├── index.ts
│ │ │ └── types.ts
│ │ └── tsconfig.json
│ ├── create-xmlui-hello-world
│ │ ├── index.js
│ │ └── package.json
│ └── vscode
│ ├── .gitignore
│ ├── .vscode
│ │ ├── launch.json
│ │ └── tasks.json
│ ├── .vscodeignore
│ ├── build.sh
│ ├── CHANGELOG.md
│ ├── esbuild.js
│ ├── eslint.config.mjs
│ ├── formatter-docs.md
│ ├── generate-test-sample.sh
│ ├── LICENSE.md
│ ├── package-lock.json
│ ├── package.json
│ ├── README.md
│ ├── resources
│ │ ├── xmlui-logo.png
│ │ └── xmlui-markup-syntax-highlighting.png
│ ├── src
│ │ ├── extension.ts
│ │ └── server.ts
│ ├── syntaxes
│ │ └── xmlui.tmLanguage.json
│ ├── test-samples
│ │ └── sample.xmlui
│ ├── tsconfig.json
│ └── tsconfig.tsbuildinfo
├── turbo.json
└── xmlui
├── .gitignore
├── bin
│ ├── bootstrap.cjs
│ ├── bootstrap.js
│ ├── build-lib.ts
│ ├── build.ts
│ ├── index.ts
│ ├── preview.ts
│ ├── start.ts
│ ├── vite-xmlui-plugin.ts
│ └── viteConfig.ts
├── CHANGELOG.md
├── conventions
│ ├── component-qa-checklist.md
│ ├── copilot-conventions.md
│ ├── create-xmlui-components.md
│ ├── mermaid.md
│ ├── testing-conventions.md
│ └── xmlui-in-a-nutshell.md
├── dev-docs
│ ├── accessibility.md
│ ├── build-system.md
│ ├── build-xmlui.md
│ ├── component-behaviors.md
│ ├── components-with-options.md
│ ├── containers.md
│ ├── data-operations.md
│ ├── glossary.md
│ ├── index.md
│ ├── next
│ │ ├── component-dev-guide.md
│ │ ├── configuration-management-enhancement-summary.md
│ │ ├── documentation-scripts-refactoring-complete-summary.md
│ │ ├── documentation-scripts-refactoring-plan.md
│ │ ├── duplicate-pattern-extraction-summary.md
│ │ ├── error-handling-standardization-summary.md
│ │ ├── generating-component-reference.md
│ │ ├── index.md
│ │ ├── logging-consistency-implementation-summary.md
│ │ ├── project-build.md
│ │ ├── project-structure.md
│ │ ├── theme-context.md
│ │ ├── tiptap-design-considerations.md
│ │ ├── working-with-code.md
│ │ ├── xmlui-runtime-architecture
│ │ └── xmlui-wcag-accessibility-report.md
│ ├── react-fundamentals.md
│ ├── release-method.md
│ ├── standalone-app.md
│ ├── ud-components.md
│ └── xmlui-repo.md
├── package.json
├── scripts
│ ├── coverage-only.js
│ ├── e2e-test-summary.js
│ ├── generate-docs
│ │ ├── build-downloads-map.mjs
│ │ ├── build-pages-map.mjs
│ │ ├── components-config.json
│ │ ├── configuration-management.mjs
│ │ ├── constants.mjs
│ │ ├── create-theme-files.mjs
│ │ ├── DocsGenerator.mjs
│ │ ├── error-handling.mjs
│ │ ├── extensions-config.json
│ │ ├── folders.mjs
│ │ ├── generate-summary-files.mjs
│ │ ├── get-docs.mjs
│ │ ├── input-handler.mjs
│ │ ├── logger.mjs
│ │ ├── logging-standards.mjs
│ │ ├── MetadataProcessor.mjs
│ │ ├── pattern-utilities.mjs
│ │ └── utils.mjs
│ ├── get-langserver-metadata.js
│ ├── inline-links.mjs
│ └── README-e2e-summary.md
├── src
│ ├── abstractions
│ │ ├── _conventions.md
│ │ ├── ActionDefs.ts
│ │ ├── AppContextDefs.ts
│ │ ├── ComponentDefs.ts
│ │ ├── ContainerDefs.ts
│ │ ├── ExtensionDefs.ts
│ │ ├── FunctionDefs.ts
│ │ ├── RendererDefs.ts
│ │ ├── scripting
│ │ │ ├── BlockScope.ts
│ │ │ ├── Compilation.ts
│ │ │ ├── LogicalThread.ts
│ │ │ ├── LoopScope.ts
│ │ │ ├── modules.ts
│ │ │ ├── ScriptParserError.ts
│ │ │ ├── Token.ts
│ │ │ ├── TryScope.ts
│ │ │ └── TryScopeExp.ts
│ │ └── ThemingDefs.ts
│ ├── components
│ │ ├── _conventions.md
│ │ ├── abstractions.ts
│ │ ├── Accordion
│ │ │ ├── Accordion.md
│ │ │ ├── Accordion.module.scss
│ │ │ ├── Accordion.spec.ts
│ │ │ ├── Accordion.tsx
│ │ │ ├── AccordionContext.tsx
│ │ │ ├── AccordionItem.tsx
│ │ │ ├── AccordionItemNative.tsx
│ │ │ └── AccordionNative.tsx
│ │ ├── Animation
│ │ │ └── AnimationNative.tsx
│ │ ├── APICall
│ │ │ ├── APICall.md
│ │ │ ├── APICall.spec.ts
│ │ │ ├── APICall.tsx
│ │ │ └── APICallNative.tsx
│ │ ├── App
│ │ │ ├── App.md
│ │ │ ├── App.module.scss
│ │ │ ├── App.spec.ts
│ │ │ ├── App.tsx
│ │ │ ├── AppLayoutContext.ts
│ │ │ ├── AppNative.tsx
│ │ │ ├── AppStateContext.ts
│ │ │ ├── doc-resources
│ │ │ │ ├── condensed-sticky.xmlui
│ │ │ │ ├── condensed.xmlui
│ │ │ │ ├── horizontal-sticky.xmlui
│ │ │ │ ├── horizontal.xmlui
│ │ │ │ ├── vertical-full-header.xmlui
│ │ │ │ ├── vertical-sticky.xmlui
│ │ │ │ └── vertical.xmlui
│ │ │ ├── IndexerContext.ts
│ │ │ ├── LinkInfoContext.ts
│ │ │ ├── SearchContext.tsx
│ │ │ ├── Sheet.module.scss
│ │ │ └── Sheet.tsx
│ │ ├── AppHeader
│ │ │ ├── AppHeader.md
│ │ │ ├── AppHeader.module.scss
│ │ │ ├── AppHeader.spec.ts
│ │ │ ├── AppHeader.tsx
│ │ │ └── AppHeaderNative.tsx
│ │ ├── AppState
│ │ │ ├── AppState.md
│ │ │ ├── AppState.spec.ts
│ │ │ ├── AppState.tsx
│ │ │ └── AppStateNative.tsx
│ │ ├── AutoComplete
│ │ │ ├── AutoComplete.md
│ │ │ ├── AutoComplete.module.scss
│ │ │ ├── AutoComplete.spec.ts
│ │ │ ├── AutoComplete.tsx
│ │ │ ├── AutoCompleteContext.tsx
│ │ │ └── AutoCompleteNative.tsx
│ │ ├── Avatar
│ │ │ ├── Avatar.md
│ │ │ ├── Avatar.module.scss
│ │ │ ├── Avatar.spec.ts
│ │ │ ├── Avatar.tsx
│ │ │ └── AvatarNative.tsx
│ │ ├── Backdrop
│ │ │ ├── Backdrop.md
│ │ │ ├── Backdrop.module.scss
│ │ │ ├── Backdrop.spec.ts
│ │ │ ├── Backdrop.tsx
│ │ │ └── BackdropNative.tsx
│ │ ├── Badge
│ │ │ ├── Badge.md
│ │ │ ├── Badge.module.scss
│ │ │ ├── Badge.spec.ts
│ │ │ ├── Badge.tsx
│ │ │ └── BadgeNative.tsx
│ │ ├── Bookmark
│ │ │ ├── Bookmark.md
│ │ │ ├── Bookmark.module.scss
│ │ │ ├── Bookmark.spec.ts
│ │ │ ├── Bookmark.tsx
│ │ │ └── BookmarkNative.tsx
│ │ ├── Breakout
│ │ │ ├── Breakout.module.scss
│ │ │ ├── Breakout.spec.ts
│ │ │ ├── Breakout.tsx
│ │ │ └── BreakoutNative.tsx
│ │ ├── Button
│ │ │ ├── Button-style.spec.ts
│ │ │ ├── Button.md
│ │ │ ├── Button.module.scss
│ │ │ ├── Button.spec.ts
│ │ │ ├── Button.tsx
│ │ │ └── ButtonNative.tsx
│ │ ├── Card
│ │ │ ├── Card.md
│ │ │ ├── Card.module.scss
│ │ │ ├── Card.spec.ts
│ │ │ ├── Card.tsx
│ │ │ └── CardNative.tsx
│ │ ├── Carousel
│ │ │ ├── Carousel.md
│ │ │ ├── Carousel.module.scss
│ │ │ ├── Carousel.spec.ts
│ │ │ ├── Carousel.tsx
│ │ │ ├── CarouselContext.tsx
│ │ │ ├── CarouselItem.tsx
│ │ │ ├── CarouselItemNative.tsx
│ │ │ └── CarouselNative.tsx
│ │ ├── ChangeListener
│ │ │ ├── ChangeListener.md
│ │ │ ├── ChangeListener.spec.ts
│ │ │ ├── ChangeListener.tsx
│ │ │ └── ChangeListenerNative.tsx
│ │ ├── chart-color-schemes.ts
│ │ ├── Charts
│ │ │ ├── AreaChart
│ │ │ │ ├── AreaChart.md
│ │ │ │ ├── AreaChart.spec.ts
│ │ │ │ ├── AreaChart.tsx
│ │ │ │ └── AreaChartNative.tsx
│ │ │ ├── BarChart
│ │ │ │ ├── BarChart.md
│ │ │ │ ├── BarChart.module.scss
│ │ │ │ ├── BarChart.spec.ts
│ │ │ │ ├── BarChart.tsx
│ │ │ │ └── BarChartNative.tsx
│ │ │ ├── DonutChart
│ │ │ │ ├── DonutChart.spec.ts
│ │ │ │ └── DonutChart.tsx
│ │ │ ├── LabelList
│ │ │ │ ├── LabelList.spec.ts
│ │ │ │ ├── LabelList.tsx
│ │ │ │ ├── LabelListNative.module.scss
│ │ │ │ └── LabelListNative.tsx
│ │ │ ├── Legend
│ │ │ │ ├── Legend.spec.ts
│ │ │ │ ├── Legend.tsx
│ │ │ │ └── LegendNative.tsx
│ │ │ ├── LineChart
│ │ │ │ ├── LineChart.md
│ │ │ │ ├── LineChart.module.scss
│ │ │ │ ├── LineChart.spec.ts
│ │ │ │ ├── LineChart.tsx
│ │ │ │ └── LineChartNative.tsx
│ │ │ ├── PieChart
│ │ │ │ ├── PieChart.md
│ │ │ │ ├── PieChart.spec.ts
│ │ │ │ ├── PieChart.tsx
│ │ │ │ ├── PieChartNative.module.scss
│ │ │ │ └── PieChartNative.tsx
│ │ │ ├── RadarChart
│ │ │ │ ├── RadarChart.md
│ │ │ │ ├── RadarChart.spec.ts
│ │ │ │ ├── RadarChart.tsx
│ │ │ │ └── RadarChartNative.tsx
│ │ │ ├── Tooltip
│ │ │ │ ├── TooltipContent.module.scss
│ │ │ │ ├── TooltipContent.spec.ts
│ │ │ │ └── TooltipContent.tsx
│ │ │ └── utils
│ │ │ ├── abstractions.ts
│ │ │ └── ChartProvider.tsx
│ │ ├── Checkbox
│ │ │ ├── Checkbox.md
│ │ │ ├── Checkbox.spec.ts
│ │ │ └── Checkbox.tsx
│ │ ├── CodeBlock
│ │ │ ├── CodeBlock.module.scss
│ │ │ ├── CodeBlock.spec.ts
│ │ │ ├── CodeBlock.tsx
│ │ │ ├── CodeBlockNative.tsx
│ │ │ └── highlight-code.ts
│ │ ├── collectedComponentMetadata.ts
│ │ ├── ColorPicker
│ │ │ ├── ColorPicker.md
│ │ │ ├── ColorPicker.module.scss
│ │ │ ├── ColorPicker.spec.ts
│ │ │ ├── ColorPicker.tsx
│ │ │ └── ColorPickerNative.tsx
│ │ ├── Column
│ │ │ ├── Column.md
│ │ │ ├── Column.tsx
│ │ │ ├── ColumnNative.tsx
│ │ │ ├── doc-resources
│ │ │ │ └── list-component-data.js
│ │ │ └── TableContext.tsx
│ │ ├── component-utils.ts
│ │ ├── ComponentProvider.tsx
│ │ ├── ComponentRegistryContext.tsx
│ │ ├── container-helpers.tsx
│ │ ├── ContentSeparator
│ │ │ ├── ContentSeparator.md
│ │ │ ├── ContentSeparator.module.scss
│ │ │ ├── ContentSeparator.spec.ts
│ │ │ ├── ContentSeparator.tsx
│ │ │ └── ContentSeparatorNative.tsx
│ │ ├── DataSource
│ │ │ ├── DataSource.md
│ │ │ └── DataSource.tsx
│ │ ├── DateInput
│ │ │ ├── DateInput.md
│ │ │ ├── DateInput.module.scss
│ │ │ ├── DateInput.spec.ts
│ │ │ ├── DateInput.tsx
│ │ │ └── DateInputNative.tsx
│ │ ├── DatePicker
│ │ │ ├── DatePicker.md
│ │ │ ├── DatePicker.module.scss
│ │ │ ├── DatePicker.spec.ts
│ │ │ ├── DatePicker.tsx
│ │ │ └── DatePickerNative.tsx
│ │ ├── DropdownMenu
│ │ │ ├── DropdownMenu.md
│ │ │ ├── DropdownMenu.module.scss
│ │ │ ├── DropdownMenu.spec.ts
│ │ │ ├── DropdownMenu.tsx
│ │ │ ├── DropdownMenuNative.tsx
│ │ │ ├── MenuItem.md
│ │ │ └── SubMenuItem.md
│ │ ├── EmojiSelector
│ │ │ ├── EmojiSelector.md
│ │ │ ├── EmojiSelector.spec.ts
│ │ │ ├── EmojiSelector.tsx
│ │ │ └── EmojiSelectorNative.tsx
│ │ ├── ExpandableItem
│ │ │ ├── ExpandableItem.module.scss
│ │ │ ├── ExpandableItem.spec.ts
│ │ │ ├── ExpandableItem.tsx
│ │ │ └── ExpandableItemNative.tsx
│ │ ├── FileInput
│ │ │ ├── FileInput.md
│ │ │ ├── FileInput.module.scss
│ │ │ ├── FileInput.spec.ts
│ │ │ ├── FileInput.tsx
│ │ │ └── FileInputNative.tsx
│ │ ├── FileUploadDropZone
│ │ │ ├── FileUploadDropZone.md
│ │ │ ├── FileUploadDropZone.module.scss
│ │ │ ├── FileUploadDropZone.spec.ts
│ │ │ ├── FileUploadDropZone.tsx
│ │ │ └── FileUploadDropZoneNative.tsx
│ │ ├── FlowLayout
│ │ │ ├── FlowLayout.md
│ │ │ ├── FlowLayout.module.scss
│ │ │ ├── FlowLayout.spec.ts
│ │ │ ├── FlowLayout.spec.ts-snapshots
│ │ │ │ └── Edge-cases-boxShadow-is-not-clipped-1-non-smoke-darwin.png
│ │ │ ├── FlowLayout.tsx
│ │ │ └── FlowLayoutNative.tsx
│ │ ├── Footer
│ │ │ ├── Footer.md
│ │ │ ├── Footer.module.scss
│ │ │ ├── Footer.spec.ts
│ │ │ ├── Footer.tsx
│ │ │ └── FooterNative.tsx
│ │ ├── Form
│ │ │ ├── Form.md
│ │ │ ├── Form.module.scss
│ │ │ ├── Form.spec.ts
│ │ │ ├── Form.tsx
│ │ │ ├── formActions.ts
│ │ │ ├── FormContext.ts
│ │ │ └── FormNative.tsx
│ │ ├── FormItem
│ │ │ ├── FormItem.md
│ │ │ ├── FormItem.module.scss
│ │ │ ├── FormItem.spec.ts
│ │ │ ├── FormItem.tsx
│ │ │ ├── FormItemNative.tsx
│ │ │ ├── HelperText.module.scss
│ │ │ ├── HelperText.tsx
│ │ │ ├── ItemWithLabel.tsx
│ │ │ └── Validations.ts
│ │ ├── FormSection
│ │ │ ├── FormSection.md
│ │ │ ├── FormSection.ts
│ │ │ └── FormSection.xmlui
│ │ ├── Fragment
│ │ │ ├── Fragment.spec.ts
│ │ │ └── Fragment.tsx
│ │ ├── Heading
│ │ │ ├── abstractions.ts
│ │ │ ├── H1.md
│ │ │ ├── H1.spec.ts
│ │ │ ├── H2.md
│ │ │ ├── H2.spec.ts
│ │ │ ├── H3.md
│ │ │ ├── H3.spec.ts
│ │ │ ├── H4.md
│ │ │ ├── H4.spec.ts
│ │ │ ├── H5.md
│ │ │ ├── H5.spec.ts
│ │ │ ├── H6.md
│ │ │ ├── H6.spec.ts
│ │ │ ├── Heading.md
│ │ │ ├── Heading.module.scss
│ │ │ ├── Heading.spec.ts
│ │ │ ├── Heading.tsx
│ │ │ └── HeadingNative.tsx
│ │ ├── HoverCard
│ │ │ ├── HoverCard.tsx
│ │ │ └── HovercardNative.tsx
│ │ ├── HtmlTags
│ │ │ ├── HtmlTags.module.scss
│ │ │ ├── HtmlTags.spec.ts
│ │ │ └── HtmlTags.tsx
│ │ ├── Icon
│ │ │ ├── AdmonitionDanger.tsx
│ │ │ ├── AdmonitionInfo.tsx
│ │ │ ├── AdmonitionNote.tsx
│ │ │ ├── AdmonitionTip.tsx
│ │ │ ├── AdmonitionWarning.tsx
│ │ │ ├── ApiIcon.tsx
│ │ │ ├── ArrowDropDown.module.scss
│ │ │ ├── ArrowDropDown.tsx
│ │ │ ├── ArrowDropUp.module.scss
│ │ │ ├── ArrowDropUp.tsx
│ │ │ ├── ArrowLeft.module.scss
│ │ │ ├── ArrowLeft.tsx
│ │ │ ├── ArrowRight.module.scss
│ │ │ ├── ArrowRight.tsx
│ │ │ ├── Attach.tsx
│ │ │ ├── Binding.module.scss
│ │ │ ├── Binding.tsx
│ │ │ ├── BoardIcon.tsx
│ │ │ ├── BoxIcon.tsx
│ │ │ ├── CheckIcon.tsx
│ │ │ ├── ChevronDownIcon.tsx
│ │ │ ├── ChevronLeft.tsx
│ │ │ ├── ChevronRight.tsx
│ │ │ ├── ChevronUpIcon.tsx
│ │ │ ├── CodeFileIcon.tsx
│ │ │ ├── CodeSandbox.tsx
│ │ │ ├── CompactListIcon.tsx
│ │ │ ├── ContentCopyIcon.tsx
│ │ │ ├── DarkToLightIcon.tsx
│ │ │ ├── DatabaseIcon.module.scss
│ │ │ ├── DatabaseIcon.tsx
│ │ │ ├── DocFileIcon.tsx
│ │ │ ├── DocIcon.tsx
│ │ │ ├── DotMenuHorizontalIcon.tsx
│ │ │ ├── DotMenuIcon.tsx
│ │ │ ├── EmailIcon.tsx
│ │ │ ├── EmptyFolderIcon.tsx
│ │ │ ├── ErrorIcon.tsx
│ │ │ ├── ExpressionIcon.tsx
│ │ │ ├── FillPlusCricleIcon.tsx
│ │ │ ├── FilterIcon.tsx
│ │ │ ├── FolderIcon.tsx
│ │ │ ├── GlobeIcon.tsx
│ │ │ ├── HomeIcon.tsx
│ │ │ ├── HyperLinkIcon.tsx
│ │ │ ├── Icon.md
│ │ │ ├── Icon.module.scss
│ │ │ ├── Icon.spec.ts
│ │ │ ├── Icon.tsx
│ │ │ ├── IconNative.tsx
│ │ │ ├── ImageFileIcon.tsx
│ │ │ ├── Inspect.tsx
│ │ │ ├── LightToDark.tsx
│ │ │ ├── LinkIcon.tsx
│ │ │ ├── ListIcon.tsx
│ │ │ ├── LooseListIcon.tsx
│ │ │ ├── MoonIcon.tsx
│ │ │ ├── MoreOptionsIcon.tsx
│ │ │ ├── NoSortIcon.tsx
│ │ │ ├── PDFIcon.tsx
│ │ │ ├── PenIcon.tsx
│ │ │ ├── PhoneIcon.tsx
│ │ │ ├── PhotoIcon.tsx
│ │ │ ├── PlusIcon.tsx
│ │ │ ├── SearchIcon.tsx
│ │ │ ├── ShareIcon.tsx
│ │ │ ├── SortAscendingIcon.tsx
│ │ │ ├── SortDescendingIcon.tsx
│ │ │ ├── StarsIcon.tsx
│ │ │ ├── SunIcon.tsx
│ │ │ ├── svg
│ │ │ │ ├── admonition_danger.svg
│ │ │ │ ├── admonition_info.svg
│ │ │ │ ├── admonition_note.svg
│ │ │ │ ├── admonition_tip.svg
│ │ │ │ ├── admonition_warning.svg
│ │ │ │ ├── api.svg
│ │ │ │ ├── arrow-dropdown.svg
│ │ │ │ ├── arrow-left.svg
│ │ │ │ ├── arrow-right.svg
│ │ │ │ ├── arrow-up.svg
│ │ │ │ ├── attach.svg
│ │ │ │ ├── binding.svg
│ │ │ │ ├── box.svg
│ │ │ │ ├── bulb.svg
│ │ │ │ ├── code-file.svg
│ │ │ │ ├── code-sandbox.svg
│ │ │ │ ├── dark_to_light.svg
│ │ │ │ ├── database.svg
│ │ │ │ ├── doc.svg
│ │ │ │ ├── empty-folder.svg
│ │ │ │ ├── expression.svg
│ │ │ │ ├── eye-closed.svg
│ │ │ │ ├── eye-dark.svg
│ │ │ │ ├── eye.svg
│ │ │ │ ├── file-text.svg
│ │ │ │ ├── filter.svg
│ │ │ │ ├── folder.svg
│ │ │ │ ├── img.svg
│ │ │ │ ├── inspect.svg
│ │ │ │ ├── light_to_dark.svg
│ │ │ │ ├── moon.svg
│ │ │ │ ├── pdf.svg
│ │ │ │ ├── photo.svg
│ │ │ │ ├── share.svg
│ │ │ │ ├── stars.svg
│ │ │ │ ├── sun.svg
│ │ │ │ ├── trending-down.svg
│ │ │ │ ├── trending-level.svg
│ │ │ │ ├── trending-up.svg
│ │ │ │ ├── txt.svg
│ │ │ │ ├── unknown-file.svg
│ │ │ │ ├── unlink.svg
│ │ │ │ └── xls.svg
│ │ │ ├── TableDeleteColumnIcon.tsx
│ │ │ ├── TableDeleteRowIcon.tsx
│ │ │ ├── TableInsertColumnIcon.tsx
│ │ │ ├── TableInsertRowIcon.tsx
│ │ │ ├── TrashIcon.tsx
│ │ │ ├── TrendingDownIcon.tsx
│ │ │ ├── TrendingLevelIcon.tsx
│ │ │ ├── TrendingUpIcon.tsx
│ │ │ ├── TxtIcon.tsx
│ │ │ ├── UnknownFileIcon.tsx
│ │ │ ├── UnlinkIcon.tsx
│ │ │ ├── UserIcon.tsx
│ │ │ ├── WarningIcon.tsx
│ │ │ └── XlsIcon.tsx
│ │ ├── IconProvider.tsx
│ │ ├── IconRegistryContext.tsx
│ │ ├── IFrame
│ │ │ ├── IFrame.md
│ │ │ ├── IFrame.module.scss
│ │ │ ├── IFrame.spec.ts
│ │ │ ├── IFrame.tsx
│ │ │ └── IFrameNative.tsx
│ │ ├── Image
│ │ │ ├── Image.md
│ │ │ ├── Image.module.scss
│ │ │ ├── Image.spec.ts
│ │ │ ├── Image.tsx
│ │ │ └── ImageNative.tsx
│ │ ├── Input
│ │ │ ├── index.ts
│ │ │ ├── InputAdornment.module.scss
│ │ │ ├── InputAdornment.tsx
│ │ │ ├── InputDivider.module.scss
│ │ │ ├── InputDivider.tsx
│ │ │ ├── InputLabel.module.scss
│ │ │ ├── InputLabel.tsx
│ │ │ ├── PartialInput.module.scss
│ │ │ └── PartialInput.tsx
│ │ ├── InspectButton
│ │ │ ├── InspectButton.module.scss
│ │ │ └── InspectButton.tsx
│ │ ├── Items
│ │ │ ├── Items.md
│ │ │ ├── Items.spec.ts
│ │ │ ├── Items.tsx
│ │ │ └── ItemsNative.tsx
│ │ ├── Link
│ │ │ ├── Link.md
│ │ │ ├── Link.module.scss
│ │ │ ├── Link.spec.ts
│ │ │ ├── Link.tsx
│ │ │ └── LinkNative.tsx
│ │ ├── List
│ │ │ ├── doc-resources
│ │ │ │ └── list-component-data.js
│ │ │ ├── List.md
│ │ │ ├── List.module.scss
│ │ │ ├── List.spec.ts
│ │ │ ├── List.tsx
│ │ │ └── ListNative.tsx
│ │ ├── Logo
│ │ │ ├── doc-resources
│ │ │ │ └── xmlui-logo.svg
│ │ │ ├── Logo.md
│ │ │ ├── Logo.tsx
│ │ │ └── LogoNative.tsx
│ │ ├── Markdown
│ │ │ ├── CodeText.module.scss
│ │ │ ├── CodeText.tsx
│ │ │ ├── Markdown.md
│ │ │ ├── Markdown.module.scss
│ │ │ ├── Markdown.spec.ts
│ │ │ ├── Markdown.tsx
│ │ │ ├── MarkdownNative.tsx
│ │ │ ├── parse-binding-expr.ts
│ │ │ └── utils.ts
│ │ ├── metadata-helpers.ts
│ │ ├── ModalDialog
│ │ │ ├── ConfirmationModalContextProvider.tsx
│ │ │ ├── Dialog.module.scss
│ │ │ ├── Dialog.tsx
│ │ │ ├── ModalDialog.md
│ │ │ ├── ModalDialog.module.scss
│ │ │ ├── ModalDialog.spec.ts
│ │ │ ├── ModalDialog.tsx
│ │ │ ├── ModalDialogNative.tsx
│ │ │ └── ModalVisibilityContext.tsx
│ │ ├── NavGroup
│ │ │ ├── NavGroup.md
│ │ │ ├── NavGroup.module.scss
│ │ │ ├── NavGroup.spec.ts
│ │ │ ├── NavGroup.tsx
│ │ │ ├── NavGroupContext.ts
│ │ │ └── NavGroupNative.tsx
│ │ ├── NavLink
│ │ │ ├── NavLink.md
│ │ │ ├── NavLink.module.scss
│ │ │ ├── NavLink.spec.ts
│ │ │ ├── NavLink.tsx
│ │ │ └── NavLinkNative.tsx
│ │ ├── NavPanel
│ │ │ ├── NavPanel.md
│ │ │ ├── NavPanel.module.scss
│ │ │ ├── NavPanel.spec.ts
│ │ │ ├── NavPanel.tsx
│ │ │ └── NavPanelNative.tsx
│ │ ├── NestedApp
│ │ │ ├── AppWithCodeView.module.scss
│ │ │ ├── AppWithCodeView.tsx
│ │ │ ├── AppWithCodeViewNative.tsx
│ │ │ ├── defaultProps.tsx
│ │ │ ├── logo.svg
│ │ │ ├── NestedApp.module.scss
│ │ │ ├── NestedApp.tsx
│ │ │ ├── NestedAppNative.tsx
│ │ │ ├── Tooltip.module.scss
│ │ │ ├── Tooltip.tsx
│ │ │ └── utils.ts
│ │ ├── NoResult
│ │ │ ├── NoResult.md
│ │ │ ├── NoResult.module.scss
│ │ │ ├── NoResult.spec.ts
│ │ │ ├── NoResult.tsx
│ │ │ └── NoResultNative.tsx
│ │ ├── NumberBox
│ │ │ ├── numberbox-abstractions.ts
│ │ │ ├── NumberBox.md
│ │ │ ├── NumberBox.module.scss
│ │ │ ├── NumberBox.spec.ts
│ │ │ ├── NumberBox.tsx
│ │ │ └── NumberBoxNative.tsx
│ │ ├── Option
│ │ │ ├── Option.md
│ │ │ ├── Option.spec.ts
│ │ │ ├── Option.tsx
│ │ │ ├── OptionNative.tsx
│ │ │ └── OptionTypeProvider.tsx
│ │ ├── PageMetaTitle
│ │ │ ├── PageMetaTilteNative.tsx
│ │ │ ├── PageMetaTitle.md
│ │ │ ├── PageMetaTitle.spec.ts
│ │ │ └── PageMetaTitle.tsx
│ │ ├── Pages
│ │ │ ├── Page.md
│ │ │ ├── Pages.md
│ │ │ ├── Pages.module.scss
│ │ │ ├── Pages.tsx
│ │ │ └── PagesNative.tsx
│ │ ├── Pagination
│ │ │ ├── Pagination.md
│ │ │ ├── Pagination.module.scss
│ │ │ ├── Pagination.spec.ts
│ │ │ ├── Pagination.tsx
│ │ │ └── PaginationNative.tsx
│ │ ├── PositionedContainer
│ │ │ ├── PositionedContainer.module.scss
│ │ │ ├── PositionedContainer.tsx
│ │ │ └── PositionedContainerNative.tsx
│ │ ├── ProfileMenu
│ │ │ ├── ProfileMenu.module.scss
│ │ │ └── ProfileMenu.tsx
│ │ ├── ProgressBar
│ │ │ ├── ProgressBar.md
│ │ │ ├── ProgressBar.module.scss
│ │ │ ├── ProgressBar.spec.ts
│ │ │ ├── ProgressBar.tsx
│ │ │ └── ProgressBarNative.tsx
│ │ ├── Queue
│ │ │ ├── Queue.md
│ │ │ ├── Queue.spec.ts
│ │ │ ├── Queue.tsx
│ │ │ ├── queueActions.ts
│ │ │ └── QueueNative.tsx
│ │ ├── RadioGroup
│ │ │ ├── RadioGroup.md
│ │ │ ├── RadioGroup.module.scss
│ │ │ ├── RadioGroup.spec.ts
│ │ │ ├── RadioGroup.tsx
│ │ │ ├── RadioGroupNative.tsx
│ │ │ ├── RadioItem.tsx
│ │ │ └── RadioItemNative.tsx
│ │ ├── RealTimeAdapter
│ │ │ ├── RealTimeAdapter.tsx
│ │ │ └── RealTimeAdapterNative.tsx
│ │ ├── Redirect
│ │ │ ├── Redirect.md
│ │ │ ├── Redirect.spec.ts
│ │ │ └── Redirect.tsx
│ │ ├── ResponsiveBar
│ │ │ ├── README.md
│ │ │ ├── ResponsiveBar.md
│ │ │ ├── ResponsiveBar.module.scss
│ │ │ ├── ResponsiveBar.spec.ts
│ │ │ ├── ResponsiveBar.tsx
│ │ │ └── ResponsiveBarNative.tsx
│ │ ├── Select
│ │ │ ├── HiddenOption.tsx
│ │ │ ├── OptionContext.ts
│ │ │ ├── Select.md
│ │ │ ├── Select.module.scss
│ │ │ ├── Select.spec.ts
│ │ │ ├── Select.tsx
│ │ │ ├── SelectContext.tsx
│ │ │ └── SelectNative.tsx
│ │ ├── SelectionStore
│ │ │ ├── SelectionStore.md
│ │ │ ├── SelectionStore.tsx
│ │ │ └── SelectionStoreNative.tsx
│ │ ├── Slider
│ │ │ ├── Slider.md
│ │ │ ├── Slider.module.scss
│ │ │ ├── Slider.spec.ts
│ │ │ ├── Slider.tsx
│ │ │ └── SliderNative.tsx
│ │ ├── Slot
│ │ │ ├── Slot.md
│ │ │ ├── Slot.spec.ts
│ │ │ └── Slot.ts
│ │ ├── SlotItem.tsx
│ │ ├── SpaceFiller
│ │ │ ├── SpaceFiller.md
│ │ │ ├── SpaceFiller.module.scss
│ │ │ ├── SpaceFiller.spec.ts
│ │ │ ├── SpaceFiller.tsx
│ │ │ └── SpaceFillerNative.tsx
│ │ ├── Spinner
│ │ │ ├── Spinner.md
│ │ │ ├── Spinner.module.scss
│ │ │ ├── Spinner.spec.ts
│ │ │ ├── Spinner.tsx
│ │ │ └── SpinnerNative.tsx
│ │ ├── Splitter
│ │ │ ├── HSplitter.md
│ │ │ ├── HSplitter.spec.ts
│ │ │ ├── Splitter.md
│ │ │ ├── Splitter.module.scss
│ │ │ ├── Splitter.spec.ts
│ │ │ ├── Splitter.tsx
│ │ │ ├── SplitterNative.tsx
│ │ │ ├── utils.ts
│ │ │ ├── VSplitter.md
│ │ │ └── VSplitter.spec.ts
│ │ ├── Stack
│ │ │ ├── CHStack.md
│ │ │ ├── CHStack.spec.ts
│ │ │ ├── CVStack.md
│ │ │ ├── CVStack.spec.ts
│ │ │ ├── HStack.md
│ │ │ ├── HStack.spec.ts
│ │ │ ├── Stack.md
│ │ │ ├── Stack.module.scss
│ │ │ ├── Stack.spec.ts
│ │ │ ├── Stack.tsx
│ │ │ ├── StackNative.tsx
│ │ │ ├── VStack.md
│ │ │ └── VStack.spec.ts
│ │ ├── StickyBox
│ │ │ ├── StickyBox.md
│ │ │ ├── StickyBox.module.scss
│ │ │ ├── StickyBox.tsx
│ │ │ └── StickyBoxNative.tsx
│ │ ├── Switch
│ │ │ ├── Switch.md
│ │ │ ├── Switch.spec.ts
│ │ │ └── Switch.tsx
│ │ ├── Table
│ │ │ ├── doc-resources
│ │ │ │ └── list-component-data.js
│ │ │ ├── react-table-config.d.ts
│ │ │ ├── Table.md
│ │ │ ├── Table.module.scss
│ │ │ ├── Table.spec.ts
│ │ │ ├── Table.tsx
│ │ │ ├── TableNative.tsx
│ │ │ └── useRowSelection.tsx
│ │ ├── TableOfContents
│ │ │ ├── TableOfContents.module.scss
│ │ │ ├── TableOfContents.spec.ts
│ │ │ ├── TableOfContents.tsx
│ │ │ └── TableOfContentsNative.tsx
│ │ ├── Tabs
│ │ │ ├── TabContext.tsx
│ │ │ ├── TabItem.md
│ │ │ ├── TabItem.tsx
│ │ │ ├── TabItemNative.tsx
│ │ │ ├── Tabs.md
│ │ │ ├── Tabs.module.scss
│ │ │ ├── Tabs.spec.ts
│ │ │ ├── Tabs.tsx
│ │ │ └── TabsNative.tsx
│ │ ├── Text
│ │ │ ├── Text.md
│ │ │ ├── Text.module.scss
│ │ │ ├── Text.spec.ts
│ │ │ ├── Text.tsx
│ │ │ └── TextNative.tsx
│ │ ├── TextArea
│ │ │ ├── TextArea.md
│ │ │ ├── TextArea.module.scss
│ │ │ ├── TextArea.spec.ts
│ │ │ ├── TextArea.tsx
│ │ │ ├── TextAreaNative.tsx
│ │ │ ├── TextAreaResizable.tsx
│ │ │ └── useComposedRef.ts
│ │ ├── TextBox
│ │ │ ├── TextBox.md
│ │ │ ├── TextBox.module.scss
│ │ │ ├── TextBox.spec.ts
│ │ │ ├── TextBox.tsx
│ │ │ └── TextBoxNative.tsx
│ │ ├── Theme
│ │ │ ├── NotificationToast.tsx
│ │ │ ├── Theme.md
│ │ │ ├── Theme.module.scss
│ │ │ ├── Theme.spec.ts
│ │ │ ├── Theme.tsx
│ │ │ └── ThemeNative.tsx
│ │ ├── TimeInput
│ │ │ ├── TimeInput.md
│ │ │ ├── TimeInput.module.scss
│ │ │ ├── TimeInput.spec.ts
│ │ │ ├── TimeInput.tsx
│ │ │ ├── TimeInputNative.tsx
│ │ │ └── utils.ts
│ │ ├── Timer
│ │ │ ├── Timer.md
│ │ │ ├── Timer.spec.ts
│ │ │ ├── Timer.tsx
│ │ │ └── TimerNative.tsx
│ │ ├── Toggle
│ │ │ ├── Toggle.module.scss
│ │ │ └── Toggle.tsx
│ │ ├── ToneChangerButton
│ │ │ ├── ToneChangerButton.md
│ │ │ ├── ToneChangerButton.spec.ts
│ │ │ └── ToneChangerButton.tsx
│ │ ├── ToneSwitch
│ │ │ ├── ToneSwitch.md
│ │ │ ├── ToneSwitch.module.scss
│ │ │ ├── ToneSwitch.spec.ts
│ │ │ ├── ToneSwitch.tsx
│ │ │ └── ToneSwitchNative.tsx
│ │ ├── Tooltip
│ │ │ ├── Tooltip.md
│ │ │ ├── Tooltip.module.scss
│ │ │ ├── Tooltip.spec.ts
│ │ │ ├── Tooltip.tsx
│ │ │ └── TooltipNative.tsx
│ │ ├── Tree
│ │ │ ├── testData.ts
│ │ │ ├── Tree-dynamic.spec.ts
│ │ │ ├── Tree-icons.spec.ts
│ │ │ ├── Tree.md
│ │ │ ├── Tree.spec.ts
│ │ │ ├── TreeComponent.module.scss
│ │ │ ├── TreeComponent.tsx
│ │ │ └── TreeNative.tsx
│ │ ├── TreeDisplay
│ │ │ ├── TreeDisplay.md
│ │ │ ├── TreeDisplay.module.scss
│ │ │ ├── TreeDisplay.tsx
│ │ │ └── TreeDisplayNative.tsx
│ │ ├── ValidationSummary
│ │ │ ├── ValidationSummary.module.scss
│ │ │ └── ValidationSummary.tsx
│ │ └── VisuallyHidden.tsx
│ ├── components-core
│ │ ├── abstractions
│ │ │ ├── ComponentRenderer.ts
│ │ │ ├── LoaderRenderer.ts
│ │ │ ├── standalone.ts
│ │ │ └── treeAbstractions.ts
│ │ ├── action
│ │ │ ├── actions.ts
│ │ │ ├── APICall.tsx
│ │ │ ├── FileDownloadAction.tsx
│ │ │ ├── FileUploadAction.tsx
│ │ │ ├── NavigateAction.tsx
│ │ │ └── TimedAction.tsx
│ │ ├── ApiBoundComponent.tsx
│ │ ├── appContext
│ │ │ ├── date-functions.ts
│ │ │ ├── math-function.ts
│ │ │ └── misc-utils.ts
│ │ ├── AppContext.tsx
│ │ ├── behaviors
│ │ │ ├── Behavior.tsx
│ │ │ └── CoreBehaviors.tsx
│ │ ├── component-hooks.ts
│ │ ├── ComponentDecorator.tsx
│ │ ├── ComponentViewer.tsx
│ │ ├── CompoundComponent.tsx
│ │ ├── constants.ts
│ │ ├── DebugViewProvider.tsx
│ │ ├── descriptorHelper.ts
│ │ ├── devtools
│ │ │ ├── InspectorDialog.module.scss
│ │ │ ├── InspectorDialog.tsx
│ │ │ └── InspectorDialogVisibilityContext.tsx
│ │ ├── EngineError.ts
│ │ ├── event-handlers.ts
│ │ ├── InspectorButton.module.scss
│ │ ├── InspectorContext.tsx
│ │ ├── interception
│ │ │ ├── abstractions.ts
│ │ │ ├── ApiInterceptor.ts
│ │ │ ├── ApiInterceptorProvider.tsx
│ │ │ ├── apiInterceptorWorker.ts
│ │ │ ├── Backend.ts
│ │ │ ├── Errors.ts
│ │ │ ├── IndexedDb.ts
│ │ │ ├── initMock.ts
│ │ │ ├── InMemoryDb.ts
│ │ │ ├── ReadonlyCollection.ts
│ │ │ └── useApiInterceptorContext.tsx
│ │ ├── loader
│ │ │ ├── ApiLoader.tsx
│ │ │ ├── DataLoader.tsx
│ │ │ ├── ExternalDataLoader.tsx
│ │ │ ├── Loader.tsx
│ │ │ ├── MockLoaderRenderer.tsx
│ │ │ └── PageableLoader.tsx
│ │ ├── LoaderComponent.tsx
│ │ ├── markup-check.ts
│ │ ├── parts.ts
│ │ ├── renderers.ts
│ │ ├── rendering
│ │ │ ├── AppContent.tsx
│ │ │ ├── AppRoot.tsx
│ │ │ ├── AppWrapper.tsx
│ │ │ ├── buildProxy.ts
│ │ │ ├── collectFnVarDeps.ts
│ │ │ ├── ComponentAdapter.tsx
│ │ │ ├── ComponentWrapper.tsx
│ │ │ ├── Container.tsx
│ │ │ ├── containers.ts
│ │ │ ├── ContainerWrapper.tsx
│ │ │ ├── ErrorBoundary.module.scss
│ │ │ ├── ErrorBoundary.tsx
│ │ │ ├── InvalidComponent.module.scss
│ │ │ ├── InvalidComponent.tsx
│ │ │ ├── nodeUtils.ts
│ │ │ ├── reducer.ts
│ │ │ ├── renderChild.tsx
│ │ │ ├── StandaloneComponent.tsx
│ │ │ ├── StateContainer.tsx
│ │ │ ├── UnknownComponent.module.scss
│ │ │ ├── UnknownComponent.tsx
│ │ │ └── valueExtractor.ts
│ │ ├── reportEngineError.ts
│ │ ├── RestApiProxy.ts
│ │ ├── script-runner
│ │ │ ├── asyncProxy.ts
│ │ │ ├── AttributeValueParser.ts
│ │ │ ├── bannedFunctions.ts
│ │ │ ├── BindingTreeEvaluationContext.ts
│ │ │ ├── eval-tree-async.ts
│ │ │ ├── eval-tree-common.ts
│ │ │ ├── eval-tree-sync.ts
│ │ │ ├── ParameterParser.ts
│ │ │ ├── process-statement-async.ts
│ │ │ ├── process-statement-common.ts
│ │ │ ├── process-statement-sync.ts
│ │ │ ├── ScriptingSourceTree.ts
│ │ │ ├── simplify-expression.ts
│ │ │ ├── statement-queue.ts
│ │ │ └── visitors.ts
│ │ ├── StandaloneApp.tsx
│ │ ├── StandaloneExtensionManager.ts
│ │ ├── TableOfContentsContext.tsx
│ │ ├── theming
│ │ │ ├── _themes.scss
│ │ │ ├── component-layout-resolver.ts
│ │ │ ├── extendThemeUtils.ts
│ │ │ ├── hvar.ts
│ │ │ ├── layout-resolver.ts
│ │ │ ├── parse-layout-props.ts
│ │ │ ├── StyleContext.tsx
│ │ │ ├── StyleRegistry.ts
│ │ │ ├── ThemeContext.tsx
│ │ │ ├── ThemeProvider.tsx
│ │ │ ├── themes
│ │ │ │ ├── base-utils.ts
│ │ │ │ ├── palette.ts
│ │ │ │ ├── root.ts
│ │ │ │ ├── solid.ts
│ │ │ │ ├── theme-colors.ts
│ │ │ │ └── xmlui.ts
│ │ │ ├── themeVars.module.scss
│ │ │ ├── themeVars.ts
│ │ │ ├── transformThemeVars.ts
│ │ │ └── utils.ts
│ │ ├── utils
│ │ │ ├── actionUtils.ts
│ │ │ ├── audio-utils.ts
│ │ │ ├── base64-utils.ts
│ │ │ ├── compound-utils.ts
│ │ │ ├── css-utils.ts
│ │ │ ├── DataLoaderQueryKeyGenerator.ts
│ │ │ ├── date-utils.ts
│ │ │ ├── extractParam.ts
│ │ │ ├── hooks.tsx
│ │ │ ├── LruCache.ts
│ │ │ ├── mergeProps.ts
│ │ │ ├── misc.ts
│ │ │ ├── request-params.ts
│ │ │ ├── statementUtils.ts
│ │ │ └── treeUtils.ts
│ │ └── xmlui-parser.ts
│ ├── index-standalone.ts
│ ├── index.scss
│ ├── index.ts
│ ├── language-server
│ │ ├── server-common.ts
│ │ ├── server-web-worker.ts
│ │ ├── server.ts
│ │ ├── services
│ │ │ ├── common
│ │ │ │ ├── docs-generation.ts
│ │ │ │ ├── lsp-utils.ts
│ │ │ │ ├── metadata-utils.ts
│ │ │ │ └── syntax-node-utilities.ts
│ │ │ ├── completion.ts
│ │ │ ├── diagnostic.ts
│ │ │ ├── format.ts
│ │ │ └── hover.ts
│ │ └── xmlui-metadata-generated.js
│ ├── logging
│ │ ├── LoggerContext.tsx
│ │ ├── LoggerInitializer.tsx
│ │ ├── LoggerService.ts
│ │ └── xmlui.ts
│ ├── logo.svg
│ ├── parsers
│ │ ├── common
│ │ │ ├── GenericToken.ts
│ │ │ ├── InputStream.ts
│ │ │ └── utils.ts
│ │ ├── scripting
│ │ │ ├── code-behind-collect.ts
│ │ │ ├── Lexer.ts
│ │ │ ├── modules.ts
│ │ │ ├── Parser.ts
│ │ │ ├── ParserError.ts
│ │ │ ├── ScriptingNodeTypes.ts
│ │ │ ├── TokenTrait.ts
│ │ │ ├── TokenType.ts
│ │ │ └── tree-visitor.ts
│ │ ├── style-parser
│ │ │ ├── errors.ts
│ │ │ ├── source-tree.ts
│ │ │ ├── StyleInputStream.ts
│ │ │ ├── StyleLexer.ts
│ │ │ ├── StyleParser.ts
│ │ │ └── tokens.ts
│ │ └── xmlui-parser
│ │ ├── CharacterCodes.ts
│ │ ├── diagnostics.ts
│ │ ├── fileExtensions.ts
│ │ ├── index.ts
│ │ ├── lint.ts
│ │ ├── parser.ts
│ │ ├── ParserError.ts
│ │ ├── scanner.ts
│ │ ├── syntax-kind.ts
│ │ ├── syntax-node.ts
│ │ ├── transform.ts
│ │ ├── utils.ts
│ │ ├── xmlui-serializer.ts
│ │ └── xmlui-tree.ts
│ ├── react-app-env.d.ts
│ ├── syntax
│ │ ├── monaco
│ │ │ ├── grammar.monacoLanguage.ts
│ │ │ ├── index.ts
│ │ │ ├── xmlui-dark.ts
│ │ │ ├── xmlui-light.ts
│ │ │ └── xmluiscript.monacoLanguage.ts
│ │ └── textMate
│ │ ├── index.ts
│ │ ├── xmlui-dark.json
│ │ ├── xmlui-light.json
│ │ ├── xmlui.json
│ │ └── xmlui.tmLanguage.json
│ ├── testing
│ │ ├── assertions.ts
│ │ ├── component-test-helpers.ts
│ │ ├── ComponentDrivers.ts
│ │ ├── drivers
│ │ │ ├── DateInputDriver.ts
│ │ │ ├── index.ts
│ │ │ ├── ModalDialogDriver.ts
│ │ │ ├── NumberBoxDriver.ts
│ │ │ ├── TextBoxDriver.ts
│ │ │ ├── TimeInputDriver.ts
│ │ │ ├── TimerDriver.ts
│ │ │ └── TreeDriver.ts
│ │ ├── fixtures.ts
│ │ ├── index.ts
│ │ ├── infrastructure
│ │ │ ├── index.html
│ │ │ ├── main.tsx
│ │ │ ├── public
│ │ │ │ ├── mockServiceWorker.js
│ │ │ │ ├── resources
│ │ │ │ │ ├── bell.svg
│ │ │ │ │ ├── box.svg
│ │ │ │ │ ├── doc.svg
│ │ │ │ │ ├── eye.svg
│ │ │ │ │ ├── flower-640x480.jpg
│ │ │ │ │ ├── sun.svg
│ │ │ │ │ ├── test-image-100x100.jpg
│ │ │ │ │ └── txt.svg
│ │ │ │ └── serve.json
│ │ │ └── TestBed.tsx
│ │ └── themed-app-test-helpers.ts
│ └── vite-env.d.ts
├── tests
│ ├── components
│ │ ├── CodeBlock
│ │ │ └── hightlight-code.test.ts
│ │ ├── playground-pattern.test.ts
│ │ └── Tree
│ │ └── Tree-states.test.ts
│ ├── components-core
│ │ ├── abstractions
│ │ │ └── treeAbstractions.test.ts
│ │ ├── container
│ │ │ └── buildProxy.test.ts
│ │ ├── interception
│ │ │ ├── orderBy.test.ts
│ │ │ ├── ReadOnlyCollection.test.ts
│ │ │ └── request-param-converter.test.ts
│ │ ├── scripts-runner
│ │ │ ├── AttributeValueParser.test.ts
│ │ │ ├── eval-tree-arrow-async.test.ts
│ │ │ ├── eval-tree-arrow.test.ts
│ │ │ ├── eval-tree-func-decl-async.test.ts
│ │ │ ├── eval-tree-func-decl.test.ts
│ │ │ ├── eval-tree-pre-post.test.ts
│ │ │ ├── eval-tree-regression.test.ts
│ │ │ ├── eval-tree.test.ts
│ │ │ ├── function-proxy.test.ts
│ │ │ ├── parser-regression.test.ts
│ │ │ ├── process-event.test.ts
│ │ │ ├── process-function.test.ts
│ │ │ ├── process-implicit-context.test.ts
│ │ │ ├── process-statement-asgn.test.ts
│ │ │ ├── process-statement-destruct.test.ts
│ │ │ ├── process-statement-regs.test.ts
│ │ │ ├── process-statement-sync.test.ts
│ │ │ ├── process-statement.test.ts
│ │ │ ├── process-switch-sync.test.ts
│ │ │ ├── process-switch.test.ts
│ │ │ ├── process-try-sync.test.ts
│ │ │ ├── process-try.test.ts
│ │ │ └── test-helpers.ts
│ │ ├── test-metadata-handler.ts
│ │ ├── theming
│ │ │ ├── border-segments.test.ts
│ │ │ ├── component-layout.resolver.test.ts
│ │ │ ├── layout-property-parser.test.ts
│ │ │ ├── layout-resolver.test.ts
│ │ │ ├── layout-resolver2.test.ts
│ │ │ ├── layout-vp-override.test.ts
│ │ │ └── padding-segments.test.ts
│ │ └── utils
│ │ ├── date-utils.test.ts
│ │ ├── format-human-elapsed-time.test.ts
│ │ └── LruCache.test.ts
│ ├── language-server
│ │ ├── completion.test.ts
│ │ ├── format.test.ts
│ │ ├── hover.test.ts
│ │ └── mockData.ts
│ └── parsers
│ ├── common
│ │ └── input-stream.test.ts
│ ├── markdown
│ │ └── parse-binding-expression.test.ts
│ ├── parameter-parser.test.ts
│ ├── paremeter-parser.test.ts
│ ├── scripting
│ │ ├── eval-tree-arrow.test.ts
│ │ ├── eval-tree-pre-post.test.ts
│ │ ├── eval-tree.test.ts
│ │ ├── function-proxy.test.ts
│ │ ├── lexer-literals.test.ts
│ │ ├── lexer-misc.test.ts
│ │ ├── module-parse.test.ts
│ │ ├── parser-arrow.test.ts
│ │ ├── parser-assignments.test.ts
│ │ ├── parser-binary.test.ts
│ │ ├── parser-destructuring.test.ts
│ │ ├── parser-errors.test.ts
│ │ ├── parser-expressions.test.ts
│ │ ├── parser-function.test.ts
│ │ ├── parser-literals.test.ts
│ │ ├── parser-primary.test.ts
│ │ ├── parser-regex.test.ts
│ │ ├── parser-statements.test.ts
│ │ ├── parser-unary.test.ts
│ │ ├── process-event.test.ts
│ │ ├── process-implicit-context.test.ts
│ │ ├── process-statement-asgn.test.ts
│ │ ├── process-statement-destruct.test.ts
│ │ ├── process-statement-regs.test.ts
│ │ ├── process-statement-sync.test.ts
│ │ ├── process-statement.test.ts
│ │ ├── process-switch-sync.test.ts
│ │ ├── process-switch.test.ts
│ │ ├── process-try-sync.test.ts
│ │ ├── process-try.test.ts
│ │ ├── simplify-expression.test.ts
│ │ ├── statement-hooks.test.ts
│ │ └── test-helpers.ts
│ ├── style-parser
│ │ ├── generateHvarChain.test.ts
│ │ ├── parseHVar.test.ts
│ │ ├── parser.test.ts
│ │ └── tokens.test.ts
│ └── xmlui
│ ├── lint.test.ts
│ ├── parser.test.ts
│ ├── scanner.test.ts
│ ├── transform.attr.test.ts
│ ├── transform.circular.test.ts
│ ├── transform.element.test.ts
│ ├── transform.errors.test.ts
│ ├── transform.escape.test.ts
│ ├── transform.regression.test.ts
│ ├── transform.script.test.ts
│ ├── transform.test.ts
│ └── xmlui.ts
├── tests-e2e
│ ├── api-bound-component-regression.spec.ts
│ ├── api-call-as-extracted-component.spec.ts
│ ├── assign-to-object-or-array-regression.spec.ts
│ ├── binding-regression.spec.ts
│ ├── children-as-template-context-vars.spec.ts
│ ├── compound-component.spec.ts
│ ├── context-vars-regression.spec.ts
│ ├── data-bindings.spec.ts
│ ├── datasource-and-api-usage-in-var.spec.ts
│ ├── datasource-direct-binding.spec.ts
│ ├── datasource-onLoaded-regression.spec.ts
│ ├── modify-array-item-regression.spec.ts
│ ├── namespaces.spec.ts
│ ├── push-to-array-regression.spec.ts
│ ├── screen-breakpoints.spec.ts
│ ├── scripting.spec.ts
│ ├── state-scope-in-pages.spec.ts
│ └── state-var-scopes.spec.ts
├── tsconfig.bin.json
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
└── vitest.config.ts
```
# Files
--------------------------------------------------------------------------------
/xmlui/dev-docs/next/tiptap-design-considerations.md:
--------------------------------------------------------------------------------
```markdown
# Tiptap Design Considerations for XMLUI Markdown Interop
## Context
XMLUI uses Markdown as the source of truth for all documentation and rich text content. The Markdown component supports a wide range of features, including GFM (GitHub Flavored Markdown) extensions, and maps Markdown/HTML tags to XMLUI's own React components for consistent theming and behavior.
## Interop Challenges
- **Tiptap (rich editor) natively produces HTML, not Markdown.**
- **Markdown is less expressive than HTML.** Some HTML features cannot be round-tripped to Markdown.
- **XMLUI Markdown only supports a subset of HTML tags,** mapped via the HTMLTags component. Arbitrary HTML is not guaranteed to render.
## Design Options
### 1. Plain Markdown Editor
- Simple `<textarea>` for raw Markdown editing.
- No conversion needed; what the user sees is what is stored.
- Power users can use all Markdown features.
### 2. Rich Editor Producing Markdown
- Use Tiptap for WYSIWYG editing, but restrict features to those that map cleanly to Markdown and XMLUI's Markdown component.
- On save, convert Tiptap's HTML output to Markdown (using a library like turndown).
- On load, convert Markdown to HTML for editing (using marked or markdown-it).
- Warn users about possible formatting loss if switching between modes.
## Supported Features
| Feature/Tag | Markdown | HTML | XMLUI Markdown Support | Notes |
|------------------|----------|-----------------------------|-----------------------|------------------------------|
| Headings | Yes | `<h1>`-`<h6>` | Yes | Standard |
| Bold/Italic | Yes | `<b>`, `<i>`, `<strong>`, `<em>` | Yes | Mapped to Text variants |
| Lists | Yes | `<ul>`, `<ol>`, `<li>` | Yes | Standard |
| Links | Yes | `<a>` | Yes | Mapped to LinkNative |
| Code/Pre | Yes | `<code>`, `<pre>` | Yes | Mapped to Text/PreTag |
| Tables | GFM | `<table>` | Yes (GFM) | Supported via remark-gfm |
| Images | Yes | `<img>` | Yes | Standard |
| Blockquote | Yes | `<blockquote>` | Yes | Standard |
| Details/Section | No (MD) | `<details>`, `<section>` | Yes (custom) | Special handling |
| Custom HTML | No | Most | No/Partial | Only mapped tags allowed |
## Best Practices
- **Keep Markdown as the canonical format.**
- **Restrict Tiptap features to those that map to XMLUI Markdown/HTMLTags.**
- **Warn users about possible formatting loss when switching between rich/plain modes.**
- **Test round-tripping** (Markdown → HTML → Markdown) for fidelity.
- **Document any limitations or unsupported features.**
## Open Questions
- Should the conversion logic live in the editor component or in global handlers?
- How should we handle features/extensions that are not supported by XMLUI Markdown?
- What is the best UX for switching between plain and rich modes?
## Focused Scope for Tiptap/TableEditor Exercise
### Motivation
The primary motivation for the Tiptap exercise is to demonstrate, in developer documentation, how to wrap a real, useful component in XMLUI. The chosen example is a TableEditor, which addresses a common pain point: creating and editing Markdown tables for documentation.
### Scope and Workflow
- The TableEditor provides a spreadsheet-like UI for editing tables.
- Users can switch to a code view to see the generated Markdown table.
- The workflow is: edit your table visually, copy the Markdown, and paste it into your documentation (e.g., in VSCode).
- There is no need to solve file persistence, in-situ editing, or live site integration for this exercise.
- This mirrors the current workflow where developers use external tools to generate Markdown tables, but brings the experience into the XMLUI/React context.
### Rationale
- This approach is practical and developer-focused, providing immediate utility without overcomplicating the implementation.
- It showcases XMLUI's extensibility and ability to integrate rich, interactive components.
- The TableEditor can be demonstrated as a standalone tool or as a Tiptap node/component, but the main value is in Markdown table generation.
### Next Step
The next step is to rename the Editor component to TableEditor to reflect this focused scope.
---
*This document is a living record of design considerations for Tiptap/Markdown interop in XMLUI. Update as the implementation evolves.*
```
--------------------------------------------------------------------------------
/xmlui/bin/index.ts:
--------------------------------------------------------------------------------
```typescript
import { build } from "./build";
import { start } from "./start";
import { preview } from "./preview";
import yargs from "yargs/yargs";
import { hideBin } from "yargs/helpers";
import AdmZip from "adm-zip";
import { buildLib } from "./build-lib";
process.on("unhandledRejection", (err) => {
throw err;
});
async function zipDirectory(sourceDir: string, outPath: string = sourceDir) {
const zip = new AdmZip();
zip.addLocalFolder(sourceDir);
await zip.writeZipPromise(outPath);
console.log(`Zip file created: ${outPath}`);
}
async function zipDist({
target = "ui.zip",
source = "dist",
}: {
target?: string;
source?: string;
}) {
await zipDirectory(`${process.cwd()}/${source}`, `${process.cwd()}/${target}`);
}
function dedupeArg(arg: any) {
if (Array.isArray(arg)) {
return arg[arg.length - 1];
}
return arg;
}
function getBoolArg(arg: any, defaultValue?: boolean | undefined) {
if (arg === undefined) {
return defaultValue;
}
return dedupeArg(arg) !== "false";
}
function getStringArg(arg: any, defaultValue: string | undefined) {
if (arg === undefined) {
return defaultValue;
}
return dedupeArg(arg);
}
// Configure yargs with proper typing
interface BuildArgs {
flatDist?: boolean;
prod?: boolean;
buildMode?: string;
withMock?: boolean;
withHostingMetaFiles?: boolean;
withRelativeRoot?: boolean;
}
interface BuildLibArgs {
watch?: boolean;
mode?: string;
}
interface StartArgs {
port?: number;
withMock?: boolean;
proxy?: string;
}
interface PreviewArgs {
proxy?: string;
}
interface ZipDistArgs {
target?: string;
source?: string;
}
const argv = yargs(hideBin(process.argv))
.command<BuildArgs>("build", "Build the project", (yargs) => {
return yargs
.option("flatDist", {
type: "boolean",
description: "Create flat distribution",
})
.option("prod", {
type: "boolean",
description: "Production build",
})
.option("buildMode", {
type: "string",
description: "Build mode",
})
.option("withMock", {
type: "boolean",
description: "Include mock data",
})
.option("withHostingMetaFiles", {
type: "boolean",
description: "Include hosting meta files",
})
.option("withRelativeRoot", {
type: "boolean",
description: "Use relative root",
});
})
.command<BuildLibArgs>("build-lib", "Build library", (yargs) => {
return yargs
.option("watch", {
type: "boolean",
description: "Watch mode",
})
.option("mode", {
type: "string",
description: "Build mode",
});
})
.command<StartArgs>("start", "Start development server", (yargs) => {
return yargs
.option("port", {
type: "number",
description: "Port number",
})
.option("withMock", {
type: "boolean",
description: "Include mock data",
})
.option("proxy", {
type: "string",
description: "Proxy target",
});
})
.command<PreviewArgs>("preview", "Preview build", (yargs) => {
return yargs.option("proxy", {
type: "string",
description: "Proxy target",
});
})
.command<ZipDistArgs>("zip-dist", "Zip distribution", (yargs) => {
return yargs
.option("target", {
type: "string",
description: "Target zip file",
})
.option("source", {
type: "string",
description: "Source directory",
});
})
.help()
.parseSync();
const command = argv._[0] as string;
switch (command) {
case "build": {
const { flatDist, prod, buildMode, withMock, withHostingMetaFiles, withRelativeRoot } =
argv as BuildArgs;
build({
buildMode: getStringArg(buildMode, prod ? "CONFIG_ONLY" : undefined),
withMock: getBoolArg(withMock, prod ? false : undefined),
withHostingMetaFiles: getBoolArg(withHostingMetaFiles, prod ? false : undefined),
withRelativeRoot: getBoolArg(withRelativeRoot, prod ? true : undefined),
flatDist: getBoolArg(flatDist, prod ? true : undefined),
});
break;
}
case "build-lib": {
const { watch, mode } = argv as BuildLibArgs;
buildLib({ watchMode: getBoolArg(watch, false), mode: getStringArg(mode, "") });
break;
}
case "start": {
const { port, withMock, proxy } = argv as StartArgs;
start({ port, withMock: getBoolArg(withMock), proxy });
break;
}
case "preview": {
const { proxy } = argv as PreviewArgs;
preview({ proxy });
break;
}
case "zip-dist": {
const { target, source } = argv as ZipDistArgs;
zipDist({ target, source });
break;
}
default: {
console.log('Unknown command "' + command + '".');
console.log("Perhaps you need to update xmlui?");
process.exit(1);
}
}
```
--------------------------------------------------------------------------------
/xmlui/src/components/Charts/AreaChart/AreaChart.md:
--------------------------------------------------------------------------------
```markdown
%-DESC-START
Interactive area chart for showing data trends over time with filled areas under the curve.
**Key features:**
- **Time series visualization**: Perfect for showing data trends over time with filled areas under the curve
- **Multiple data series**: Display several metrics on the same chart with different colored areas
- **Stacked vs overlapping**: Stack areas on top of each other or display them overlapping
- **Curved lines**: Use smooth curves for more visually appealing continuous data
- **Custom formatting**: Use `tickFormatter` to format axis labels
%-DESC-END
%-PROP-START data
```xml
<AreaChart
nameKey="name"
data="{[
{ name: 'Jan', value: 100 },
{ name: 'Feb', value: 150 },
{ name: 'Mar', value: 120 }
]}"
dataKeys="{['value']}"
/>
```
%-PROP-END
%-PROP-START nameKey
```xml
<AreaChart
nameKey="month"
data="{[
{ month: 'Jan', sales: 1200, profit: 400 },
{ month: 'Feb', sales: 1900, profit: 600 },
{ month: 'Mar', sales: 1500, profit: 500 }
]}"
dataKeys="{['sales', 'profit']}"
/>
```
%-PROP-END
%-PROP-START dataKeys
```xml
<AreaChart
nameKey="category"
data="{[
{ category: 'A', value1: 100, value2: 200 },
{ category: 'B', value1: 150, value2: 250 },
{ category: 'C', value1: 120, value2: 180 }
]}"
dataKeys="{['value1', 'value2']}"
/>
```
%-PROP-END
%-PROP-START hideX
```xml
<AreaChart
nameKey="name"
data="{[
{ name: 'A', value: 100 },
{ name: 'B', value: 200 },
{ name: 'C', value: 150 }
]}"
dataKeys="{['value']}"
hideX="true"
/>
```
%-PROP-END
%-PROP-START hideY
```xml
<AreaChart
nameKey="name"
data="{[
{ name: 'A', value: 100 },
{ name: 'B', value: 200 },
{ name: 'C', value: 150 }
]}"
dataKeys="{['value']}"
hideY="true"
/>
```
%-PROP-END
%-PROP-START hideTickX
```xml
<AreaChart
nameKey="name"
data="{[
{ name: 'A', value: 100 },
{ name: 'B', value: 200 },
{ name: 'C', value: 150 }
]}"
dataKeys="{['value']}"
hideTickX="true"
/>
```
%-PROP-END
%-PROP-START hideTickY
```xml
<AreaChart
nameKey="name"
data="{[
{ name: 'A', value: 100 },
{ name: 'B', value: 200 },
{ name: 'C', value: 150 }
]}"
dataKeys="{['value']}"
hideTickY="true"
/>
```
%-PROP-END
%-PROP-START hideTooltip
```xml
<AreaChart
nameKey="name"
data="{[
{ name: 'A', value: 100 },
{ name: 'B', value: 200 },
{ name: 'C', value: 150 }
]}"
dataKeys="{['value']}"
hideTooltip="true"
/>
```
%-PROP-END
%-PROP-START showLegend
```xml
<AreaChart
nameKey="quarter"
data="{[
{ quarter: 'Q1', revenue: 1000, expenses: 800, profit: 200 },
{ quarter: 'Q2', revenue: 1200, expenses: 900, profit: 300 },
{ quarter: 'Q3', revenue: 1100, expenses: 850, profit: 250 },
{ quarter: 'Q4', revenue: 1400, expenses: 1000, profit: 400 }
]}"
dataKeys="{['revenue', 'expenses', 'profit']}"
showLegend="true"
/>
```
%-PROP-END
%-PROP-START stacked
```xml
<AreaChart
nameKey="category"
data="{[
{ category: 'A', value1: 100, value2: 200 },
{ category: 'B', value1: 150, value2: 250 },
{ category: 'C', value1: 120, value2: 180 }
]}"
dataKeys="{['value1', 'value2']}"
stacked="true"
/>
```
%-PROP-END
%-PROP-START curved
```xml
<AreaChart
nameKey="time"
data="{[
{ time: '00:00', temperature: 18 },
{ time: '06:00', temperature: 15 },
{ time: '12:00', temperature: 25 },
{ time: '18:00', temperature: 22 },
{ time: '24:00', temperature: 19 }
]}"
dataKeys="{['temperature']}"
curved="true"
/>
```
%-PROP-END
%-PROP-START tooltipTemplate
```xmlui-pg copy display height="320px" name="Example: tooltipTemplate" /tooltipTemplate/
<App>
<AreaChart
height="240px"
data="{[
{ 'month': 'Jan', 'sales': 1200, 'profit': 400 },
{ 'month': 'Feb', 'sales': 1900, 'profit': 600 },
{ 'month': 'Mar', 'sales': 1500, 'profit': 500 },
{ 'month': 'Apr', 'sales': 1800, 'profit': 700 }
]}"
dataKeys="{['sales', 'profit']}"
nameKey="month"
>
<property name="tooltipTemplate">
<VStack backgroundColor='white' padding="$space-2">
<Text fontWeight='bold'>{$tooltip.label}</Text>
<HStack>
<Text color='blue'>Sales: {$tooltip.payload.sales}</Text>
<Text color='green'>Profit: {$tooltip.payload.profit}</Text>
</HStack>
</VStack>
</property>
</AreaChart>
</App>
```
The `tooltipTemplate` prop allows you to customize the appearance and content of chart tooltips. The template receives a `$tooltip` context variable containing:
- `$tooltip.label`: The label for the data point (typically the nameKey value)
- `$tooltip.payload`: An object containing all data values for the hovered point
- `$tooltip.active`: Boolean indicating if the tooltip is currently active
%-PROP-END
```
--------------------------------------------------------------------------------
/xmlui/src/components/Heading/HeadingNative.tsx:
--------------------------------------------------------------------------------
```typescript
import React, {
type CSSProperties,
type ForwardedRef,
forwardRef,
type ReactNode,
useCallback,
useContext,
useEffect,
useRef,
useState,
} from "react";
import { composeRefs } from "@radix-ui/react-compose-refs";
import classnames from "classnames";
import styles from "./Heading.module.scss";
import { getMaxLinesStyle } from "../../components-core/utils/css-utils";
import { TableOfContentsContext } from "../../components-core/TableOfContentsContext";
import { useIsomorphicLayoutEffect } from "../../components-core/utils/hooks";
import type { HeadingLevel } from "./abstractions";
import { Link } from "@remix-run/react";
import { useAppContext } from "../../components-core/AppContext";
import type { RegisterComponentApiFn } from "../../abstractions/RendererDefs";
export type HeadingProps = {
uid?: string;
level?: HeadingLevel;
children: ReactNode;
sx?: CSSProperties;
style?: CSSProperties;
maxLines?: number;
preserveLinebreaks?: boolean;
ellipses?: boolean;
title?: string;
className?: string;
showAnchor?: boolean;
registerComponentApi?: RegisterComponentApiFn;
[furtherProps: string]: any;
};
export const defaultProps: Pick<
HeadingProps,
"level" | "ellipses" | "omitFromToc" | "maxLines" | "preserveLinebreaks" | "showAnchor"
> = {
level: "h1",
ellipses: true,
omitFromToc: false,
maxLines: 0,
preserveLinebreaks: false,
showAnchor: false,
};
export const Heading = forwardRef(function Heading(
{
uid,
level = defaultProps.level,
children,
sx,
style,
title,
maxLines = defaultProps.maxLines,
preserveLinebreaks,
ellipses = defaultProps.ellipses,
className,
omitFromToc = defaultProps.omitFromToc,
showAnchor,
registerComponentApi,
...furtherProps
}: HeadingProps,
forwardedRef: ForwardedRef<HTMLHeadingElement>,
) {
const Element = level?.toLowerCase() as HeadingLevel;
const elementRef = useRef<HTMLHeadingElement>(null);
const [anchorId, setAnchorId] = useState<string | null>(null);
const anchorRef = useRef<HTMLAnchorElement>(null);
const tableOfContentsContext = useContext(TableOfContentsContext);
const registerHeading = tableOfContentsContext?.registerHeading;
const appContext = useAppContext();
if (showAnchor === undefined) {
showAnchor = appContext?.appGlobals?.showHeadingAnchors ?? false;
}
const ref = forwardedRef ? composeRefs(elementRef, forwardedRef) : elementRef;
const scrollIntoView = useCallback((options?: ScrollIntoViewOptions) => {
if (elementRef.current) {
elementRef.current.scrollIntoView({
behavior: 'smooth',
block: 'start',
...options,
});
}
}, []);
const hasOverflow = useCallback(() => {
if (elementRef.current) {
const element = elementRef.current;
return element.scrollWidth > element.clientWidth || element.scrollHeight > element.clientHeight;
}
return false;
}, []);
useEffect(() => {
registerComponentApi?.({
scrollIntoView,
hasOverflow,
});
}, [registerComponentApi, scrollIntoView, hasOverflow]);
useEffect(() => {
if (elementRef.current) {
const newAnchorId = elementRef.current.textContent
?.trim()
?.replace(/[^\w\s-]/g, "")
?.replace(/\s+/g, "-")
?.toLowerCase();
setAnchorId(newAnchorId || null);
}
}, []);
useIsomorphicLayoutEffect(() => {
if (elementRef.current && anchorId && !omitFromToc) {
return registerHeading?.({
id: anchorId,
level: parseInt(level.replace("h", "")),
text: elementRef.current.textContent!.trim().replace(/#$/, ""), // Remove trailing #
anchor: anchorRef.current,
});
}
}, [anchorId, registerHeading, level, omitFromToc]);
return (
<Element
{...furtherProps}
ref={ref}
id={uid}
title={title}
style={{ ...sx, ...style, ...getMaxLinesStyle(maxLines) }}
className={classnames(styles.heading, styles[Element], className, {
[styles.truncateOverflow]: maxLines > 0,
[styles.preserveLinebreaks]: preserveLinebreaks,
[styles.noEllipsis]: !ellipses,
})}
>
{anchorId && (
<span ref={anchorRef} id={anchorId} className={styles.anchorRef} data-anchor={true} />
)}
{children}
{showAnchor && anchorId && (
<Link
to={`#${anchorId}`}
aria-hidden="true"
onClick={(event) => {
// cmd/ctrl + click - open in new tab, don't prevent that
if (tableOfContentsContext) {
if (!event.ctrlKey && !event.metaKey && !event.metaKey) {
event.preventDefault();
}
tableOfContentsContext.scrollToAnchor(anchorId, true);
}
}}
>
#
</Link>
)}
</Element>
);
});
```
--------------------------------------------------------------------------------
/xmlui/src/components/Splitter/VSplitter.spec.ts:
--------------------------------------------------------------------------------
```typescript
import { getBounds } from "../../testing/component-test-helpers";
import { expect, test } from "../../testing/fixtures";
// =============================================================================
// BASIC FUNCTIONALITY TESTS
// =============================================================================
test.describe("Basic Functionality", () => {
test("renders with basic setup", async ({ initTestBed, page }) => {
await initTestBed(`
<VSplitter height="200px" width="400px" testId="vsplitter">
<Stack backgroundColor="lightblue" height="100%" testId="primary"/>
<Stack backgroundColor="darksalmon" height="100%" testId="secondary"/>
</VSplitter>
`);
await expect(page.getByTestId("vsplitter")).toBeVisible();
await expect(page.getByTestId("primary")).toBeVisible();
await expect(page.getByTestId("secondary")).toBeVisible();
});
test("defaults to vertical orientation", async ({ initTestBed, page }) => {
await initTestBed(`
<VSplitter height="200px" width="400px" testId="vsplitter">
<Stack backgroundColor="lightblue" height="100%" testId="primary"/>
<Stack backgroundColor="darksalmon" height="100%" testId="secondary"/>
</VSplitter>
`);
const primary = page.getByTestId("primary");
const secondary = page.getByTestId("secondary");
const primaryBounds = await getBounds(primary);
const secondaryBounds = await getBounds(secondary);
// In vertical orientation, primary should be above secondary
expect(primaryBounds.bottom).toBeLessThanOrEqual(secondaryBounds.top + 10); // Allow for small overlap due to resizer
});
test("ignores orientation property when explicitly set", async ({ initTestBed, page }) => {
await initTestBed(`
<VSplitter height="200px" width="400px" orientation="horizontal" testId="vsplitter">
<Stack backgroundColor="lightblue" height="100%" testId="primary"/>
<Stack backgroundColor="darksalmon" height="100%" testId="secondary"/>
</VSplitter>
`);
const primary = page.getByTestId("primary");
const secondary = page.getByTestId("secondary");
const primaryBounds = await getBounds(primary);
const secondaryBounds = await getBounds(secondary);
// Even with orientation="horizontal", VSplitter should still be vertical
// Primary should be above secondary, NOT to the left of it
expect(primaryBounds.bottom).toBeLessThanOrEqual(secondaryBounds.top + 10);
});
test("works with swapped property", async ({ initTestBed, page }) => {
await initTestBed(`
<VSplitter height="200px" width="400px" swapped="true" testId="vsplitter">
<Stack backgroundColor="lightblue" height="100%" testId="primary"/>
<Stack backgroundColor="darksalmon" height="100%" testId="secondary"/>
</VSplitter>
`);
const primary = page.getByTestId("primary");
const secondary = page.getByTestId("secondary");
const primaryBounds = await getBounds(primary);
const secondaryBounds = await getBounds(secondary);
// With swapped=true, secondary should be above primary
expect(secondaryBounds.bottom).toBeLessThanOrEqual(primaryBounds.top + 10);
});
test("maintains vertical orientation even with invalid orientation values", async ({ initTestBed, page }) => {
await initTestBed(`
<VSplitter height="200px" width="400px" orientation="invalid-value" testId="vsplitter">
<Stack backgroundColor="lightblue" height="100%" testId="primary"/>
<Stack backgroundColor="darksalmon" height="100%" testId="secondary"/>
</VSplitter>
`);
const primary = page.getByTestId("primary");
const secondary = page.getByTestId("secondary");
const primaryBounds = await getBounds(primary);
const secondaryBounds = await getBounds(secondary);
// Should still be vertical regardless of invalid orientation value
expect(primaryBounds.bottom).toBeLessThanOrEqual(secondaryBounds.top + 10);
});
});
// =============================================================================
// ACCESSIBILITY TESTS
// =============================================================================
test.describe("Accessibility", () => {
test("resizer has vertical cursor style", async ({ initTestBed, page, createSplitterDriver }) => {
await initTestBed(`
<VSplitter height="200px" width="400px" testId="vsplitter">
<Stack backgroundColor="lightblue" height="100%"/>
<Stack backgroundColor="darksalmon" height="100%"/>
</VSplitter>
`);
const vsplitter = page.getByTestId("vsplitter");
const driver = await createSplitterDriver(vsplitter);
const resizer = await driver.getResizer();
// VSplitter should always use vertical cursor (ns-resize)
await expect(resizer).toHaveCSS("cursor", "ns-resize");
});
});
```
--------------------------------------------------------------------------------
/docs/content/components/Footer.md:
--------------------------------------------------------------------------------
```markdown
# Footer [#footer]
`Footer` provides a designated area at the bottom of your application for footer content such as branding, copyright notices, or utility controls like theme toggles.
## Properties [#properties]
This component does not have any properties.
## Events [#events]
This component does not have any events.
## 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)-Footer | $backgroundColor-AppHeader | $backgroundColor-AppHeader |
| [border](../styles-and-themes/common-units/#border)-Footer | *none* | *none* |
| [borderBottom](../styles-and-themes/common-units/#border)-Footer | *none* | *none* |
| [borderBottomColor](../styles-and-themes/common-units/#color)-Footer | *none* | *none* |
| [borderBottomStyle](../styles-and-themes/common-units/#border-style)-Footer | *none* | *none* |
| [borderBottomWidth](../styles-and-themes/common-units/#size)-Footer | *none* | *none* |
| [borderColor](../styles-and-themes/common-units/#color)-Footer | *none* | *none* |
| [borderEndEndRadius](../styles-and-themes/common-units/#border-rounding)-Footer | *none* | *none* |
| [borderEndStartRadius](../styles-and-themes/common-units/#border-rounding)-Footer | *none* | *none* |
| [borderHorizontal](../styles-and-themes/common-units/#border)-Footer | *none* | *none* |
| [borderHorizontalColor](../styles-and-themes/common-units/#color)-Footer | *none* | *none* |
| [borderHorizontalStyle](../styles-and-themes/common-units/#border-style)-Footer | *none* | *none* |
| [borderHorizontalWidth](../styles-and-themes/common-units/#size)-Footer | *none* | *none* |
| [borderLeft](../styles-and-themes/common-units/#border)-Footer | *none* | *none* |
| [color](../styles-and-themes/common-units/#color)-Footer | *none* | *none* |
| [borderLeftStyle](../styles-and-themes/common-units/#border-style)-Footer | *none* | *none* |
| [borderLeftWidth](../styles-and-themes/common-units/#size)-Footer | *none* | *none* |
| [borderRight](../styles-and-themes/common-units/#border)-Footer | *none* | *none* |
| [color](../styles-and-themes/common-units/#color)-Footer | *none* | *none* |
| [borderRightStyle](../styles-and-themes/common-units/#border-style)-Footer | *none* | *none* |
| [borderRightWidth](../styles-and-themes/common-units/#size)-Footer | *none* | *none* |
| [borderStartEndRadius](../styles-and-themes/common-units/#border-rounding)-Footer | *none* | *none* |
| [borderStartStartRadius](../styles-and-themes/common-units/#border-rounding)-Footer | *none* | *none* |
| [borderStyle](../styles-and-themes/common-units/#border-style)-Footer | *none* | *none* |
| [borderTop](../styles-and-themes/common-units/#border)-Footer | 1px solid $borderColor | 1px solid $borderColor |
| [borderTopColor](../styles-and-themes/common-units/#color)-Footer | *none* | *none* |
| [borderTopStyle](../styles-and-themes/common-units/#border-style)-Footer | *none* | *none* |
| [borderTopWidth](../styles-and-themes/common-units/#size)-Footer | *none* | *none* |
| [borderHorizontal](../styles-and-themes/common-units/#border)-Footer | *none* | *none* |
| [borderVerticalColor](../styles-and-themes/common-units/#color)-Footer | *none* | *none* |
| [borderVerticalStyle](../styles-and-themes/common-units/#border-style)-Footer | *none* | *none* |
| [borderVerticalWidth](../styles-and-themes/common-units/#size)-Footer | *none* | *none* |
| [borderWidth](../styles-and-themes/common-units/#size)-Footer | *none* | *none* |
| [fontSize](../styles-and-themes/common-units/#size)-Footer | $fontSize-sm | $fontSize-sm |
| [gap](../styles-and-themes/common-units/#size)-Footer | $space-normal | $space-normal |
| [height](../styles-and-themes/common-units/#size)-Footer | *none* | *none* |
| [margin](../styles-and-themes/common-units/#size)-Footer | 0 auto | 0 auto |
| [maxWidth-content](../styles-and-themes/common-units/#size)-Footer | $maxWidth-content | $maxWidth-content |
| [padding](../styles-and-themes/common-units/#size)-Footer | $space-2 $space-4 | $space-2 $space-4 |
| [paddingBottom](../styles-and-themes/common-units/#size)-Footer | *none* | *none* |
| [paddingHorizontal](../styles-and-themes/common-units/#size)-Footer | *none* | *none* |
| [paddingLeft](../styles-and-themes/common-units/#size)-Footer | *none* | *none* |
| [paddingRight](../styles-and-themes/common-units/#size)-Footer | *none* | *none* |
| [paddingTop](../styles-and-themes/common-units/#size)-Footer | *none* | *none* |
| [paddingVertical](../styles-and-themes/common-units/#size)-Footer | *none* | *none* |
| [textColor](../styles-and-themes/common-units/#color)-Footer | $textColor-secondary | $textColor-secondary |
| [verticalAlignment](../styles-and-themes/common-units/#alignment)-Footer | center | center |
```
--------------------------------------------------------------------------------
/xmlui/src/components-core/theming/parse-layout-props.ts:
--------------------------------------------------------------------------------
```typescript
import type { MediaBreakpointType} from "../../abstractions/AppContextDefs";
import { MediaBreakpointKeys } from "../../abstractions/AppContextDefs";
export type ParsedLayout = {
property: string;
part?: string;
component?: string;
screenSizes?: MediaBreakpointType[];
states?: string[];
}
/**
* Mapping exceptions for camelCase property names to CSS property names.
* These properties don't follow the standard camelCase-to-kebab-case conversion.
*/
export const CSS_PROPERTY_EXCEPTIONS: Record<string, string> = {
textColor: "color",
paddingVertical: "",
paddingHorizontal: "",
marginVertical: "",
marginHorizontal: "",
borderVertical: "",
borderHorizontal: "",
};
export function parseLayoutProperty(prop: string, parseComponent: boolean = false): ParsedLayout | string {
if (!prop || typeof prop !== 'string') {
return "Property string cannot be empty";
}
// Split by '--' to separate states from the rest
const parts = prop.split('--');
const mainPart = parts[0];
const stateParts = parts.slice(1);
// Validate state names
const states: string[] = [];
for (const statePart of stateParts) {
if (!statePart) {
return "State name cannot be empty";
}
if (!isValidName(statePart)) {
return `Invalid state name: ${statePart}`;
}
states.push(statePart);
}
// Split main part by '-' to get segments
const segments = mainPart.split('-').filter(segment => segment.length > 0);
if (segments.length === 0) {
return "CSS property name is required";
}
// The first segment is always the CSS property name (camelCase, no dashes)
const property = segments[0];
// Validate CSS property name (camelCase)
if (!isValidPropertyName(property)) {
return `Invalid CSS property name: ${property}`;
}
const result: ParsedLayout = {
property,
states: states.length > 0 ? states : undefined
};
let segmentIndex = 1;
const screenSizes: MediaBreakpointType[] = [];
// Process remaining segments
while (segmentIndex < segments.length) {
const segment = segments[segmentIndex];
// Check if it's a screen size
if (isMediaBreakpoint(segment)) {
screenSizes.push(segment as MediaBreakpointType);
segmentIndex++;
continue;
}
// Check if it's a component name (starts with uppercase)
if (isComponentName(segment)) {
if (!parseComponent) {
return `Component names are not allowed when parseComponent is false: ${segment}`;
}
if (result.component) {
return "Multiple component names found";
}
result.component = segment;
segmentIndex++;
continue;
}
// Check if it's a part name (starts with lowercase)
if (isValidPartName(segment)) {
if (result.part) {
return "Multiple part names found";
}
result.part = segment;
segmentIndex++;
continue;
}
// If we reach here, the segment is invalid
return `Invalid segment: ${segment}`;
}
// Set screen sizes if any were found
if (screenSizes.length > 0) {
result.screenSizes = screenSizes;
}
return result;
}
/**
* Transforms a camelCase property name (as used in ParsedLayout.property)
* to its corresponding CSS style property name.
*
* Handles special cases defined in CSS_PROPERTY_EXCEPTIONS, otherwise
* converts camelCase to kebab-case (e.g., "fontSize" -> "font-size").
*
* @param property - The camelCase property name from ParsedLayout
* @returns The CSS property name in kebab-case or the mapped exception
*
* @example
* toCssPropertyName('fontSize') // returns 'font-size'
* toCssPropertyName('textColor') // returns 'color' (exception)
* toCssPropertyName('backgroundColor') // returns 'background-color'
*/
export function toCssPropertyName(property: string): string {
// Check if there's a mapping exception
if (property in CSS_PROPERTY_EXCEPTIONS) {
return CSS_PROPERTY_EXCEPTIONS[property];
}
// Convert camelCase to kebab-case
return property.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
}
function isValidPropertyName(name: string): boolean {
// CSS property names in camelCase - start with lowercase letter, can contain letters and numbers
return /^[a-z][a-zA-Z0-9]*$/.test(name);
}
function isValidName(name: string): boolean {
// Names start with a letter and can contain letters, numbers, or underscores
return /^[a-zA-Z][a-zA-Z0-9_]*$/.test(name);
}
function isValidPartName(name: string): boolean {
// Part names start with lowercase letter and can contain letters, numbers, or underscores
return /^[a-z][a-zA-Z0-9_]*$/.test(name);
}
function isComponentName(name: string): boolean {
// Component names start with uppercase letter
return /^[A-Z][a-zA-Z0-9_]*$/.test(name);
}
function isMediaBreakpoint(value: string): boolean {
return MediaBreakpointKeys.includes(value as MediaBreakpointType);
}
```
--------------------------------------------------------------------------------
/xmlui/src/components/Charts/AreaChart/AreaChart.tsx:
--------------------------------------------------------------------------------
```typescript
import { AreaChart, defaultProps } from "./AreaChartNative";
import { createComponentRenderer } from "../../../components-core/renderers";
import { createMetadata } from "../../metadata-helpers";
import { MemoizedItem } from "../../container-helpers";
const COMP = "AreaChart";
export const AreaChartMd = createMetadata({
status: "experimental",
description: "Interactive area chart for showing data trends over time with filled areas under the curve",
docFolder: "Charts/AreaChart",
props: {
data: {
description:
"This property is used to provide the component with data to display. " +
"The data needs to be an array of objects.",
},
dataKeys: {
description:
"This property specifies the keys in the data objects that should be used for rendering the chart elements. " +
"E.g. 'value' or 'amount'.",
valueType: "string",
},
nameKey: {
description:
"Specifies the key in the data objects that will be used to label the different data series.",
valueType: "string",
},
hideX: {
description:
"Determines whether the X-axis should be hidden. If set to `true`, the axis will not be rendered.",
valueType: "boolean",
defaultValue: defaultProps.hideX,
},
hideY: {
description:
"Determines whether the Y-axis should be hidden. If set to `true`, the axis will not be rendered.",
valueType: "boolean",
defaultValue: defaultProps.hideY,
},
hideTickX: {
description:
"Determines whether the X-axis tick labels should be hidden. If set to `true`, the tick labels will not be rendered.",
valueType: "boolean",
defaultValue: defaultProps.hideTickX,
},
hideTickY: {
description:
"Determines whether the Y-axis tick labels should be hidden. If set to `true`, the tick labels will not be rendered.",
valueType: "boolean",
defaultValue: defaultProps.hideTickY,
},
hideTooltip: {
description:
"Determines whether the tooltip should be hidden. If set to `true`, the tooltip will not be rendered.",
valueType: "boolean",
defaultValue: defaultProps.hideTooltip,
},
showLegend: {
description:
"Determines whether the legend should be shown. If set to `true`, the legend will be rendered.",
valueType: "boolean",
defaultValue: defaultProps.showLegend,
},
stacked: {
description:
"Determines whether multiple areas should be stacked on top of each other. If set to `true`, areas will be stacked.",
valueType: "boolean",
defaultValue: defaultProps.stacked,
},
curved: {
description:
"Determines whether the area lines should be curved (smooth) or straight. If set to `true`, lines will be curved.",
valueType: "boolean",
defaultValue: defaultProps.curved,
},
tooltipTemplate: {
description: "This property allows replacing the default template to display a tooltip.",
},
},
events: {
// Standard chart events - customize based on chart type
},
apis: {
// Chart-specific APIs if needed
},
contextVars: {
// Add context variables if needed
},
});
// Component renderer
export const areaChartComponentRenderer = createComponentRenderer(
COMP,
AreaChartMd,
({ extractValue, node, className, lookupSyncCallback, renderChild }: any) => {
return (
<AreaChart
className={className}
tickFormatterX={lookupSyncCallback(node.props?.tickFormatterX)}
tickFormatterY={lookupSyncCallback(node.props?.tickFormatterY)}
data={extractValue(node.props?.data)}
nameKey={extractValue(node.props?.nameKey)}
dataKeys={extractValue(node.props?.dataKeys)}
hideX={extractValue.asOptionalBoolean(node.props?.hideX)}
hideY={extractValue.asOptionalBoolean(node.props?.hideY)}
hideTickX={extractValue.asOptionalBoolean(node.props?.hideTickX)}
hideTickY={extractValue.asOptionalBoolean(node.props?.hideTickY)}
hideTooltip={extractValue.asOptionalBoolean(node.props?.hideTooltip)}
showLegend={extractValue.asOptionalBoolean(node.props?.showLegend)}
stacked={extractValue.asOptionalBoolean(node.props?.stacked)}
curved={extractValue.asOptionalBoolean(node.props?.curved)}
tooltipRenderer={
node.props.tooltipTemplate
? (tooltipData) => {
return (
<MemoizedItem
node={node.props.tooltipTemplate}
item={tooltipData}
contextVars={{
$tooltip: tooltipData,
}}
renderChild={renderChild}
/>
);
}
: undefined
}
>
{renderChild(node.children)}
</AreaChart>
);
},
);
```
--------------------------------------------------------------------------------
/xmlui/src/abstractions/ThemingDefs.ts:
--------------------------------------------------------------------------------
```typescript
import type { Dispatch, SetStateAction } from "react";
// This type describes one the available theme tones.
export type ThemeTone = "light" | "dark";
// When rendering any part of an XMLUI app, the styles to apply are enclosed in a
// theme scope. Most apps use a single theme scope that includes all nodes within
// the root app node. However, any app can use multiple theme scopes.
//
// The scope determines how the app applies styles to the particular section. This
// type defines the properties of such a theme scope.
export type ThemeScope = {
// Gets the id of the scope's theme
activeThemeId: string;
// Gets the current tone of the scope's theme
activeThemeTone: ThemeTone;
// The HTML element that works as the root of the theme's scope.
root: HTMLElement | undefined;
// Sets the root HTML element the theme is assigned to
setRoot: Dispatch<SetStateAction<HTMLElement | undefined>>;
// The active theme in the current scope
activeTheme: ThemeDefinition;
// This hash object stores the CSS theme variable names with their CSS values
// definition, like "--xmlui-verticalAlignment-Text-sub": "sub";
// "--xmlui-backgroundColor": "var(--xmlui-color-surface-50)"
themeStyles: Record<string, string>;
// This hash object stores the theme variable names with their CSS values definition,
// like "verticalAlignment-Text-sub": "sub";
// "backgroundColor": "var(--xmlui-color-surface-50)".
themeVars: Record<string, string>;
// This function retrieves the physical path of the provided resource. The path can
// be used as a URL in HTML tags, like in the `src` attribute of `<img>`.
getResourceUrl: (resourceString?: string) => string | undefined;
// This function gets the value of the specified theme variable.
getThemeVar: (themeVar: string) => string | undefined;
};
// This type represents the object managing app themes. When an app runs, styles are
// applied according to the current (active) application theme and tone.
export type AppThemes = {
// Sets the active theme id
setActiveThemeId: (newThemeId: string) => void;
// Sets the active theme tone
setActiveThemeTone: (newTone: ThemeTone) => void;
// Toggles the current theme tone
toggleThemeTone: () => void;
// Gets the id of the active theme
activeThemeId: string;
// Gets the tone of the active theme
activeThemeTone: ThemeTone;
// This array holds all theme definitions available in the app.
themes: Array<ThemeDefinition>;
// This array holds all resource definitions available in the app.
resources: Record<string, string>;
// During the build process, resources may be renamed (resource folder and file
// hierarchy flattened). This map resolves the original resource URLs to their
// corresponding names created during the build.
resourceMap: Record<string, string>;
// This array lists the ids of available themes in the app.
availableThemeIds: Array<string>;
// This property holds the current theme definition used in the app.
activeTheme: ThemeDefinition;
};
// This type describes a font definition resource.
export type FontDef =
| {
// Specifies a name that will be used as the font face value for font properties
fontFamily: string;
// A fontStyle value. Accepts two values to specify a range that is supported by
// a font-face, for example `fontStyle: oblique 20deg 50deg`
fontStyle?: string;
// A font-weight value. Accepts two values to specify a range that is supported by
// a font-face, for example `font-weight: 100 900`
fontWeight?: string;
// This property determines how a font face is displayed based on whether and
// when it is downloaded and ready to use.
fontDisplay?: string;
// The mime type of the font file
format?: string;
// Specifies references to font resources.
src: string;
}
| string;
export interface ThemeDefinitionDetails {
themeVars?: Record<string, string>;
resources?: Record<string, string | FontDef>;
}
// This type represents a theme definition object. Theme files can use this object's
// JSON-serialized format to define an app theme; an app can have multiple theme files.
export interface ThemeDefinition extends ThemeDefinitionDetails {
// Theme id
id: string;
// Optional theme name
name?: string;
// A theme can extend existing themes. The extension means that the theme variable
// values defined in the new theme override the base theme's variable values.
extends?: string | Array<string>;
// This property defines the tone-dependent theme variable values. When a theme
// variable value is resolved, the common theme variable values are overridden with
// their theme-specific values.
tones?: Record<string | ThemeTone, ThemeDefinitionDetails>;
}
export type DefaultThemeVars = Record<string | ThemeTone, string | Record<string, string>>;
```
--------------------------------------------------------------------------------
/xmlui/src/components/IFrame/IFrame.md:
--------------------------------------------------------------------------------
```markdown
%-DESC-START
**Key features:**
- **External content embedding**: Load web pages, documents, or media from external URLs
- **Security controls**: Built-in sandbox and permission policies for safe content isolation
- **HTML content support**: Display inline HTML content without external sources
- **Event handling**: Track loading states with load events
%-DESC-END
%-PROP-START src
```xmlui-pg copy display name="Example: src"
<App>
<IFrame
src="https://example.com"
width="100%"
height="300px" />
</App>
```
%-PROP-END
%-PROP-START srcdoc
```xmlui-pg copy display name="Example: srcdoc"
<App>
<IFrame
srcdoc="
<h1>Hello World</h1>
<p>This is embedded HTML content with <strong>formatting</strong>.</p>
"
width="100%"
height="200px" />
</App>
```
%-PROP-END
%-PROP-START allow
The `allow` property controls which browser features the embedded content can access. Common values include camera, microphone, geolocation, and fullscreen permissions.
```xmlui-pg copy display name="Example: allow"
<App>
<IFrame
src="https://www.youtube.com/embed/dQw4w9WgXcQ"
allow="camera; microphone; geolocation"
width="560px"
height="315px"
title="Rick Astley - Never Gonna Give You Up (Official Video)"
/>
</App>
```
%-PROP-END
%-PROP-START name
```xmlui-pg copy display name="Example: name" /name="myFrame"/
<App>
<VStack gap="$space-2">
<Button
label="Open 'Kraftwerk: The Model' in IFrame"
onClick="window.open('https://www.youtube.com/embed/-s4zRw16tMA', 'myFrame')"
/>
<IFrame
src="https://example.com"
name="myFrame"
width="100%"
height="300px" />
</VStack>
</App>
```
%-PROP-END
%-EVENT-START load
```xmlui-pg copy display name="Example: load"
<App var.loadStatus="Loading...">
<VStack gap="$space-2">
<Text value="Status: {loadStatus}" />
<IFrame
src="https://example.com"
onLoad="loadStatus = 'Content loaded successfully!'"
width="100%"
height="200px" />
</VStack>
</App>
```
%-EVENT-END
%-STYLE-START
### Size Control
IFrame supports these theme variables for consistent sizing:
- `width-IFrame`
- `height-IFrame`
- `borderRadius-IFrame`
- `border-IFrame`
```xmlui-pg display copy name="Example: IFrame with custom styling"
<App>
<Theme
width-IFrame="400px"
height-IFrame="250px"
borderRadius-IFrame="8px"
border-IFrame="2px solid $primaryColor">
<IFrame src="https://example.com" />
</Theme>
</App>
```
%-STYLE-END
%-API-START postMessage
```xmlui-pg copy display name="Example: postMessage" /postMessage/
<App>
<VStack var.messageStatus="Ready to send" gap="$space-2">
<Button
label="Send Message to IFrame"
onClick="
myIframe.postMessage({type: 'greeting', text: 'Hello from parent!'}, '*');
messageStatus = 'Message sent!';
" />
<Card title="Status: {messageStatus}" />
<IFrame
id="myIframe"
srcdoc="
<script>
window.addEventListener('message', (event) => \{
console.log('Received message:', event.data);
document.body.innerHTML =
'<h1>Message: ' + JSON.stringify(event.data) + '</h1>';
});
</script>
<h1>Waiting for message...</h1>
"
width="100%"
height="200px" />
</VStack>
</App>
```
%-API-END
%-API-START getContentWindow
Get access to the iframe's content window object. Returns null if the content window is not accessible.
```xmlui-pg copy display name="Example: getContentWindow" /getContentWindow/
<App>
<VStack var.windowStatus="Not checked yet" gap="$space-2">
<Button
label="Check Content Window"
onClick="
const contentWindow = myIframe.getContentWindow();
windowStatus = contentWindow
? 'Content window is accessible'
: 'Content window is not accessible';
" />
<Card title="Status: {windowStatus}" />
<IFrame
id="myIframe"
src="https://example.com"
width="100%"
height="200px" />
</VStack>
</App>
```
%-API-END
%-API-START getContentDocument
Get access to the iframe's content document object. Returns null if the content document is not accessible.
```xmlui-pg copy display name="Example: getContentDocument" /getContentDocument/
<App>
<VStack var.iFrameTitle="<not queried yet>" >
<Button
label="Get Document Title"
onClick="
const contentDoc = myIframe.getContentDocument();
iFrameTitle = contentDoc
? contentDoc.title
: 'Content document not accessible';
" />
<Card title="IFrame title: {iFrameTitle}" />
<IFrame
id="myIframe"
srcdoc="
<html>
<head><title>My Awesome Document</title></head>
<body><h1>Awesome Content</h1></body>
</html>"
width="100%"
height="200px" />
</VStack>
</App>
```
%-API-END
```
--------------------------------------------------------------------------------
/xmlui/src/components-core/interception/Backend.ts:
--------------------------------------------------------------------------------
```typescript
import { isArray, isObject, mapValues } from "lodash-es";
import type { BindingTreeEvaluationContext } from "../script-runner/BindingTreeEvaluationContext";
import { delay } from "../utils/misc";
import { runEventHandlerCode } from "../utils/statementUtils";
import { dateFunctions } from "../appContext/date-functions";
import { miscellaneousUtils } from "../appContext/misc-utils";
import { getDate } from "../utils/date-utils";
import Errors from "../interception/Errors";
import type { AuthService } from "../interception/ApiInterceptor";
import type {
BackendDefinition,
BackendEnvironment,
IDatabase,
RequestParams,
} from "./abstractions";
// Use this backend environment as the default
export const defaultBackendEnvironment: BackendEnvironment = {
getDate: (date?: string | number | Date) => (date ? new Date(date) : new Date()),
};
const mapValuesDeep = (obj: any, cb: (o: any) => any): any => {
if (isArray(obj)) {
return obj.map((innerObj) => mapValuesDeep(innerObj, cb));
} else if (isObject(obj)) {
return mapValues(obj, (val) => mapValuesDeep(val, cb));
} else {
return cb(obj);
}
};
export class CookieService {
private cookies: Record<string, string | Array<string>> = {};
public setCookie(key: string, value: string | Array<string>) {
this.cookies[key] = value;
}
public getCookieHeader() {
const cookieArrays: Array<[string, string]> = [];
Object.entries(this.cookies).forEach(([key, value]) => {
if (Array.isArray(value)) {
value.forEach((val) => cookieArrays.push(["Set-Cookie", `${key}=${val}`]));
} else {
cookieArrays.push(["Set-Cookie", `${key}=${value}`]);
}
});
return new Headers(cookieArrays);
}
}
export class HeaderService {
private headers: Record<string, string> = {};
public setHeader(key: string, value: string) {
this.headers[key] = value;
}
public getHeaders() {
const headersArray: Array<[string, string]> = [];
Object.entries(this.headers).forEach(([key, value]) => {
headersArray.push([key, value]);
});
return new Headers(headersArray);
}
}
export class Backend {
private readonly resolvedHelpers?: Record<string, any>;
private readonly apiStateHash: Record<string, any> = {};
constructor(
public readonly definition: BackendDefinition,
public readonly db: IDatabase,
public readonly authService: AuthService,
) {
this.resolvedHelpers = mapValuesDeep(definition.helpers, (helper) => {
if (typeof helper === "string") {
return (...params: any[]) => this.runFn(helper, ...params);
}
return helper;
});
if (definition.initialize) {
void this.runFn(definition.initialize);
}
}
async executeOperation(
operationId: string,
requestParams: RequestParams,
cookieService: CookieService,
headerService: HeaderService,
) {
const handler = this.definition.operations?.[operationId];
if (!handler) {
throw new Error(`Unknown backend operation: ${operationId}`);
}
return await this.runFn(handler, requestParams, cookieService, headerService);
}
private async runFn(src: string, ...args: any[]) {
let localContext = {
...this.resolvedHelpers,
$db: this.db,
$state: this.apiStateHash,
$authService: this.authService,
$env: defaultBackendEnvironment,
$loggedInUser: this.authService.getLoggedInUser(),
$pathParams: args[0]?.pathParams,
$queryParams: args[0]?.queryParams,
$requestBody: args[0]?.requestBody,
$cookies: args[0]?.cookies,
$requestHeaders: args[0]?.requestHeaders,
$cookieService: args[1], //TODO really ugly, temporary solution
$headerService: args[2], //TODO really ugly, temporary solution
};
const evalContext = createEvalContext({
localContext: localContext,
eventArgs: args,
appContext: {
...dateFunctions,
...miscellaneousUtils,
delay,
Errors,
createFile: (...args: any[]) => {
return new File(args[0], args[1], args[2]);
},
appendBlob: (blob1: Blob | undefined | null, blob2: Blob) => {
if (blob1 && blob2) {
return new Blob([blob1, blob2], { type: blob1.type || blob2.type });
}
if (blob1) {
return blob1;
}
if (blob2) {
return blob2;
}
return null;
},
getDate,
},
});
await runEventHandlerCode(src, evalContext);
return evalContext.mainThread?.blocks?.length
? evalContext.mainThread.blocks[evalContext.mainThread.blocks.length - 1].returnValue
: undefined;
}
}
function createEvalContext(
parts: Partial<BindingTreeEvaluationContext>,
): BindingTreeEvaluationContext {
return {
...{
mainThread: {
childThreads: [],
blocks: [{ vars: {} }],
loops: [],
breakLabelValue: -1,
},
},
...parts,
};
}
```
--------------------------------------------------------------------------------
/xmlui/src/components/TextArea/TextArea.md:
--------------------------------------------------------------------------------
```markdown
%-DESC-START
It is often used in forms, see [this guide](/forms) for details.
%-DESC-END
%-PROP-START autoSize
> **Note**: If either `autoSize`, `maxRows` or `minRows` is set, the `rows` prop has no effect.
Write multiple lines in the `TextArea` in the demo below to see how it resizes automatically.
```xmlui-pg copy display name="Example: autoSize" height="240px"
<App>
<TextArea autoSize="true" />
</App>
```
%-PROP-END
%-PROP-START enabled
```xmlui-pg copy display name="Example: enabled"
<App>
<TextArea enabled="false" />
</App>
```
%-PROP-END
%-PROP-START enterSubmits
Press `Enter` after writing something in the `TextArea` in the demo below.
See [Using Forms](/forms) for details.
```xmlui-pg copy display name="Example: enterSubmits"
<App>
<Form onSubmit="toast.success(JSON.stringify(address.value))">
<TextArea
id="address"
enterSubmits="true"
initialValue="Suzy Queue, 4455 Landing Lange, APT 4, Louisville, KY 40018-1234" />
</Form>
</App>
```
%-PROP-END
%-PROP-START initialValue
The initial value displayed in the input field.
```xmlui-pg copy display name="Example: initialValue"
<App>
<TextArea initialValue="Example text" />
</App>
```
%-PROP-END
%-PROP-START maxRows
> **Note**: If either `autoSize`, `maxRows` or `minRows` is set, the `rows` prop has no effect.
```xmlui-pg copy {3} display name="Example: maxRows" height="160px"
<App>
<TextArea
maxRows="3"
initialValue="Lorem ipsum dolor sit amet,
consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." />
</App>
```
%-PROP-END
%-PROP-START minRows
> **Note**: If either `autoSize`, `maxRows` or `minRows` is set, the `rows` prop has no effect.
```xmlui-pg copy display name="Example: minRows" height="200px"
<App>
<TextArea minRows="3" initialValue="Lorem ipsum dolor sit amet..." />
</App>
```
%-PROP-END
%-PROP-START placeholder
```xmlui-pg copy display name="Example: placeholder"
<App>
<TextArea placeholder="This is a placeholder" />
</App>
```
%-PROP-END
%-PROP-START readOnly
```xmlui-pg copy display name="Example: readOnly"
<App>
<TextArea initialValue="Example text" readOnly="{true}" />
</App>
```
%-PROP-END
%-PROP-START resize
If you allow resizing, the `TextArea` turns off automatic sizing.
When you allow vertical resizing, you can limit the sizable range according to `minRows` and `maxRows`.
Drag the small resize indicators at the bottom right on each of the controls in the demo.
```xmlui-pg copy display name="Example: resize" height="300px"
<App>
<TextArea resize="vertical" minRows="1" maxRows="8" />
<TextArea resize="both" />
</App>
```
%-PROP-END
%-PROP-START rows
> **Note**: If either `autoSize`, `maxRows` or `minRows` is set, the `rows` prop has no effect.
```xmlui-pg copy display name="Example: rows"
<App>
<TextArea rows="10" />
</App>
```
%-PROP-END
%-PROP-START validationStatus
This prop is used to visually indicate status changes reacting to form field validation.
```xmlui-pg copy display name="Example: validationStatus"
<App>
<TextArea />
<TextArea validationStatus="valid" />
<TextArea validationStatus="warning" />
<TextArea validationStatus="error" />
</App>
```
%-PROP-END
%-EVENT-START didChange
Write in the input field and see how the `Text` underneath it is updated in parallel.
```xmlui-pg copy display name="Example: didChange"
<App var.field="">
<TextArea
initialValue="{field}"
onDidChange="(val) => field = val"
/>
<Text value="{field}" />
</App>
```
%-EVENT-END
%-EVENT-START gotFocus
Clicking on the `TextArea` in the example demo changes the label text.
Note how clicking elsewhere resets the text to the original.
```xmlui-pg copy display name="Example: gotFocus/lostFocus"
<App>
<TextArea
initialValue="{focused === true ? 'I got focused!' : 'I lost focus...'}"
onGotFocus="focused = true"
onLostFocus="focused = false"
var.focused="{false}" />
</App>
```
%-EVENT-END
%-API-START focus
```xmlui-pg copy display name="Example: focus"
<App>
<Button label="Trigger Focus" onClick="inputComponent.focus()" />
<TextArea id="inputComponent" />
</App>
```
%-API-END
%-API-START value
In the example below, typing in the `TextArea` will also display the length of the text typed into it above the field:
```xmlui-pg copy display name="Example: value"
<App>
<Text value="TextArea content length: {inputComponent.value.length}" />
<TextArea id="inputComponent" />
</App>
```
%-API-END
%-API-START setValue
```xmlui-pg copy display name="Example: setValue"
<App var.changes="">
<TextArea
id="inputField"
readOnly="true"
onDidChange="changes++" />
<HStack>
<Button
label="Check"
onClick="inputField.setValue('example ')" />
<Text value="Number of changes: {changes}" />
</HStack>
</App>
```
%-API-END
```
--------------------------------------------------------------------------------
/xmlui/scripts/generate-docs/generate-summary-files.mjs:
--------------------------------------------------------------------------------
```
import * as fs from "fs";
import * as path from "path";
import { FOLDERS } from "./folders.mjs";
import { createTable, getSectionBeforeAndAfter } from "./utils.mjs";
import { logger, LOGGER_LEVELS, processError } from "./logger.mjs";
import {
COMPONENT_STATUS_CONFIG,
SUMMARY_FILE_CONFIG,
OUTPUT_FILES
} from "./constants.mjs";
const ACCEPTED_STATUSES = COMPONENT_STATUS_CONFIG.ACCEPTED_STATUSES;
const DEFAULT_STATUS = COMPONENT_STATUS_CONFIG.DEFAULT_STATUS;
logger.setLevels(LOGGER_LEVELS.warning, LOGGER_LEVELS.error);
logger.info("Generating summary files...");
// components
logger.info("Components summary");
const componentsMetaFolder = path.join(FOLDERS.script, "metadata", "components");
const componentsOutFolder = path.join(FOLDERS.docsRoot, "content", "components");
generateComponentsSummary(
path.join(componentsMetaFolder, OUTPUT_FILES.METADATA_JSON),
"",
componentsOutFolder,
"_overview.md",
SUMMARY_FILE_CONFIG.COMPONENTS_OVERVIEW_HEADER,
);
logger.info("Components summary DONE");
// NOTE: Unused
// extensions
// logger.info("Extensions summary");
// const extensionsMetaFolder = path.join(FOLDERS.script, "metadata", "extensions");
// fs.readdirSync(extensionsMetaFolder).forEach((file) => {
// if (path.extname(file) === ".json") {
// let extensionName = path.basename(file, ".json");
// extensionName = extensionName.replace("-metadata", "");
// const extensionOutFolder = path.join(FOLDERS.docsRoot, "content", "extensions", extensionName);
// generateComponentsSummary(
// path.join(extensionsMetaFolder, file),
// `extensions/${extensionName}`,
// extensionOutFolder,
// `_overview.md`,
// SUMMARY_FILE_CONFIG.PACKAGE_COMPONENTS_HEADER,
// );
// }
// });
// logger.info("Extensions summary DONE");
// ---
/**
* Generates the component summary table and adds it to the provided file.
* The function looks for the summary section in the provided file and regenerates it if present,
* otherwise it appends one to the end of the file.
* @param {string} metadataFile The path & name of the file containing the component metadata
* @param {string} urlPath the path that is used to point to the generated component article in the docs
* @param {string} outFolder The folder to add the summary to
* @param {string} summaryFile The name of the file (with extesion) to add the summary to
* @param {string} summarySectionName The section to look for and add the summary to
* @param {boolean?} hasRowNums Whether to add row numbers to the summary table
*/
function generateComponentsSummary(
metadataFile,
urlPath,
outFolder,
summaryFile,
summarySectionName,
hasRowNums,
) {
try {
if (!fs.existsSync(metadataFile)) {
throw new Error(
`Metadata file does not exist: ${metadataFile}. Please run generate-docs first.`,
);
}
const outFile = path.join(outFolder, summaryFile ?? `_overview.md`);
if (!fs.existsSync(outFile)) {
fs.writeFileSync(outFile, "");
}
const metadata = JSON.parse(fs.readFileSync(metadataFile, "utf8"));
const table = createSummaryTable(metadata, urlPath, outFolder, hasRowNums);
const fileContents = fs.readFileSync(outFile, "utf8");
let { beforeSection, afterSection } = getSectionBeforeAndAfter(
fileContents,
summarySectionName,
);
beforeSection = beforeSection.length > 0 ? beforeSection + "\n\n" : beforeSection;
const summary = beforeSection + `${summarySectionName}\n\n` + table + "\n" + afterSection;
fs.writeFileSync(outFile, summary);
} catch (error) {
processError(error);
}
}
/**
* Translates a metadata object to a summary table in markdown.
* @param {Record<string, any>} metadata object with components and their metadata
* @param {string} urlPath the path that is used to point to the generated component article in the docs
* @param {string} filePath the path that contains the generated component article file itself
* @param {boolean?} hasRowNums should the table have numbered rows
* @returns stringified markdown table
*/
function createSummaryTable(metadata, urlPath, filePath, hasRowNums = false) {
const headers = [{ value: "Component", style: "center" }, "Description"];
const rows = metadata
.sort((a, b) => a.displayName.localeCompare(b.displayName))
.filter((component) => {
const componentStatus = component.status ?? DEFAULT_STATUS;
return !ACCEPTED_STATUSES.includes(componentStatus) ? false : true;
})
.map((component) => {
const componentFilePath = path.join(filePath, `${component.displayName}.md`);
const componentUrl = urlPath ? `./${urlPath}/${component.displayName}` : `./${component.displayName}`;
return [
fs.existsSync(componentFilePath)
? `[${component.displayName}](${componentUrl})`
: component.displayName,
component.description,
];
});
return createTable({ headers, rows, rowNums: hasRowNums });
}
```
--------------------------------------------------------------------------------
/xmlui/src/components/Tooltip/Tooltip.tsx:
--------------------------------------------------------------------------------
```typescript
import { createComponentRenderer } from "../../components-core/renderers";
import { createMetadata } from "../metadata-helpers";
import { parseScssVar } from "../../components-core/theming/themeVars";
import { Tooltip } from "./TooltipNative";
import type { TooltipProps } from "./TooltipNative";
import styles from "./Tooltip.module.scss";
const COMP = "Tooltip";
export const TooltipMd = createMetadata({
status: "stable",
description: "A tooltip component that displays text when hovering over trigger content.",
props: {
text: {
description: "The text content to display in the tooltip",
type: "string",
isRequired: false,
},
markdown: {
description: "The markdown content to display in the tooltip",
type: "string",
isRequired: false,
},
tooltipTemplate: {
description: "The template for the tooltip content",
type: "Component",
isRequired: false,
},
delayDuration: {
description:
"The duration from when the mouse enters a tooltip trigger until the tooltip opens (in ms)",
type: "number",
defaultValue: 700,
},
skipDelayDuration: {
description:
"How much time a user has to enter another trigger without incurring a delay again (in ms)",
type: "number",
defaultValue: 300,
},
defaultOpen: {
description: "The open state of the tooltip when it is initially rendered",
type: "boolean",
defaultValue: false,
},
showArrow: {
description: "Whether to show the arrow pointing to the trigger element",
type: "boolean",
defaultValue: false,
},
side: {
description: "The preferred side of the trigger to render against when open",
type: "string",
availableValues: ["top", "right", "bottom", "left"],
defaultValue: "top",
},
align: {
description: "The preferred alignment against the trigger",
type: "string",
availableValues: ["start", "center", "end"],
defaultValue: "center",
},
sideOffset: {
description: "The distance in pixels from the trigger",
type: "number",
defaultValue: 4,
},
alignOffset: {
description: "An offset in pixels from the 'start' or 'end' alignment options",
type: "number",
defaultValue: 0,
},
avoidCollisions: {
description:
"When true, overrides the side and align preferences to prevent collisions with boundary edges",
type: "boolean",
defaultValue: true,
},
},
events: {},
apis: {},
contextVars: {},
themeVars: parseScssVar(styles.themeVars),
limitThemeVarsToComponent: true,
defaultThemeVars: {
[`backgroundColor-${COMP}`]: "$color-surface-0",
[`border-${COMP}`]: "none",
[`textColor-${COMP}`]: "$textcolor-primary",
[`borderRadius-${COMP}`]: "4px",
[`fontSize-${COMP}`]: "15px",
[`lineHeight-${COMP}`]: "1",
[`paddingTop-${COMP}`]: "10px",
[`paddingBottom-${COMP}`]: "10px",
[`paddingLeft-${COMP}`]: "15px",
[`paddingRight-${COMP}`]: "15px",
[`boxShadow-${COMP}`]:
"hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px",
[`fill-arrow-${COMP}`]: "$color-surface-200",
[`stroke-arrow-${COMP}`]: "$color-surface-200",
[`strokeWidth-arrow-${COMP}`]: "0",
[`animationDuration-${COMP}`]: "400ms",
[`animation-${COMP}`]: "cubic-bezier(0.16, 1, 0.3, 1)",
dark: {
[`backgroundColor-${COMP}`]: "$color-surface-200",
},
},
});
export const tooltipComponentRenderer = createComponentRenderer(
"Tooltip",
TooltipMd,
({ node, extractValue, renderChild, layoutContext }) => {
// If there are no children, do not render anything
if (!node.children || node.children.length === 0) {
return null;
}
// If text is not provided, do not render anything
const text = extractValue.asOptionalString(node.props.text);
const markdown = extractValue.asOptionalString(node.props.markdown);
const tooltipTemplate = node.props.tooltipTemplate;
return (
<Tooltip
text={text}
markdown={markdown}
tooltipTemplate={renderChild(tooltipTemplate)}
delayDuration={extractValue.asOptionalNumber(node.props.delayDuration)}
skipDelayDuration={extractValue.asOptionalNumber(node.props.skipDelayDuration)}
defaultOpen={extractValue.asOptionalBoolean(node.props.defaultOpen)}
showArrow={extractValue.asOptionalBoolean(node.props.showArrow)}
side={extractValue.asOptionalString(node.props.side) as TooltipProps["side"]}
align={extractValue.asOptionalString(node.props.align) as TooltipProps["align"]}
sideOffset={extractValue.asOptionalNumber(node.props.sideOffset)}
alignOffset={extractValue.asOptionalNumber(node.props.alignOffset)}
avoidCollisions={extractValue.asOptionalBoolean(node.props.avoidCollisions)}
>
{renderChild(node.children, layoutContext)}
</Tooltip>
);
},
);
```
--------------------------------------------------------------------------------
/xmlui/src/testing/themed-app-test-helpers.ts:
--------------------------------------------------------------------------------
```typescript
import type { BrowserContext, Locator, Page } from "@playwright/test";
import { xmlUiMarkupToComponent } from "../components-core/xmlui-parser";
import type { ThemeDefinition } from "../abstractions/ThemingDefs";
import type { ComponentDef, CompoundComponentDef } from "../abstractions/ComponentDefs";
import type { StandaloneAppDescription } from "../components-core/abstractions/standalone";
type EntryPoint = string | Record<string, any>;
type UnparsedAppDescription = Omit<Partial<StandaloneAppDescription>, "entryPoint"> & {
entryPoint?: EntryPoint;
};
export type ThemeTestDesc = {
themeVar: string;
themeVarAsCSS: string;
expected: string;
dependsOnVars?: Record<string, string>;
};
function parseComponent(entryPoint: ComponentDef<any> | string) {
if (typeof entryPoint === "string") {
return xmlUiMarkupToComponent(entryPoint).component;
}
return entryPoint;
}
export async function initApp(
page: Page,
appDescription: UnparsedAppDescription,
url: string = "/",
resources = {},
) {
const { entryPoint, components } = appDescription;
const _appDescription: StandaloneAppDescription = {
...appDescription,
name: appDescription.name || "Test App",
entryPoint: parseComponent(entryPoint as ComponentDef) as ComponentDef,
resources,
components: (!components
? undefined
: Array.isArray(components)
? components.map((comp) => parseComponent(comp as unknown as ComponentDef))
: [parseComponent(components)]) as CompoundComponentDef[],
};
await page.addInitScript((app) => {
// @ts-ignore
window.TEST_ENV = app;
}, _appDescription);
await page.goto(url);
}
export async function prepPage(
co: BrowserContext,
appDesc: StandaloneAppDescription,
url: string = "/",
) {
const context = co;
const page = await context.newPage();
await initApp(page, appDesc, url);
return page;
}
export async function initThemedApp(
page: Page,
entryPoint: EntryPoint,
theme: Partial<ThemeDefinition>,
) {
theme.id ??= "testTheme";
theme.name ??= "Custom Test theme";
await initApp(page, { entryPoint, defaultTheme: theme.id, themes: [theme as any] });
}
/**
* @param percentage a percenage value as a string, like "40%"
* @param scalarOf100Percent The value to multiply the percentage by
*/
export function scalePercentBy(scalarOf100Percent: number, percentage: string) {
if (!percentage.endsWith("%")) {
throw new Error("argument doesn't end with % sign");
}
const percentageNum = Number(percentage.slice(0, -1));
return (scalarOf100Percent / 100) * percentageNum;
}
export function getBoundingRect(locator: Locator) {
return locator.evaluate((element) => element.getBoundingClientRect());
}
export async function getFullRectangle(locator: Locator) {
const boundingRect = await locator.evaluate((element) => element.getBoundingClientRect());
const margins = await getElementStyles(locator, [
"margin-left",
"margin-right",
"margin-top",
"margin-bottom",
]);
const marginLeft = parseFloat(margins["margin-left"]);
const marginRight = parseFloat(margins["margin-right"]);
const marginTop = parseFloat(margins["margin-top"]);
const marginBottom = parseFloat(margins["margin-bottom"]);
const width = boundingRect.width + marginLeft + marginRight;
const height = boundingRect.height + marginTop + marginBottom;
const left = boundingRect.left - marginLeft;
const right = boundingRect.right + marginRight;
const top = boundingRect.top - marginTop;
const bottom = boundingRect.bottom + marginBottom;
return { width, height, left, right, top, bottom };
}
export async function isElementOverflown(locator: Locator, direction: "x" | "y" | "both" = "both") {
const [width, height, scrollWidth, scrollHeight] = await locator.evaluate((element) => [
element.clientWidth,
element.clientHeight,
element.scrollWidth,
element.scrollHeight,
]);
if (direction === "x") return scrollWidth > width;
if (direction === "y") return scrollHeight > height;
return scrollWidth > width && scrollHeight > height;
}
export function pixelStrToNum(pixelStr: string) {
return Number(pixelStr.replace("px", ""));
}
export function getElementStyle(locator: Locator, style: string) {
return locator.evaluate(
(element, style) => window.getComputedStyle(element).getPropertyValue(style),
style,
);
}
/**
* Retreives all the provided style properties from the locator
* @returns an object with the keys being the elements of the styles argument
*/
export function getElementStyles(locator: Locator, styles: string[] = []) {
return locator.evaluate(
(element, styles) =>
Object.fromEntries(
styles.map((styleName) => [
styleName,
window.getComputedStyle(element).getPropertyValue(styleName),
]),
),
styles,
);
}
export async function getStyle(page: Page, testId: string, style: string) {
const locator = page.getByTestId(testId);
return await getElementStyle(locator, style);
}
```
--------------------------------------------------------------------------------
/xmlui/src/components/ModalDialog/ModalDialog.tsx:
--------------------------------------------------------------------------------
```typescript
import styles from "./ModalDialog.module.scss";
import { createComponentRenderer } from "../../components-core/renderers";
import { parseScssVar } from "../../components-core/theming/themeVars";
import { paddingSubject } from "../../components-core/theming/themes/base-utils";
import { MemoizedItem } from "../container-helpers";
import { ModalDialog, ModalDialogFrame, defaultProps } from "./ModalDialogNative";
import { createMetadata, d } from "../metadata-helpers";
const COMP = "ModalDialog";
export const ModalDialogMd = createMetadata({
status: "stable",
description:
"`ModalDialog` creates overlay dialogs that appear on top of the main interface, " +
"ideal for forms, confirmations, detailed views, or any content that requires " +
"focused user attention. Dialogs are programmatically opened using the `open()` " +
"method and can receive parameters for dynamic content.",
parts: {
content: {
description: "The main content area of the modal dialog.",
},
title: {
description: "The title area of the modal dialog.",
},
},
props: {
fullScreen: {
description:
`Toggles whether the dialog encompasses the whole UI (\`true\`) or not and has a minimum ` +
`width and height (\`false\`).`,
valueType: "boolean",
defaultValue: defaultProps.fullScreen,
},
title: d(`Provides a prestyled heading to display the intent of the dialog.`),
closeButtonVisible: {
description: `Shows (\`true\`) or hides (\`false\`) the visibility of the close button on the dialog.`,
valueType: "boolean",
defaultValue: defaultProps.closeButtonVisible,
},
},
events: {
open: d(
`This event is fired when the \`${COMP}\` is opened either via a \`when\` or an ` +
`imperative API call (\`open()\`).`,
),
close: d(
`This event is fired when the close button is pressed or the user clicks outside ` +
`the \`${COMP}\`.`,
),
},
apis: {
close: {
description:
`This method is used to close the \`${COMP}\`. Invoke it using \`modalId.close()\` ` +
`where \`modalId\` refers to a \`ModalDialog\` component.`,
signature: "close(): void",
},
open: {
description:
"This method imperatively opens the modal dialog. You can pass an arbitrary number " +
"of parameters to the method. In the `ModalDialog` instance, you can access those " +
"with the `\$param` and `\$params` context values.",
signature: "open(...params: any[]): void",
parameters: {
params: "An arbitrary number of parameters that can be used to pass data to the dialog.",
},
},
},
contextVars: {
$param: d("First parameter passed to the `open()` method"),
$params: d(
"Array of all parameters passed to `open()` method (access with `$params[0]`, `$params[1]`, etc.)",
),
},
themeVars: parseScssVar(styles.themeVars),
defaultThemeVars: {
...paddingSubject(COMP, { all: "$space-7" }),
[`backgroundColor-${COMP}`]: "$backgroundColor-primary",
[`backgroundColor-overlay-${COMP}`]: "$backgroundColor-overlay",
[`textColor-${COMP}`]: "$textColor-primary",
[`borderRadius-${COMP}`]: "$borderRadius",
[`fontFamily-${COMP}`]: "$fontFamily",
[`maxWidth-${COMP}`]: "450px",
[`marginBottom-title-${COMP}`]: "0",
},
});
export const modalViewComponentRenderer = createComponentRenderer(
COMP,
ModalDialogMd,
({
node,
contextVars,
extractValue,
className,
renderChild,
lookupEventHandler,
registerComponentApi,
layoutContext,
}) => {
// --- If the ModalDialog is not inside a ModalDialogFrame, wrap it in one.
if (!layoutContext?._insideModalFrame) {
// --- Context variables are now directly available via contextVars parameter
return (
<ModalDialogFrame
isInitiallyOpen={extractValue(node.when) !== undefined}
registerComponentApi={registerComponentApi}
onClose={lookupEventHandler("close")}
onOpen={lookupEventHandler("open")}
renderDialog={({ openParams, ref }) => {
return (
<MemoizedItem
node={node}
renderChild={renderChild}
layoutContext={{ _insideModalFrame: true }}
contextVars={{
...contextVars,
$param: openParams?.[0],
$params: openParams
}}
/>
);
}}
/>
);
}
return (
<ModalDialog
className={className}
fullScreen={extractValue.asOptionalBoolean(node.props?.fullScreen)}
title={extractValue(node.props?.title)}
closeButtonVisible={extractValue.asOptionalBoolean(node.props.closeButtonVisible)}
externalAnimation={extractValue.asOptionalBoolean(node.props.externalAnimation)}
>
{renderChild(node.children, { type: "Stack" })}
</ModalDialog>
);
},
);
```
--------------------------------------------------------------------------------
/xmlui/src/components/ExpandableItem/ExpandableItem.tsx:
--------------------------------------------------------------------------------
```typescript
import styles from "./ExpandableItem.module.scss";
import { createComponentRenderer } from "../../components-core/renderers";
import { parseScssVar } from "../../components-core/theming/themeVars";
import { iconPositionMd } from "../abstractions";
import { createMetadata, d, dComponent } from "../../components/metadata-helpers";
import { defaultExpandableItemProps, ExpandableItem } from "./ExpandableItemNative";
const COMP = "ExpandableItem";
export const ExpandableItemMd = createMetadata({
status: "stable",
description:
"`ExpandableItem` creates expandable/collapsible section, similar to the HTML " +
"details disclosure element. When the user clicks on the `summary` the content " +
"expands or collapses.",
props: {
summary: dComponent("The summary content that is always visible and acts as the trigger."),
initiallyExpanded: {
description: "Determines if the component is initially expanded when rendered.",
valueType: "boolean",
defaultValue: defaultExpandableItemProps.initiallyExpanded,
},
enabled: {
description:
"When true, the expandable item can be opened and closed. When false, it cannot be toggled.",
valueType: "boolean",
defaultValue: defaultExpandableItemProps.enabled,
},
iconCollapsed: {
description: "The icon to display when the item is collapsed.",
valueType: "string",
defaultValue: defaultExpandableItemProps.iconCollapsed,
},
iconExpanded: {
description: "The icon to display when the item is expanded.",
valueType: "string",
defaultValue: defaultExpandableItemProps.iconExpanded,
},
iconPosition: {
description: "Determines the position of the icon (start or end).",
valueType: "string",
availableValues: iconPositionMd,
defaultValue: defaultExpandableItemProps.iconPosition,
},
withSwitch: {
description: "When true, a switch is used instead of an icon to toggle the expanded state.",
valueType: "boolean",
defaultValue: defaultExpandableItemProps.withSwitch,
},
},
events: {
expandedChange: d(
`This event fires when the expandable item is expanded or collapsed. It provides a boolean value indicating the new state.`,
),
},
apis: {
expand: {
description: `This method expands the item.`,
signature: "expand(): void",
},
collapse: {
description: `This method collapses the item.`,
signature: "collapse(): void",
},
toggle: {
description: `This method toggles the item's expanded state.`,
signature: "toggle(): void",
},
isExpanded: {
description: `This method returns a boolean indicating whether the item is currently expanded.`,
signature: "isExpanded(): boolean",
},
},
themeVars: parseScssVar(styles.themeVars),
limitThemeVarsToComponent: true,
defaultThemeVars: {
[`backgroundColor-${COMP}`]: "transparent",
[`color-${COMP}`]: "$textColor-primary",
[`color-${COMP}--disabled`]: "$textColor--disabled",
[`fontFamily-${COMP}`]: "$fontFamily",
[`borderColor-${COMP}`]: "$borderColor",
[`borderWidth-${COMP}`]: "0",
[`borderBottomWidth-${COMP}`]: "1px",
[`borderStyle-${COMP}`]: "solid",
[`borderRadius-${COMP}`]: "0",
[`paddingTop-${COMP}`]: "$space-2",
[`paddingBottom-${COMP}`]: "$space-2",
[`paddingLeft-${COMP}`]: "$space-0",
[`paddingRight-${COMP}`]: "$space-0",
[`gap-${COMP}`]: "$space-2",
[`paddingLeft-content-${COMP}`]: "$space-3",
[`paddingRight-content-${COMP}`]: "$space-3",
[`paddingVertical-content-${COMP}`]: "$space-2",
[`transition-${COMP}`]: "0.2s ease",
},
});
export const expandableItemComponentRenderer = createComponentRenderer(
COMP,
ExpandableItemMd,
({ node, renderChild, lookupEventHandler, registerComponentApi, extractValue, className }) => {
return (
<ExpandableItem
summary={extractValue(node.props?.summary)}
initiallyExpanded={extractValue.asOptionalBoolean(
node.props.initiallyExpanded,
defaultExpandableItemProps.initiallyExpanded,
)}
enabled={extractValue.asOptionalBoolean(
node.props.enabled,
defaultExpandableItemProps.enabled,
)}
iconExpanded={
extractValue(node.props?.iconExpanded) ?? defaultExpandableItemProps.iconExpanded
}
iconCollapsed={
extractValue(node.props?.iconCollapsed) ?? defaultExpandableItemProps.iconCollapsed
}
iconPosition={
extractValue.asOptionalString(node.props.iconPosition) ??
defaultExpandableItemProps.iconPosition
}
withSwitch={extractValue.asOptionalBoolean(
node.props.withSwitch,
defaultExpandableItemProps.withSwitch,
)}
onExpandedChange={lookupEventHandler("expandedChange")}
className={className}
registerComponentApi={registerComponentApi}
>
{renderChild(node.children)}
</ExpandableItem>
);
},
);
```
--------------------------------------------------------------------------------
/docs/content/components/Slot.md:
--------------------------------------------------------------------------------
```markdown
# Slot [#slot]
Placeholder in a reusable component. Signs the slot where the component's injected children should be rendered.
## Using Slot [#using-slot]
You can add `Slot` to a user-defined component as a placeholder. When you refer to the particular component in the markup, the children are transposed to the `Slot`.
```xmlui-pg name="Using Slot"
---app copy display {3-5}
<App name="XMLUI Hello World">
<ActionBar>
<Button label="Create" onClick="window.alert('Create clicked')" />
<Button label="Edit" onClick="window.alert('Edit clicked')" />
<Button label="Delete" onClick="window.alert('Delete clicked')" />
</ActionBar>
</App>
---desc
The app flows down three buttons to the `ActionBar` to render.
---comp copy display {5}
<Component name="ActionBar">
<Card>
<H3>Use these actions</H3>
<HStack>
<Slot />
</HStack>
</Card>
</Component>
---desc
`ActionBar` renders the passed children by replacing `Slot` with them.
```
## Default Slot content [#default-slot-content]
You can provide default content for the `Slot`. If the user-defined component does not have any children, XMLUI will render the default content.
```xmlui-pg
---app copy display name="Define default Slot content"
<App>
<ActionBar />
</App>
---comp copy display {6}
<Component name="ActionBar">
<Card>
<H3>Use these actions</H3>
<HStack>
<Slot>
<Button label="Default" onClick="window.alert('Default clicked')" />
</Slot>
</HStack>
</Card>
</Component>
```
## Named Slots [#named-slots]
You can add multiple slots to a user-defined component; you can have a default slot and several *named* slots. Slot names should end with `template`, and you can use the `<property>` markup syntax to declare their values.
```xmlui-pg
---app copy display name="Using named Slots" {4, 7, 9-11}
<App>
<ActionBar>
<property name="headerTemplate">
<H2>Click one of these actions</H2>
</property>
<property name="footerTemplate">
<Text>Footer content goes here</Text>
</property>
<Button label="Create" onClick="window.alert('Create clicked')" />
<Button label="Edit" onClick="window.alert('Edit clicked')" />
<Button label="Delete" onClick="window.alert('Delete clicked')" />
</ActionBar>
</App>
---desc
This app passes a header template and a footer template slot to the `ActionBar` component and also declares buttons to render.
---comp copy display {3-5, 7-9, 11}
<Component name="ActionBar">
<Card>
<Slot name="headerTemplate">
<H3>Use these actions</H3>
</Slot>
<HStack>
<Slot>
<Button label="Default" onClick="window.alert('Default clicked')" />
</Slot>
</HStack>
<Slot name="footerTemplate" />
</Card>
</Component>
---desc
XMLUI finds the appropriate slots by their name and transposes their content received from the app. Just like the default slot, named slots can have default content.
```
> [!WARN] XMLUI will display an error message when the `Slot` name does not end with "Template".
## Template properties [#template-properties]
The user-defined component can provide properties for the actual template.
```xmlui-pg
---app copy display name="Using template properties" /header/ /name="headerTemplate"/ /$processedHeader/
<App>
<ActionBar header="Action Bar Example">
<property name="headerTemplate">
<Text variant="title">{$processedHeader}</Text>
</property>
<Button label="Create" onClick="window.alert('Create clicked')" />
<Button label="Edit" onClick="window.alert('Edit clicked')" />
<Button label="Delete" onClick="window.alert('Delete clicked')" />
</ActionBar>
</App>
---desc
The app passes a `header` property value to the `ActionBar` component. `Actionbar` utilizes this property, transforms it, and passes it back to the template in the `$processedHeader` context variable so that the app can use it. `$processHeader` is available only within the `headerTemplate` slot.
---comp copy display /transformedHeader/ /processedHeader="{transformedHeader}"/
<Component name="ActionBar">
<Card var.transformedHeader="*** {$props.header.toUpperCase()} ***">
<Slot name="headerTemplate" processedHeader="{transformedHeader}" >
<H3>{transformedHeader}</H3>
</Slot>
<HStack>
<Slot>
<Button label="Default" onClick="window.alert('Default clicked')" />
</Slot>
</HStack>
</Card>
</Component>
---desc
`Actionbar` transforms the `header` property and stores it internally in the `transformedHeader` variable. It utilizes the value in the default header definition and also passes it back to the actual template definition with the `processedHeader` name. XMLUI creates the `$processedHeader` context variable from this name.
```
## Properties [#properties]
### `name` [#name]
This optional property defines the name of the slot.
## Events [#events]
This component does not have any events.
## Exposed Methods [#exposed-methods]
This component does not expose any methods.
## Styling [#styling]
This component does not have any styles.
```