This is page 99 of 190. Use http://codebase.md/xmlui-org/xmlui/%7Bnode.props.src?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .changeset
│ └── config.json
├── .eslintrc.cjs
├── .github
│ ├── build-checklist.png
│ ├── ISSUE_TEMPLATE
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ └── workflows
│ ├── deploy-blog-optimized.yml
│ ├── deploy-blog-swa.yml
│ ├── deploy-blog.yml
│ ├── deploy-docs-optimized.yml
│ ├── deploy-docs-swa.yml
│ ├── deploy-docs.yml
│ ├── prepare-versions.yml
│ ├── release-packages.yml
│ ├── run-all-tests.yml
│ └── run-smoke-tests.yml
├── .gitignore
├── .prettierrc.js
├── .vscode
│ ├── launch.json
│ └── settings.json
├── blog
│ ├── .gitignore
│ ├── .gitkeep
│ ├── CHANGELOG.md
│ ├── extensions.ts
│ ├── index.html
│ ├── index.ts
│ ├── package.json
│ ├── public
│ │ ├── blog
│ │ │ ├── images
│ │ │ │ ├── an-advanced-codefence.gif
│ │ │ │ ├── an-advanced-codefence.mp4
│ │ │ │ ├── blog-page-component.png
│ │ │ │ ├── blog-scrabble.png
│ │ │ │ ├── codefence-runner.png
│ │ │ │ ├── integrated-blog-search.png
│ │ │ │ ├── lorem-ipsum.png
│ │ │ │ ├── playground-checkbox-source.png
│ │ │ │ ├── playground.png
│ │ │ │ ├── use-xmlui-mcp-to-find-a-howto.png
│ │ │ │ └── xmlui-demo-gallery.png
│ │ │ ├── introducing-xmlui.md
│ │ │ ├── lorem-ipsum.md
│ │ │ ├── newest-post.md
│ │ │ ├── older-post.md
│ │ │ ├── xmlui-playground.md
│ │ │ └── xmlui-powered-blog.md
│ │ ├── mockServiceWorker.js
│ │ ├── resources
│ │ │ ├── favicon.ico
│ │ │ ├── files
│ │ │ │ └── for-download
│ │ │ │ └── xmlui
│ │ │ │ └── xmlui-standalone.umd.js
│ │ │ ├── github.svg
│ │ │ ├── icons
│ │ │ │ ├── github.svg
│ │ │ │ └── rss.svg
│ │ │ ├── llms.txt
│ │ │ ├── logo-dark.svg
│ │ │ ├── logo.svg
│ │ │ ├── pg-popout.svg
│ │ │ ├── rss.svg
│ │ │ └── xmlui-logo.svg
│ │ ├── serve.json
│ │ ├── staticwebapp.config.json
│ │ └── web.config
│ ├── scripts
│ │ ├── download-latest-xmlui.js
│ │ ├── generate-rss.js
│ │ ├── get-releases.js
│ │ └── utils.js
│ ├── src
│ │ ├── components
│ │ │ ├── BlogOverview.xmlui
│ │ │ ├── BlogPage.xmlui
│ │ │ ├── LinkButton.xmlui
│ │ │ ├── PageNotFound.xmlui
│ │ │ └── Separator.xmlui
│ │ ├── config.ts
│ │ ├── Main.xmlui
│ │ └── themes
│ │ └── blog-theme.ts
│ └── tsconfig.json
├── CONTRIBUTING.md
├── docs
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── ComponentRefLinks.txt
│ ├── content
│ │ ├── _meta.json
│ │ ├── components
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── APICall.md
│ │ │ ├── App.md
│ │ │ ├── AppHeader.md
│ │ │ ├── AppState.md
│ │ │ ├── AutoComplete.md
│ │ │ ├── Avatar.md
│ │ │ ├── Backdrop.md
│ │ │ ├── Badge.md
│ │ │ ├── BarChart.md
│ │ │ ├── Bookmark.md
│ │ │ ├── Breakout.md
│ │ │ ├── Button.md
│ │ │ ├── Card.md
│ │ │ ├── Carousel.md
│ │ │ ├── ChangeListener.md
│ │ │ ├── Checkbox.md
│ │ │ ├── CHStack.md
│ │ │ ├── ColorPicker.md
│ │ │ ├── Column.md
│ │ │ ├── ContentSeparator.md
│ │ │ ├── CVStack.md
│ │ │ ├── DataSource.md
│ │ │ ├── DateInput.md
│ │ │ ├── DatePicker.md
│ │ │ ├── DonutChart.md
│ │ │ ├── DropdownMenu.md
│ │ │ ├── EmojiSelector.md
│ │ │ ├── ExpandableItem.md
│ │ │ ├── FileInput.md
│ │ │ ├── FileUploadDropZone.md
│ │ │ ├── FlowLayout.md
│ │ │ ├── Footer.md
│ │ │ ├── Form.md
│ │ │ ├── FormItem.md
│ │ │ ├── FormSection.md
│ │ │ ├── Fragment.md
│ │ │ ├── H1.md
│ │ │ ├── H2.md
│ │ │ ├── H3.md
│ │ │ ├── H4.md
│ │ │ ├── H5.md
│ │ │ ├── H6.md
│ │ │ ├── Heading.md
│ │ │ ├── HSplitter.md
│ │ │ ├── HStack.md
│ │ │ ├── Icon.md
│ │ │ ├── IFrame.md
│ │ │ ├── Image.md
│ │ │ ├── Items.md
│ │ │ ├── LabelList.md
│ │ │ ├── Legend.md
│ │ │ ├── LineChart.md
│ │ │ ├── Link.md
│ │ │ ├── List.md
│ │ │ ├── Logo.md
│ │ │ ├── Markdown.md
│ │ │ ├── MenuItem.md
│ │ │ ├── MenuSeparator.md
│ │ │ ├── ModalDialog.md
│ │ │ ├── NavGroup.md
│ │ │ ├── NavLink.md
│ │ │ ├── NavPanel.md
│ │ │ ├── NoResult.md
│ │ │ ├── NumberBox.md
│ │ │ ├── Option.md
│ │ │ ├── Page.md
│ │ │ ├── PageMetaTitle.md
│ │ │ ├── Pages.md
│ │ │ ├── Pagination.md
│ │ │ ├── PasswordInput.md
│ │ │ ├── PieChart.md
│ │ │ ├── ProgressBar.md
│ │ │ ├── Queue.md
│ │ │ ├── RadioGroup.md
│ │ │ ├── RealTimeAdapter.md
│ │ │ ├── Redirect.md
│ │ │ ├── Select.md
│ │ │ ├── Slider.md
│ │ │ ├── Slot.md
│ │ │ ├── SpaceFiller.md
│ │ │ ├── Spinner.md
│ │ │ ├── Splitter.md
│ │ │ ├── Stack.md
│ │ │ ├── StickyBox.md
│ │ │ ├── SubMenuItem.md
│ │ │ ├── Switch.md
│ │ │ ├── TabItem.md
│ │ │ ├── Table.md
│ │ │ ├── TableOfContents.md
│ │ │ ├── Tabs.md
│ │ │ ├── Text.md
│ │ │ ├── TextArea.md
│ │ │ ├── TextBox.md
│ │ │ ├── Theme.md
│ │ │ ├── TimeInput.md
│ │ │ ├── Timer.md
│ │ │ ├── ToneChangerButton.md
│ │ │ ├── ToneSwitch.md
│ │ │ ├── Tooltip.md
│ │ │ ├── Tree.md
│ │ │ ├── VSplitter.md
│ │ │ ├── VStack.md
│ │ │ ├── xmlui-animations
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── Animation.md
│ │ │ │ ├── FadeAnimation.md
│ │ │ │ ├── FadeInAnimation.md
│ │ │ │ ├── FadeOutAnimation.md
│ │ │ │ ├── ScaleAnimation.md
│ │ │ │ └── SlideInAnimation.md
│ │ │ ├── xmlui-pdf
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Pdf.md
│ │ │ ├── xmlui-spreadsheet
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Spreadsheet.md
│ │ │ └── xmlui-website-blocks
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── Carousel.md
│ │ │ ├── HelloMd.md
│ │ │ ├── HeroSection.md
│ │ │ └── ScrollToTop.md
│ │ └── extensions
│ │ ├── _meta.json
│ │ ├── xmlui-animations
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── Animation.md
│ │ │ ├── FadeAnimation.md
│ │ │ ├── FadeInAnimation.md
│ │ │ ├── FadeOutAnimation.md
│ │ │ ├── ScaleAnimation.md
│ │ │ └── SlideInAnimation.md
│ │ └── xmlui-website-blocks
│ │ ├── _meta.json
│ │ ├── _overview.md
│ │ ├── Carousel.md
│ │ ├── FancyButton.md
│ │ ├── HeroSection.md
│ │ └── ScrollToTop.md
│ ├── extensions.ts
│ ├── index.html
│ ├── index.ts
│ ├── package.json
│ ├── public
│ │ ├── feed.rss
│ │ ├── mockServiceWorker.js
│ │ ├── pages
│ │ │ ├── _meta.json
│ │ │ ├── app-structure.md
│ │ │ ├── build-editor-component.md
│ │ │ ├── build-hello-world-component.md
│ │ │ ├── components-intro.md
│ │ │ ├── context-variables.md
│ │ │ ├── forms.md
│ │ │ ├── globals.md
│ │ │ ├── glossary.md
│ │ │ ├── helper-tags.md
│ │ │ ├── hosted-deployment.md
│ │ │ ├── howto
│ │ │ │ ├── assign-a-complex-json-literal-to-a-component-variable.md
│ │ │ │ ├── chain-a-refetch.md
│ │ │ │ ├── control-cache-invalidation.md
│ │ │ │ ├── debounce-user-input-for-api-calls.md
│ │ │ │ ├── debounce-with-changelistener.md
│ │ │ │ ├── debug-a-component.md
│ │ │ │ ├── delay-a-datasource-until-another-datasource-is-ready.md
│ │ │ │ ├── delegate-a-method.md
│ │ │ │ ├── do-custom-form-validation.md
│ │ │ │ ├── expose-a-method-from-a-component.md
│ │ │ │ ├── filter-and-transform-data-from-an-api.md
│ │ │ │ ├── group-items-in-list-by-a-property.md
│ │ │ │ ├── handle-background-operations.md
│ │ │ │ ├── hide-an-element-until-its-datasource-is-ready.md
│ │ │ │ ├── make-a-set-of-equal-width-cards.md
│ │ │ │ ├── make-a-table-responsive.md
│ │ │ │ ├── make-navpanel-width-responsive.md
│ │ │ │ ├── modify-a-value-reported-in-a-column.md
│ │ │ │ ├── paginate-a-list.md
│ │ │ │ ├── pass-data-to-a-modal-dialog.md
│ │ │ │ ├── react-to-button-click-not-keystrokes.md
│ │ │ │ ├── set-the-initial-value-of-a-select-from-fetched-data.md
│ │ │ │ ├── share-a-modaldialog-across-components.md
│ │ │ │ ├── sync-selections-between-table-and-list-views.md
│ │ │ │ ├── update-ui-optimistically.md
│ │ │ │ ├── use-built-in-form-validation.md
│ │ │ │ └── use-the-same-modaldialog-to-add-or-edit.md
│ │ │ ├── howto.md
│ │ │ ├── intro.md
│ │ │ ├── layout.md
│ │ │ ├── markup.md
│ │ │ ├── mcp.md
│ │ │ ├── modal-dialogs.md
│ │ │ ├── news-and-reviews.md
│ │ │ ├── reactive-intro.md
│ │ │ ├── refactoring.md
│ │ │ ├── routing-and-links.md
│ │ │ ├── samples
│ │ │ │ ├── color-palette.xmlui
│ │ │ │ ├── color-values.xmlui
│ │ │ │ ├── shadow-sizes.xmlui
│ │ │ │ ├── spacing-sizes.xmlui
│ │ │ │ ├── swatch.xmlui
│ │ │ │ ├── theme-gallery-brief.xmlui
│ │ │ │ └── theme-gallery.xmlui
│ │ │ ├── scoping.md
│ │ │ ├── scripting.md
│ │ │ ├── styles-and-themes
│ │ │ │ ├── common-units.md
│ │ │ │ ├── layout-props.md
│ │ │ │ ├── theme-variable-defaults.md
│ │ │ │ ├── theme-variables.md
│ │ │ │ └── themes.md
│ │ │ ├── template-properties.md
│ │ │ ├── test.md
│ │ │ ├── tutorial-01.md
│ │ │ ├── tutorial-02.md
│ │ │ ├── tutorial-03.md
│ │ │ ├── tutorial-04.md
│ │ │ ├── tutorial-05.md
│ │ │ ├── tutorial-06.md
│ │ │ ├── tutorial-07.md
│ │ │ ├── tutorial-08.md
│ │ │ ├── tutorial-09.md
│ │ │ ├── tutorial-10.md
│ │ │ ├── tutorial-11.md
│ │ │ ├── tutorial-12.md
│ │ │ ├── universal-properties.md
│ │ │ ├── user-defined-components.md
│ │ │ ├── vscode.md
│ │ │ ├── working-with-markdown.md
│ │ │ ├── working-with-text.md
│ │ │ ├── xmlui-animations
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── Animation.md
│ │ │ │ ├── FadeAnimation.md
│ │ │ │ ├── FadeInAnimation.md
│ │ │ │ ├── FadeOutAnimation.md
│ │ │ │ ├── ScaleAnimation.md
│ │ │ │ └── SlideInAnimation.md
│ │ │ ├── xmlui-charts
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── BarChart.md
│ │ │ │ ├── DonutChart.md
│ │ │ │ ├── LabelList.md
│ │ │ │ ├── Legend.md
│ │ │ │ ├── LineChart.md
│ │ │ │ └── PieChart.md
│ │ │ ├── xmlui-pdf
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Pdf.md
│ │ │ └── xmlui-spreadsheet
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ └── Spreadsheet.md
│ │ ├── resources
│ │ │ ├── devdocs
│ │ │ │ ├── debug-proxy-object-2.png
│ │ │ │ ├── debug-proxy-object.png
│ │ │ │ ├── table_editor_01.png
│ │ │ │ ├── table_editor_02.png
│ │ │ │ ├── table_editor_03.png
│ │ │ │ ├── table_editor_04.png
│ │ │ │ ├── table_editor_05.png
│ │ │ │ ├── table_editor_06.png
│ │ │ │ ├── table_editor_07.png
│ │ │ │ ├── table_editor_08.png
│ │ │ │ ├── table_editor_09.png
│ │ │ │ ├── table_editor_10.png
│ │ │ │ ├── table_editor_11.png
│ │ │ │ ├── table-editor-01.png
│ │ │ │ ├── table-editor-02.png
│ │ │ │ ├── table-editor-03.png
│ │ │ │ ├── table-editor-04.png
│ │ │ │ ├── table-editor-06.png
│ │ │ │ ├── table-editor-07.png
│ │ │ │ ├── table-editor-08.png
│ │ │ │ ├── table-editor-09.png
│ │ │ │ └── xmlui-rendering-of-tiptap-markdown.png
│ │ │ ├── favicon.ico
│ │ │ ├── files
│ │ │ │ ├── clients.json
│ │ │ │ ├── daily-revenue.json
│ │ │ │ ├── dashboard-stats.json
│ │ │ │ ├── demo.xmlui
│ │ │ │ ├── demo.xmlui.xs
│ │ │ │ ├── downloads
│ │ │ │ │ └── downloads.json
│ │ │ │ ├── for-download
│ │ │ │ │ ├── index-with-api.html
│ │ │ │ │ ├── index.html
│ │ │ │ │ ├── mockApi.js
│ │ │ │ │ ├── start-darwin.sh
│ │ │ │ │ ├── start-linux.sh
│ │ │ │ │ ├── start.bat
│ │ │ │ │ └── xmlui
│ │ │ │ │ └── xmlui-standalone.umd.js
│ │ │ │ ├── getting-started
│ │ │ │ │ ├── cl-tutorial-final.zip
│ │ │ │ │ ├── cl-tutorial.zip
│ │ │ │ │ ├── cl-tutorial2.zip
│ │ │ │ │ ├── cl-tutorial3.zip
│ │ │ │ │ ├── cl-tutorial4.zip
│ │ │ │ │ ├── cl-tutorial5.zip
│ │ │ │ │ ├── cl-tutorial6.zip
│ │ │ │ │ ├── getting-started.zip
│ │ │ │ │ ├── hello-xmlui.zip
│ │ │ │ │ ├── xmlui-empty.zip
│ │ │ │ │ └── xmlui-starter.zip
│ │ │ │ ├── howto
│ │ │ │ │ └── component-icons
│ │ │ │ │ └── up-arrow.svg
│ │ │ │ ├── invoices.json
│ │ │ │ ├── monthly-status.json
│ │ │ │ ├── news-and-reviews.json
│ │ │ │ ├── products.json
│ │ │ │ ├── releases.json
│ │ │ │ ├── tutorials
│ │ │ │ │ ├── datasource
│ │ │ │ │ │ └── api.ts
│ │ │ │ │ └── p2do
│ │ │ │ │ ├── api.ts
│ │ │ │ │ └── todo-logo.svg
│ │ │ │ └── xmlui.json
│ │ │ ├── icons
│ │ │ │ ├── github.svg
│ │ │ │ └── rss.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
│ │ ├── staticwebapp.config.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
│ │ │ ├── LinkButton.xmlui
│ │ │ ├── NameValue.xmlui
│ │ │ ├── PageNotFound.xmlui
│ │ │ ├── PaletteItem.xmlui
│ │ │ ├── Palettes.xmlui
│ │ │ ├── SectionHeader.xmlui
│ │ │ ├── Separator.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.module.scss
│ │ │ ├── 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.tsx
│ │ │ ├── StandalonePlayground.tsx
│ │ │ ├── StandalonePlaygroundNative.module.scss
│ │ │ ├── StandalonePlaygroundNative.tsx
│ │ │ ├── ThemeSwitcher.module.scss
│ │ │ ├── ThemeSwitcher.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
│ ├── component-metadata.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
│ ├── theme-variables-refactoring.md
│ ├── ud-components.md
│ └── xmlui-repo.md
├── package.json
├── scripts
│ ├── coverage-only.js
│ ├── e2e-test-summary.js
│ ├── extract-component-metadata.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
│ ├── generate-metadata-markdown.js
│ ├── 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.module.scss
│ │ │ │ ├── LabelList.spec.ts
│ │ │ │ ├── LabelList.tsx
│ │ │ │ └── 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
│ │ │ └── test-padding.xmlui
│ │ ├── 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.json
├── tsdown.config.ts
├── vite.config.ts
└── vitest.config.ts
```
# Files
--------------------------------------------------------------------------------
/docs/public/pages/layout.md:
--------------------------------------------------------------------------------
```markdown
1 | # Layout
2 |
3 | An XMLUI app is a hierarchical component tree in which parent components nest their children. Components like `HStack`, `VStack`, and `FlowLayout` use specialized layout strategies to arrange their children.
4 |
5 | ## Viewport
6 |
7 | Each component has a rectangular UI patch for rendering its content (and nested children). This is the component's **viewport**. The component decides (according to its rendering strategy) how it places its contents into the viewport. It may fill it partially, stretch the content to fill the entire viewport, or even overflow it vertically and/or horizontally.
8 |
9 | The following app contains two components, an `App`, and a `Text`:
10 |
11 | ```xmlui-pg display name="Example: viewport"
12 | <App border="2px dotted red">
13 | <Text width="30%" border="2px dotted green">Hello from XMLUI</Text>
14 | </App>
15 | ```
16 |
17 | The borders mark the viewport boundaries of the components:
18 |
19 | - `App`: The dotted red border is the app's viewport boundary. An `App` has the entire browser window as its viewport; however, it reserves some space to the left and right for scrollbars (to avoid viewport resizing when a vertical scrollbar appears or gets removed).
20 | - `Text`: The dotted green border is the text's viewport boundary. Its parent, `App`, uses some padding around its children.
21 |
22 | ## Orientation
23 |
24 | When rendering its children, a component may render them with vertical or horizontal orientation.
25 |
26 | - Vertical orientation: Each child begins a new row when its parent displays it.
27 | - Horizontal orientation: Each child appears in the same row as its previous sibling. In case of overflow the component can decide to push the child to a new row.
28 |
29 | `App` uses vertical orientation by default; `HStack` (horizontal stack) uses horizontal orientation.
30 |
31 |
32 | ```xmlui-pg display name="Example: orientation"
33 | <App>
34 | <Text>First item</Text>
35 | <HStack>
36 | <Text>Second item</Text>
37 | <Text>Third item</Text>
38 | <Text>Fourth item</Text>
39 | </HStack>
40 | <Text>Fifth item</Text>
41 | </App>
42 | ```
43 |
44 | ## Direction
45 |
46 | Some languages (such as Hebrew and Arabic) are read from right to left. XMLUI components use this information to change their children's rendering direction. This example shows right-to-left.
47 |
48 | ```xmlui-pg copy display name="Example: direction" /direction="rtl"/
49 | <App direction="rtl">
50 | <Text>First item</Text>
51 | <HStack>
52 | <Text>Second item</Text>
53 | <Text>Third item</Text>
54 | <Text>Fourth item</Text>
55 | </HStack>
56 | <Text>Fifth item</Text>
57 | </App>
58 | ```
59 |
60 | ## Paddings and gaps
61 |
62 | Each component may adjust the padding around children and gaps between adjacent children.
63 |
64 |
65 | ```xmlui-pg display name="Example: paddings and gaps"
66 | <App border="2px dotted red">
67 | <Text border="2px dotted green">First item</Text>
68 | <HStack border="2px dotted green">
69 | <Text border="2px dotted purple">Second item</Text>
70 | <Text border="2px dotted purple">Third item</Text>
71 | <Text border="2px dotted purple">Fourth item</Text>
72 | </HStack>
73 | <Text border="2px dotted green">Fifth item</Text>
74 | </App>
75 | ```
76 |
77 | - `App` applies vertical and horizontal padding, which is why the top left corner of the red border and the green border do not meet. It also adds gaps, which are the spaces between the green border areas.
78 | - `HStack` uses no paddings so the top left corner of its green border and the first item's top-left corner (the purple border) meet. Like `App`, `HStack` adds gaps, here shown as spaces between the purple border areas.
79 |
80 | > [!INFO]
81 | > Web and desktop UI frameworks typically use margins to establish layout. XMLUI layout components do not use margins, they only use paddings and gaps. Although you can use margins, they tend to complicate layouts so use them as a last resort.
82 |
83 | ## Dimensions
84 |
85 | Each component has a default strategy for determining the dimensions (height and width) of its children.
86 | `VStack` determines its dimensions according to its content. If we want to display a 40px by 60px orange box with nothing in it and 60px wide orange-red box with empty content, we must explicitly set dimensions (and background color).
87 |
88 | ```xmlui-pg display name="Example: dimensions"
89 | <App>
90 | <VStack height="40px" width="60px" backgroundColor="orangered" />
91 | </App>
92 | ```
93 |
94 | ## Alignment
95 |
96 | Components can align their children in the viewport both vertically and horizontally.
97 |
98 | ```xmlui-pg display name="Example: alignment" /horizontalAlignment/ /verticalAlignment/
99 | <App>
100 | <HStack>
101 | <VStack
102 | width="50%"
103 | border="2px dotted red"
104 | height="200px"
105 | horizontalAlignment="end"
106 | >
107 | <Text>Item #1</Text>
108 | <Text>Item #2</Text>
109 | <Text>Item #3</Text>
110 | </VStack>
111 | <VStack
112 | width="50%"
113 | border="2px
114 | dotted green"
115 | height="200px"
116 | verticalAlignment="center"
117 | >
118 | <Text>Item #1</Text>
119 | <Text>Item #2</Text>
120 | <Text>Item #3</Text>
121 | </VStack>
122 | </HStack>
123 | </App>
124 | ```
125 |
126 | The component with the red border aligns its children vertically to the start and horizontally to the end. The green-bordered component aligns its children vertically to the center and horizontally to the start.
127 |
128 | ## Layout containers
129 |
130 | XMLUI uses only two fundamental layout containers, `Stack`, and `FlowLayout`. All other container-like components (such as `Card`, `List`, and others) apply these to establish more sophisticated layout arrangements.
131 |
132 | `Stack` is a layout container that uses a particular orientation (vertical or horizontal) to render its children in a single column or row. If the children do not fit into the viewport, they overflow. `Stack` has two specialized variants, `HStack` (horizontal stack) and `VStack` (vertical stack).
133 |
134 | `FlowLayout` is a layout container that renders its children horizontally while they fit into the current row; otherwise, the child enters a new row. If the children do not fit into the viewport, they overflow.
135 |
136 | > [!INFO]
137 | > Your application markup must have a single root component. The browser window is an implicit `VStack` layout container with that root element as its single child.
138 |
139 | ## Dimension units
140 |
141 | To explicitly control a child's dimensions, instead of it being determined by its content, you can set one or more of these [properties](/styles-and-themes/layout-props): `width`, `height`, `minWidth`, `minHeight`, `maxWidth`, and `maxHeight` using several kinds of values.
142 |
143 | - **No value**. The layout container determines the default size of the child element according to its strategy.
144 | - **Container-independent size value**. All sizes except percentage (`%`) and star sizes (`*`) belong to this category. The container respects the child's requested size.
145 | - **Percentage size**. The container calculates the child's requested size as a percentage of the viewport's corresponding dimension.
146 | - **Star size**. The child provides a weight the parent container utilizes when distributing the _remaining space_ among its children. The remaining space is the parent viewport's size minus the sum sizes of child components within the first two categories (no value, container-independent size value).
147 |
148 | ## Gaps
149 |
150 | The fundamental layout containers apply a default gap ensuring that children have some space between them.
151 |
152 | ```xmlui-pg copy display name="Example: default gaps"
153 | <App>
154 | <HStack>
155 | <Button>First button</Button>
156 | <Button>Second button</Button>
157 | <Button>Third button</Button>
158 | </HStack>
159 | </App>
160 | ```
161 |
162 | You can remove the gaps.
163 |
164 | ```xmlui-pg copy display /gap="0"/ name="Example: default gaps removed"
165 | <App>
166 | <HStack gap="0">
167 | <Button>First button</Button>
168 | <Button>Second button</Button>
169 | <Button>Third button</Button>
170 | </HStack>
171 | </App>
172 | ```
173 |
174 |
175 | XMLUI offers several [predefined gap values](/styles-and-themes/theme-variables#spacing-in-layout-containers). Use these instead of inline literals like "16px" or "0.5rem" to enable theming and consistent design.
176 |
177 | ```xmlui-pg copy /gap="$gap-tight"/ /gap="$gap-loose"/ name="Example: predefined gap values"
178 | <App>
179 | <VStack>
180 | <HStack gap="$gap-tight">
181 | <Button>First button</Button>
182 | <Button>Second button</Button>
183 | <Button>Third button</Button>
184 | </HStack>
185 | <HStack gap="$gap-loose">
186 | <Button>First button</Button>
187 | <Button>Second button</Button>
188 | <Button>Third button</Button>
189 | </HStack>
190 | </VStack>
191 | </App>
192 | ```
193 |
194 | ## Rendering children
195 |
196 | Layout containers render their children in declaration order, subject to the current page direction (left-to-right or right-to-left).
197 |
198 | This markup displays five boxes; the third is wider than the others.
199 |
200 | ```xmlui-pg name="Example: VStack"
201 | ---app display copy
202 | <App>
203 | <VStack>
204 | <Stack height="20px" width="20%" backgroundColor="orangered" />
205 | <Stack height="20px" width="20%" backgroundColor="orangered" />
206 | <Stack height="20px" width="80%" backgroundColor="orangered" />
207 | <Stack height="20px" width="20%" backgroundColor="orangered" />
208 | <Stack height="20px" width="20%" backgroundColor="orangered" />
209 | </VStack>
210 | </App>
211 | ---desc
212 | In a `VStack` each child takes a new row.
213 | ```
214 |
215 |
216 | ```xmlui-pg name="Example: HStack"
217 | ---app copy display
218 | <App>
219 | <HStack>
220 | <Stack height="20px" width="20%" backgroundColor="orangered" />
221 | <Stack height="20px" width="20%" backgroundColor="orangered" />
222 | <Stack height="20px" width="80%" backgroundColor="orangered" />
223 | <Stack height="20px" width="20%" backgroundColor="orangered" />
224 | <Stack height="20px" width="20%" backgroundColor="orangered" />
225 | </HStack>
226 | </App>
227 | ---desc
228 | In an `HStack` they occupy one row. The app provides a horizontal scrollbar to accommodate overflow.
229 | ```
230 |
231 |
232 | ```xmlui-pg name="Example: HStack"
233 | ---app copy display
234 | <App>
235 | <FlowLayout>
236 | <Stack height="20px" width="20%" backgroundColor="orangered" />
237 | <Stack height="20px" width="20%" backgroundColor="orangered" />
238 | <Stack height="20px" width="80%" backgroundColor="orangered" />
239 | <Stack height="20px" width="20%" backgroundColor="orangered" />
240 | <Stack height="20px" width="20%" backgroundColor="orangered" />
241 | </FlowLayout>
242 | </App>
243 | ---desc
244 | In a `FlowLayout` the children occupy new rows as needed.
245 | ```
246 |
247 | If you set an explicit `height` the layout container will use that height; otherwise, it accommodates the height of its children. This example uses the natural height of the content.
248 |
249 | ```xmlui-pg copy display name="Example: Text in VStack"
250 | <VStack
251 | backgroundColor="cyan"
252 | horizontalAlignment="center"
253 | verticalAlignment="center">
254 | This is some text within a VStack
255 | </VStack>
256 | ```
257 |
258 | This example increases the height.
259 |
260 | ```xmlui-pg copy display name="Example: VStack with explicit height"
261 | <VStack
262 | height="160px"
263 | backgroundColor="cyan"
264 | horizontalAlignment="center"
265 | verticalAlignment="center">
266 | This is some text within a VStack
267 | </VStack>
268 | ```
269 |
270 | ## Container width
271 |
272 | Unless you use an explicit width, a layout container uses the entire width of its viewport. This example uses implicit width.
273 |
274 | ```xmlui-pg copy display name="Example: default container width"
275 | <VStack
276 | backgroundColor="cyan"
277 | horizontalAlignment="center"
278 | verticalAlignment="center">
279 | This is some text within a VStack
280 | </VStack>
281 | ```
282 |
283 | This one uses explicit width.
284 |
285 | ```xmlui-pg copy display /width="400px"/ name="Example: explicit container width"
286 | <VStack
287 | width="400px"
288 | backgroundColor="cyan"
289 | horizontalAlignment="center"
290 | verticalAlignment="center">
291 | This is some text within a VStack
292 | </VStack>
293 | ```
294 |
295 | When you explicitly set the width of a layout container and the content is wider, it will break or overflow.
296 |
297 | ## Stack
298 |
299 | `Stack` renders its child items horizontally or vertically according to its `orientation` property, optionally providing some gap between child components.
300 |
301 | You can assign the `horizontal` or `vertical` values to the `Stack` component's `orientation` property to declare its rendering orientation. The default value is `vertical`.
302 |
303 | > [!INFO]
304 | > Use `Stack` when the `orientation` property can vary as the result of an expression. If the orientation is static, use `VStack` (equivalent to `<Stack orientation="vertical">`) or `HStack` (`<Stack orientation="horizontal">`).
305 |
306 | ### Alignment
307 |
308 | The `horizontalAlignment` and `verticalAlignment` properties govern alignment within a stack, using values like `start`, `end`, and `center`. See the full list of values [here](/styles-and-themes/common-units#alignment)
309 |
310 | This markup aligns the children of an `HStack` horizontally in the center:
311 |
312 | ```xmlui-pg copy display /horizontalAlignment="center"/ name="Example: horizontally centered content in a HStack"
313 | <HStack horizontalAlignment="center">
314 | <Stack backgroundColor="red" height="36px" width="36px" />
315 | <Stack backgroundColor="green" height="36px" width="36px" />
316 | <Stack backgroundColor="blue" height="36px" width="36px" />
317 | </HStack>
318 | ```
319 |
320 | This markup aligns the children of a `VStack` horizontally to the end (right edge):
321 |
322 | ```xmlui-pg copy display /horizontalAlignment="end"/ name="Example: horizontally centered content in a VStack"
323 | <VStack horizontalAlignment="end">
324 | <Stack backgroundColor="red" height="36px" width="36px" />
325 | <Stack backgroundColor="green" height="36px" width="36px" />
326 | <Stack backgroundColor="blue" height="36px" width="36px" />
327 | </VStack>
328 | ```
329 |
330 | This markup aligns the children of an `HStack` vertically in the center:
331 |
332 | ```xmlui-pg copy display /verticalAlignment="center"/ name="Exmaple: vertically centered text in a HStack"
333 | <HStack verticalAlignment="center">
334 | <Stack backgroundColor="red" height="36px" width="36px" />
335 | <Stack backgroundColor="green" height="72px" width="36px" />
336 | <Stack backgroundColor="blue" height="48px" width="36px" />
337 | </HStack>
338 | ```
339 |
340 | ## VStack
341 |
342 | A `VStack` component displays each of its children in a new row. If a child has no explicit (or component-specific) width, the `VStack` stretches the component to the entire viewport width. `VStack` keeps the child components' heights intact.
343 |
344 | ```xmlui-pg copy display name="Example: rendering children in a VStack "
345 | <VStack>
346 | <H2 backgroundColor="orangered">I'm a heading with colored background</H2>
347 | <Button>I'm a button</Button>
348 | </VStack>
349 | ```
350 |
351 | The `H2` component has no explicit size, so its width is set to the width of the text content (as the background color indicates). Though the `Button` component has no explicit size, it has a component-specific one (according to its content), so it is not stretched horizontally. The button is taller than the `VStack`, so its height determines the `VStack` height, and the text height is stretched to that.
352 |
353 | When you use a `VStack` child with percentage height, the effective height is calculated from the entire stack height. Such a setup may cause overflow if the sum of percentages equals 100%, as the gaps between children are also included in the stack height. This example demonstrates an overflow.
354 |
355 | ```xmlui-pg copy display name="Example: VStack with percentage height (overflow)"
356 | <VStack height="200px" border="4px dotted green">
357 | <Stack backgroundColor="cyan" height="50%" />
358 | <Stack backgroundColor="orangered" height="50%" />
359 | </VStack>
360 | ```
361 |
362 | There is no overflow when the stack does not apply gaps.
363 |
364 | ```xmlui-pg copy display /gap="0"/ name="Example: VStack with percentage height (no overflow)"
365 | <VStack gap="0" height="200px" border="4px dotted green">
366 | <Stack backgroundColor="cyan" height="50%" />
367 | <Stack backgroundColor="orangered" height="50%" />
368 | </VStack>
369 | ```
370 |
371 | When you use a `VStack` child height with star sizing, the effective height is calculated from the remaining height of the entire stack after subtracting the heights of explicitly-sized children and gaps.
372 | Such a configuration will not cause overflow.
373 |
374 | ```xmlui-pg copy display name="Example: VStack with star-sized height"
375 | <VStack height="240px" border="4px dotted green">
376 | <Stack backgroundColor="cyan" height="*" />
377 | <H3>I'm a heading</H3>
378 | <Stack backgroundColor="orangered" height="5*" />
379 | </VStack>
380 | ```
381 |
382 | ## HStack
383 |
384 | An `HStack` component displays each of its children in a single row. If a child has no explicit (or component-specific) width, the `HStack` fits the component width to its content. `HStack` sets the child components' heights to the stack's viewport height.
385 |
386 | ```xmlui-pg copy display name="Example: rendering children in a HStack"
387 | <HStack>
388 | <H2 backgroundColor="orangered">I'm a heading with colored background</H2>
389 | <Button>I'm a button</Button>
390 | </HStack>
391 | ```
392 |
393 | The `H2` component has no explicit size, so it's stretched to the viewport width (as the background color indicates). Though `Button` has no explicit size, it has a component-specific one (according to its content), so it is not stretched.
394 |
395 | When you use a `HStack` child with percentage width, the effective width is calculated from the stack's viewport width. Such a setup may cause horizontal overflow if the sum of percentages equals 100%, as the gaps between children are also included in the stack height.
396 |
397 | ```xmlui-pg copy display name="Example: HStack with percentage width (overflow)"
398 | <HStack border="4px dotted green" height="200px">
399 | <Stack backgroundColor="cyan" width="50%" />
400 | <Stack backgroundColor="orangered" width="50%" />
401 | </HStack>
402 | ```
403 |
404 |
405 | When the stack does not apply gaps, there is no overflow:
406 |
407 | ```xmlui-pg copy display /gap="0"/ name="Example: HStack with percentage width (no overflow)"
408 | <HStack gap="0" border="4px dotted green" height="200px">
409 | <Stack backgroundColor="cyan" width="50%" />
410 | <Stack backgroundColor="orangered" width="50%" />
411 | </HStack>
412 | ```
413 |
414 | When you use a `HStack` child with star sizing, the effective width is calculated from the remaining width of the stack's viewport width after subtracting the widths of explicitly sized children and gaps. Such a configuration will not cause overflow.
415 |
416 | ```xmlui-pg copy display name="Example: HStack with star-sized width"
417 | <HStack height="60px" border="4px dotted green">
418 | <Stack backgroundColor="cyan" width="*" />
419 | <H3>I'm a heading</H3>
420 | <Stack backgroundColor="orangered" width="2*" />
421 | </HStack>
422 | ```
423 |
424 | `HStack` has a `wrapContent` property. If you set it to `true`, the engine starts a new line (or column) when the subsequent child to render would overflow in the current line. Here the fourth child does not fit in the first line entirely, so it overflows:
425 |
426 | ```xmlui-pg copy display name="Example: HStack with overflow in a single row"
427 | <HStack>
428 | <Stack backgroundColor="red" height="36px" width="25%" />
429 | <Stack backgroundColor="green" height="36px" width="40%" />
430 | <Stack backgroundColor="blue" height="36px" width="20%" />
431 | <Stack backgroundColor="purple" height="36px" width="30%" />
432 | </HStack>
433 | ```
434 |
435 | With `wrapContent` flag, the forth child begins a new line.
436 |
437 | ```xmlui-pg copy display /wrapContent="true"/ name="Example: HStack with wrapContent"
438 | <HStack wrapContent="true">
439 | <Stack backgroundColor="red" height="36px" width="25%" />
440 | <Stack backgroundColor="green" height="36px" width="40%" />
441 | <Stack backgroundColor="blue" height="36px" width="20%" />
442 | <Stack backgroundColor="purple" height="36px" width="30%" />
443 | </HStack>
444 | ```
445 |
446 | ## CHStack
447 |
448 | `CHStack` is a shorthand version of `Stack` with a horizontal orientation and centered contents.
449 |
450 | ```xmlui-pg copy display name="Example: CHStack"
451 | <CHStack height="100px" width="200px" backgroundColor="lightgray">
452 | <Stack backgroundColor="red" height="36px" width="36px" />
453 | <Stack backgroundColor="green" height="72px" width="36px" />
454 | <Stack backgroundColor="blue" height="48px" width="36px" />
455 | </CHStack>
456 | ```
457 |
458 | ## CVStack
459 |
460 | `CVStack` is a shorthand version of `Stack` with a vertical orientation and centered contents.
461 |
462 | ```xmlui-pg display copy name="Example: CVStack"
463 | <CVStack height="200px" width="100px" backgroundColor="lightgray">
464 | <Stack backgroundColor="red" height="36px" width="36px" />
465 | <Stack backgroundColor="green" height="36px" width="72px" />
466 | <Stack backgroundColor="blue" height="36px" width="48px" />
467 | </CVStack>
468 | ```
469 |
470 | ## FlowLayout
471 |
472 | `FlowLayout` component is effectively a horizontal stack with content wrapping. Though it implements the same behavior, it has extra features:
473 |
474 | - **Percentage sizing**: `FlowLayout` considers the gaps between child elements when using percentage sizing, unlike `Stack`.
475 | - **Responsiveness**: `FlowLayout` resizes percentage-sized children on mobile devices.
476 |
477 | When you use an `HStack` with percentage sizing and the sum width of children is 100%, an overflow will occur because gaps require extra space.
478 |
479 | ```xmlui-pg copy display name="Example: HStack with overflow"
480 | <HStack>
481 | <Stack backgroundColor="red" height="36px" width="25%" />
482 | <Stack backgroundColor="green" height="36px" width="50%" />
483 | <Stack backgroundColor="blue" height="36px" width="25%" />
484 | </HStack>
485 | ```
486 |
487 | `FlowLayout` handles this sizing issue by adjusting the child component dimensions, accounting for the gaps.
488 |
489 | ```xmlui-pg copy display name="Example: FlowLayout"
490 | <FlowLayout>
491 | <Stack backgroundColor="red" height="36px" width="25%" />
492 | <Stack backgroundColor="green" height="36px" width="50%" />
493 | <Stack backgroundColor="blue" height="36px" width="25%" />
494 | </FlowLayout>
495 | ```
496 |
497 | `FlowLayout` caps the size of items exceeding the available width. Here the red box is too wide but `FlowLayout` trims it back to 100% width.
498 |
499 | ```xmlui-pg copy display /width="1000000px"/ name="Example: FlowLayout with size capping"
500 | <FlowLayout>
501 | <Stack backgroundColor="red" height="36px" width="1000000px" />
502 | <Stack backgroundColor="green" height="36px" width="50%" />
503 | <Stack backgroundColor="blue" height="36px" width="25%" />
504 | </FlowLayout>
505 | ```
506 |
507 |
508 | Note how the extreme width of the first child is capped to the space available for the `FlowLayout`, while the other children's sizes remain unmodified:
509 |
510 | ## SpaceFiller
511 |
512 | `SpaceFiller` fills unused space in layout containers. Its behavior depends on the layout container in which it is used. In a `Stack`, it pushes the children following it to the other end of the container.
513 |
514 | ```xmlui-pg copy display name="Example: SpaceFiller in a HStack"
515 | <HStack>
516 | <Stack width="36px" height="36px" backgroundColor="red" />
517 | <SpaceFiller />
518 | <Stack width="36px" height="36px" backgroundColor="blue" />
519 | </HStack>
520 | ```
521 |
522 | In a `FlowLayout`, it acts as a line break for a row. The children following the `SpaceFiller` begin a new line.
523 |
524 | ```xmlui-pg copy display name="Example: SpaceFiller in a FlowLayout"
525 | <FlowLayout>
526 | <Stack width="20%" height="36px" backgroundColor="red" />
527 | <SpaceFiller />
528 | <Stack width="20%" height="36px" backgroundColor="green" />
529 | <Stack width="20%" height="36px" backgroundColor="blue" />
530 | </FlowLayout>
531 | ```
532 |
533 | ## Splitter
534 |
535 | `Splitter` divides a container into two resizable sections (a primary and a secondary) and puts a draggable bar between them. It has two specialized variants, `HSplitter` and `VSplitter`, which separate the two sections vertically and horizontally.
536 |
537 | Here's a horizontal splitter that sets some constraints on the size of the primary section.
538 |
539 | ```xmlui-pg copy display height="200px" name="Example: Splitter"
540 | <HSplitter
541 | height="100%"
542 | minPrimarySize="15%"
543 | maxPrimarySize="85%">
544 | <CVStack backgroundColor="lightblue" height="100%">Primary</CVStack>
545 | <CVStack backgroundColor="darksalmon" height="100%">Secondary</CVStack>
546 | </HSplitter>
547 | ```
548 |
549 | Try dragging the splitter bar between the sections to see how it works.
550 |
551 |
```
--------------------------------------------------------------------------------
/xmlui/src/components/Carousel/Carousel.spec.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { test, expect } from "../../testing/fixtures";
2 |
3 | // =============================================================================
4 | // BASIC FUNCTIONALITY TESTS
5 | // =============================================================================
6 |
7 | test.describe("Basic Functionality", () => {
8 | test("component renders", async ({ page, initTestBed }) => {
9 | await initTestBed(`<Carousel testId="carousel"></Carousel>`);
10 | await expect(page.getByTestId("carousel")).toBeVisible();
11 | });
12 |
13 | test("component renders with correct role", async ({ page, initTestBed }) => {
14 | await initTestBed(`<Carousel></Carousel>`);
15 | await expect(page.getByRole("region")).toBeVisible();
16 | });
17 |
18 | // NOTE: all carousel items are visible by default in embla-carousel
19 | // From this point on, instead of visibility checks, we check that the correct slide is in the viewport
20 | test("component renders with correct role on items", async ({ page, initTestBed }) => {
21 | await initTestBed(`
22 | <Carousel>
23 | <CarouselItem>Slide 1</CarouselItem>
24 | <CarouselItem>Slide 2</CarouselItem>
25 | <CarouselItem>Slide 3</CarouselItem>
26 | </Carousel>
27 | `);
28 | const slides = page.getByRole("region").getByRole("group");
29 | await expect(slides).toHaveCount(3);
30 | await expect(slides.nth(0)).toContainText("Slide 1");
31 | await expect(slides.nth(1)).toContainText("Slide 2");
32 | await expect(slides.nth(2)).toContainText("Slide 3");
33 | });
34 |
35 | test("renders slide #2 if startIndex is 1", async ({ page, initTestBed }) => {
36 | await initTestBed(`
37 | <Carousel startIndex="1">
38 | <CarouselItem>Slide 1</CarouselItem>
39 | <CarouselItem>Slide 2</CarouselItem>
40 | <CarouselItem>Slide 3</CarouselItem>
41 | </Carousel>
42 | `);
43 | await expect(page.getByRole("region").getByRole("group").nth(1)).toBeInViewport();
44 | });
45 |
46 | test("controls prop displays controls", async ({ page, initTestBed }) => {
47 | await initTestBed(`
48 | <Carousel controls="true">
49 | <CarouselItem>Slide 1</CarouselItem>
50 | <CarouselItem>Slide 2</CarouselItem>
51 | <CarouselItem>Slide 3</CarouselItem>
52 | </Carousel>
53 | `);
54 | await expect(page.getByRole("button", { name: "Previous Slide" })).toBeVisible();
55 | await expect(page.getByRole("button", { name: "Next Slide" })).toBeVisible();
56 | });
57 |
58 | test("component scrolls to next slide on control click", async ({ page, initTestBed }) => {
59 | await initTestBed(`
60 | <Carousel controls="true">
61 | <CarouselItem>Slide 1</CarouselItem>
62 | <CarouselItem>Slide 2</CarouselItem>
63 | <CarouselItem>Slide 3</CarouselItem>
64 | </Carousel>
65 | `);
66 | // Click next button
67 | await page.getByRole("button", { name: "Next Slide" }).click();
68 |
69 | // Verify second slide is now visible
70 | await expect(page.getByRole("region").getByRole("group").nth(1)).toBeInViewport();
71 | });
72 |
73 | test("component scrolls to previous slide on control click", async ({ page, initTestBed }) => {
74 | await initTestBed(`
75 | <Carousel startIndex="1" controls="true">
76 | <CarouselItem>Slide 1</CarouselItem>
77 | <CarouselItem>Slide 2</CarouselItem>
78 | <CarouselItem>Slide 3</CarouselItem>
79 | </Carousel>
80 | `);
81 | // Verify we start on slide 2
82 | await expect(page.getByRole("region").getByRole("group").nth(1)).toBeInViewport();
83 |
84 | // Click previous button
85 | await page.getByRole("button", { name: "Previous Slide" }).click();
86 |
87 | // Verify first slide is now visible
88 | const firstSlide = page.getByRole("region").getByRole("group").first();
89 | await expect(firstSlide).toBeInViewport();
90 | });
91 |
92 | test("component navigates to slide on indicator click", async ({ page, initTestBed }) => {
93 | await initTestBed(`
94 | <Carousel>
95 | <CarouselItem>Slide 1</CarouselItem>
96 | <CarouselItem>Slide 2</CarouselItem>
97 | <CarouselItem>Slide 3</CarouselItem>
98 | </Carousel>
99 | `);
100 |
101 | // Click on the third indicator
102 | const indicators = page.getByRole("tab", { name: "Go to slide 3" });
103 | await indicators.click();
104 |
105 | // Verify third slide is visible
106 | const thirdSlide = page.getByRole("region").getByRole("group").nth(2);
107 | await expect(thirdSlide).toBeInViewport();
108 | });
109 |
110 | test("component loops correctly when loop is enabled", async ({ page, initTestBed }) => {
111 | await initTestBed(`
112 | <Carousel loop="true" startIndex="2">
113 | <CarouselItem>Slide 1</CarouselItem>
114 | <CarouselItem>Slide 2</CarouselItem>
115 | <CarouselItem>Slide 3</CarouselItem>
116 | </Carousel>
117 | `);
118 | // Verify we're on the last slide
119 | await expect(page.getByRole("region").getByRole("group").nth(2)).toBeInViewport();
120 |
121 | // Click next again to loop back to first slide
122 | await page.getByRole("button", { name: "Next Slide" }).click();
123 |
124 | // Verify we're back on the first slide
125 | await expect(page.getByRole("region").getByRole("group").first()).toBeInViewport();
126 | });
127 |
128 | test("component autoplay works correctly", async ({ page, initTestBed }) => {
129 | await initTestBed(`
130 | <Carousel autoplay="true" autoplayInterval="100">
131 | <CarouselItem>Slide 1</CarouselItem>
132 | <CarouselItem>Slide 2</CarouselItem>
133 | <CarouselItem>Slide 3</CarouselItem>
134 | </Carousel>
135 | `);
136 | const slides = page.getByRole("region").getByRole("group");
137 |
138 | // Verify first slide is initially visible
139 | await expect(slides.first()).toBeInViewport();
140 |
141 | await page.waitForTimeout(200);
142 | await expect(slides.nth(1)).toBeInViewport();
143 | });
144 | });
145 |
146 | // =============================================================================
147 | // ACCESSIBILITY TESTS
148 | // =============================================================================
149 |
150 | // --- Used this resource for a11y testing best practices: https://www.w3.org/WAI/ARIA/apg/patterns/carousel/
151 | test.describe("Accessibility", () => {
152 | test("regular carousel has correct a11y attributes", async ({ page, initTestBed }) => {
153 | await initTestBed(`
154 | <Carousel indicators="false">
155 | <CarouselItem>Slide 1</CarouselItem>
156 | <CarouselItem>Slide 2</CarouselItem>
157 | </Carousel>
158 | `);
159 | const carousel = page.getByRole("region");
160 | const slides = carousel.getByRole("group");
161 |
162 | await expect(carousel).toHaveAttribute("aria-roledescription", "carousel");
163 | await expect(slides.first()).toHaveAttribute("aria-roledescription", "slide");
164 | });
165 |
166 | test("tabbed carousel has correct a11y attributes", async ({ page, initTestBed }) => {
167 | await initTestBed(`
168 | <Carousel indicators="true">
169 | <CarouselItem>Slide 1</CarouselItem>
170 | </Carousel>
171 | `);
172 | const carousel = page.getByRole("region");
173 | const slides = carousel.getByRole("group");
174 |
175 | await expect(slides.first()).toHaveAttribute("role", "group tabpanel");
176 | await expect(slides.first()).not.toHaveAttribute("aria-roledescription", "slide");
177 | });
178 |
179 | test("carousel controls have correct a11y attributes", async ({ page, initTestBed }) => {
180 | await initTestBed(`
181 | <Carousel controls="true">
182 | <CarouselItem>Slide 1</CarouselItem>
183 | <CarouselItem>Slide 2</CarouselItem>
184 | </Carousel>
185 | `);
186 | const prevControl = page.getByRole("button", { name: "Previous Slide" });
187 | const nextControl = page.getByRole("button", { name: "Next Slide" });
188 |
189 | await expect(prevControl).toHaveAttribute("aria-label", "Previous Slide");
190 | await expect(nextControl).toHaveAttribute("aria-label", "Next Slide");
191 | });
192 |
193 | test("component is keyboard navigable", async ({ page, initTestBed }) => {
194 | await initTestBed(`
195 | <Carousel>
196 | <CarouselItem>Slide 1</CarouselItem>
197 | <CarouselItem>Slide 2</CarouselItem>
198 | <CarouselItem>Slide 3</CarouselItem>
199 | </Carousel>
200 | `);
201 |
202 | // Focus the carousel container
203 | await page.getByRole("region").focus();
204 |
205 | // Press right arrow key
206 | await page.keyboard.press("ArrowRight");
207 |
208 | // Verify second slide is visible
209 | const secondSlide = page.getByRole("region").getByRole("group").nth(1);
210 | await expect(secondSlide).toBeInViewport();
211 |
212 | // Press left arrow key
213 | await page.keyboard.press("ArrowLeft");
214 |
215 | // Verify first slide is visible again
216 | const firstSlide = page.getByRole("region").getByRole("group").first();
217 | await expect(firstSlide).toBeInViewport();
218 | });
219 | });
220 |
221 | // =============================================================================
222 | // VISUAL STATE TESTS
223 | // =============================================================================
224 |
225 | test.describe("Visual States", () => {
226 | test("component width", async ({ page, initTestBed }) => {
227 | await initTestBed(
228 | `<Carousel width="500px">
229 | <CarouselItem>Slide 1</CarouselItem>
230 | <CarouselItem>Slide 2</CarouselItem>
231 | </Carousel>`
232 | );
233 | const carousel = page.getByRole("region");
234 | await expect(carousel).toHaveCSS("width", "500px");
235 | });
236 |
237 | test("component height", async ({ page, initTestBed }) => {
238 | await initTestBed(
239 | `<Carousel height="500px">
240 | <CarouselItem>Slide 1</CarouselItem>
241 | <CarouselItem>Slide 2</CarouselItem>
242 | </Carousel>`
243 | );
244 | const carousel = page.getByRole("region");
245 | await expect(carousel).toHaveCSS("height", "500px");
246 | });
247 |
248 | test("component control background color", async ({ page, initTestBed }) => {
249 | await initTestBed(
250 | `<Carousel controls="true">
251 | <CarouselItem>Slide 1</CarouselItem>
252 | <CarouselItem>Slide 2</CarouselItem>
253 | </Carousel>`,
254 | {
255 | testThemeVars: {
256 | "backgroundColor-control-Carousel": "rgb(255, 0, 0)",
257 | },
258 | },
259 | );
260 | const control = page.getByRole("button", { name: "Next Slide" });
261 | await expect(control).toHaveCSS("background-color", "rgb(255, 0, 0)");
262 | });
263 |
264 | test("component control text color", async ({ page, initTestBed }) => {
265 | await initTestBed(
266 | `<Carousel controls="true">
267 | <CarouselItem>Slide 1</CarouselItem>
268 | <CarouselItem>Slide 2</CarouselItem>
269 | </Carousel>`,
270 | {
271 | testThemeVars: {
272 | "textColor-control-Carousel": "rgb(0, 0, 255)",
273 | },
274 | },
275 | );
276 | const control = page.getByRole("button", { name: "Next Slide" });
277 | await expect(control).toHaveCSS("color", "rgb(0, 0, 255)");
278 | });
279 |
280 | test("component indicator width", async ({ page, initTestBed }) => {
281 | await initTestBed(
282 | `<Carousel indicators="true">
283 | <CarouselItem>Slide 1</CarouselItem>
284 | <CarouselItem>Slide 2</CarouselItem>
285 | </Carousel>`,
286 | {
287 | testThemeVars: {
288 | "width-indicator-Carousel": "20px",
289 | },
290 | },
291 | );
292 | const indicator = page.getByRole("tab", { name: "Go to slide 1" });
293 | await expect(indicator).toHaveCSS("width", "20px");
294 | });
295 |
296 | test("component control height", async ({ page, initTestBed }) => {
297 | await initTestBed(
298 | `<Carousel controls="true">
299 | <CarouselItem>Slide 1</CarouselItem>
300 | <CarouselItem>Slide 2</CarouselItem>
301 | </Carousel>`,
302 | {
303 | testThemeVars: {
304 | "height-control-Carousel": "50px",
305 | },
306 | },
307 | );
308 | const control = page.getByRole("button", { name: "Next Slide" });
309 | await expect(control).toHaveCSS("height", "50px");
310 | });
311 |
312 | test("component control width", async ({ page, initTestBed }) => {
313 | await initTestBed(
314 | `<Carousel controls="true">
315 | <CarouselItem>Slide 1</CarouselItem>
316 | <CarouselItem>Slide 2</CarouselItem>
317 | </Carousel>`,
318 | {
319 | testThemeVars: {
320 | "width-control-Carousel": "50px",
321 | },
322 | },
323 | );
324 | const control = page.getByRole("button", { name: "Next Slide" });
325 | await expect(control).toHaveCSS("width", "50px");
326 | });
327 |
328 | test("component control border radius", async ({ page, initTestBed }) => {
329 | await initTestBed(
330 | `<Carousel controls="true">
331 | <CarouselItem>Slide 1</CarouselItem>
332 | <CarouselItem>Slide 2</CarouselItem>
333 | </Carousel>`,
334 | {
335 | testThemeVars: {
336 | "borderRadius-control-Carousel": "10px",
337 | },
338 | },
339 | );
340 | const control = page.getByRole("button", { name: "Next Slide" });
341 | await expect(control).toHaveCSS("border-radius", "10px");
342 | });
343 |
344 | test("component control hover background color", async ({ page, initTestBed }) => {
345 | await initTestBed(
346 | `<Carousel controls="true">
347 | <CarouselItem>Slide 1</CarouselItem>
348 | <CarouselItem>Slide 2</CarouselItem>
349 | </Carousel>`,
350 | {
351 | testThemeVars: {
352 | "backgroundColor-control-hover-Carousel": "rgb(255, 165, 0)",
353 | },
354 | },
355 | );
356 | const control = page.getByRole("button", { name: "Next Slide" });
357 | await control.hover();
358 | await expect(control).toHaveCSS("background-color", "rgb(255, 165, 0)");
359 | });
360 |
361 | test("component control hover text color", async ({ page, initTestBed }) => {
362 | await initTestBed(
363 | `<Carousel controls="true">
364 | <CarouselItem>Slide 1</CarouselItem>
365 | <CarouselItem>Slide 2</CarouselItem>
366 | </Carousel>`,
367 | {
368 | testThemeVars: {
369 | "textColor-control-hover-Carousel": "rgb(255, 255, 255)",
370 | },
371 | },
372 | );
373 | const control = page.getByRole("button", { name: "Next Slide" });
374 | await control.hover();
375 | await expect(control).toHaveCSS("color", "rgb(255, 255, 255)");
376 | });
377 |
378 | test("component control active background color", async ({ page, initTestBed }) => {
379 | await initTestBed(
380 | `<Carousel controls="true">
381 | <CarouselItem>Slide 1</CarouselItem>
382 | <CarouselItem>Slide 2</CarouselItem>
383 | </Carousel>`,
384 | {
385 | testThemeVars: {
386 | "backgroundColor-control-active-Carousel": "rgb(0, 128, 0)",
387 | },
388 | },
389 | );
390 | const control = page.getByRole("button", { name: "Next Slide" });
391 | await control.hover();
392 | await page.mouse.down();
393 | await expect(control).toHaveCSS("background-color", "rgb(0, 128, 0)");
394 | });
395 |
396 | test("component control active text color", async ({ page, initTestBed }) => {
397 | await initTestBed(
398 | `<Carousel controls="true">
399 | <CarouselItem>Slide 1</CarouselItem>
400 | <CarouselItem>Slide 2</CarouselItem>
401 | </Carousel>`,
402 | {
403 | testThemeVars: {
404 | "textColor-control-active-Carousel": "rgb(255, 255, 0)",
405 | },
406 | },
407 | );
408 | const control = page.getByRole("button", { name: "Next Slide" });
409 | await control.hover();
410 | await page.mouse.down();
411 | await expect(control).toHaveCSS("color", "rgb(255, 255, 0)");
412 | });
413 |
414 | test("component control disabled background color", async ({ page, initTestBed }) => {
415 | await initTestBed(
416 | `<Carousel controls="true" loop="false">
417 | <CarouselItem>Slide 1</CarouselItem>
418 | </Carousel>`,
419 | {
420 | testThemeVars: {
421 | "backgroundColor-control-disabled-Carousel": "rgb(200, 200, 200)",
422 | },
423 | },
424 | );
425 | const control = page.getByRole("button", { name: "Next Slide" });
426 | await expect(control).toHaveCSS("background-color", "rgb(200, 200, 200)");
427 | });
428 |
429 | test("component control disabled text color", async ({ page, initTestBed }) => {
430 | await initTestBed(
431 | `<Carousel controls="true" loop="false">
432 | <CarouselItem>Slide 1</CarouselItem>
433 | </Carousel>`,
434 | {
435 | testThemeVars: {
436 | "textColor-control-disabled-Carousel": "rgb(150, 150, 150)",
437 | },
438 | },
439 | );
440 | const control = page.getByRole("button", { name: "Next Slide" });
441 | await expect(control).toHaveCSS("color", "rgb(150, 150, 150)");
442 | });
443 |
444 | test("component indicator height", async ({ page, initTestBed }) => {
445 | await initTestBed(
446 | `<Carousel indicators="true">
447 | <CarouselItem>Slide 1</CarouselItem>
448 | <CarouselItem>Slide 2</CarouselItem>
449 | </Carousel>`,
450 | {
451 | testThemeVars: {
452 | "height-indicator-Carousel": "15px",
453 | },
454 | },
455 | );
456 | const indicator = page.getByRole("tab", { name: "Go to slide 1" });
457 | await expect(indicator).toHaveCSS("height", "15px");
458 | });
459 |
460 | test("component indicator background color", async ({ page, initTestBed }) => {
461 | await initTestBed(
462 | `<Carousel indicators="true">
463 | <CarouselItem>Slide 1</CarouselItem>
464 | <CarouselItem>Slide 2</CarouselItem>
465 | </Carousel>`,
466 | {
467 | testThemeVars: {
468 | "backgroundColor-indicator-Carousel": "rgb(100, 100, 100)",
469 | },
470 | },
471 | );
472 | const indicator = page.getByRole("tab", { name: "Go to slide 2" });
473 | await expect(indicator).toHaveCSS("background-color", "rgb(100, 100, 100)");
474 | });
475 |
476 | test("component indicator text color", async ({ page, initTestBed }) => {
477 | await initTestBed(
478 | `<Carousel indicators="true">
479 | <CarouselItem>Slide 1</CarouselItem>
480 | <CarouselItem>Slide 2</CarouselItem>
481 | </Carousel>`,
482 | {
483 | testThemeVars: {
484 | "textColor-indicator-Carousel": "rgb(50, 50, 50)",
485 | },
486 | },
487 | );
488 | const indicator = page.getByRole("tab", { name: "Go to slide 2" });
489 | await expect(indicator).toHaveCSS("color", "rgb(50, 50, 50)");
490 | });
491 |
492 | test("component indicator hover background color", async ({ page, initTestBed }) => {
493 | await initTestBed(
494 | `<Carousel indicators="true">
495 | <CarouselItem>Slide 1</CarouselItem>
496 | <CarouselItem>Slide 2</CarouselItem>
497 | </Carousel>`,
498 | {
499 | testThemeVars: {
500 | "backgroundColor-indicator-hover-Carousel": "rgb(150, 150, 255)",
501 | },
502 | },
503 | );
504 | const indicator = page.getByRole("tab", { name: "Go to slide 2" });
505 | await indicator.hover();
506 | await expect(indicator).toHaveCSS("background-color", "rgb(150, 150, 255)");
507 | });
508 |
509 | test("component indicator hover text color", async ({ page, initTestBed }) => {
510 | await initTestBed(
511 | `<Carousel indicators="true">
512 | <CarouselItem>Slide 1</CarouselItem>
513 | <CarouselItem>Slide 2</CarouselItem>
514 | </Carousel>`,
515 | {
516 | testThemeVars: {
517 | "textColor-indicator-hover-Carousel": "rgb(255, 100, 100)",
518 | },
519 | },
520 | );
521 | const indicator = page.getByRole("tab", { name: "Go to slide 2" });
522 | await indicator.hover();
523 | await expect(indicator).toHaveCSS("color", "rgb(255, 100, 100)");
524 | });
525 |
526 | test("component indicator active background color", async ({ page, initTestBed }) => {
527 | await initTestBed(
528 | `<Carousel indicators="true">
529 | <CarouselItem>Slide 1</CarouselItem>
530 | <CarouselItem>Slide 2</CarouselItem>
531 | </Carousel>`,
532 | {
533 | testThemeVars: {
534 | "backgroundColor-indicator-active-Carousel": "rgb(0, 0, 255)",
535 | },
536 | },
537 | );
538 | const indicator = page.getByRole("tab", { name: "Go to slide 1" });
539 | await expect(indicator).toHaveCSS("background-color", "rgb(0, 0, 255)");
540 | });
541 |
542 | test("component indicator active text color", async ({ page, initTestBed }) => {
543 | await initTestBed(
544 | `<Carousel indicators="true">
545 | <CarouselItem>Slide 1</CarouselItem>
546 | <CarouselItem>Slide 2</CarouselItem>
547 | </Carousel>`,
548 | {
549 | testThemeVars: {
550 | "textColor-indicator-active-Carousel": "rgb(255, 255, 255)",
551 | },
552 | },
553 | );
554 | const indicator = page.getByRole("tab", { name: "Go to slide 1" });
555 | await expect(indicator).toHaveCSS("color", "rgb(255, 255, 255)");
556 | });
557 | });
558 |
559 | // =============================================================================
560 | // EDGE CASE TESTS
561 | // =============================================================================
562 |
563 | test.describe("Edge Cases", () => {
564 | test("component handles many slides efficiently", async ({ page, initTestBed }) => {
565 | const itemNum = 20;
566 |
567 | // Create many carousel items
568 | let carouselItems = "";
569 | for (let i = 1; i <= itemNum; i++) {
570 | carouselItems += `<CarouselItem>Slide ${i}</CarouselItem>`;
571 | }
572 |
573 | await initTestBed(`
574 | <Carousel>
575 | ${carouselItems}
576 | </Carousel>
577 | `);
578 |
579 | // Verify carousel renders
580 | await expect(page.getByRole("region")).toBeVisible();
581 | const slides = page.getByRole("region").getByRole("group");
582 |
583 | // Verify all slides are created
584 | await expect(slides).toHaveCount(itemNum);
585 |
586 | // Verify we can navigate through slides
587 | await page
588 | .getByRole("button", { name: "Next Slide" })
589 | .click({ clickCount: itemNum, delay: 100 });
590 |
591 | // Verify we've navigated to the correct slide
592 | await expect(slides.last()).toBeInViewport();
593 | });
594 |
595 | test("component displays tab indicators for many slides", async ({ page, initTestBed }) => {
596 | // Create many carousel items
597 | let carouselItems = "";
598 | for (let i = 1; i <= 20; i++) {
599 | carouselItems += `<CarouselItem>Slide ${i}</CarouselItem>`;
600 | }
601 |
602 | await initTestBed(`
603 | <Carousel indicators="true" controls="false">
604 | ${carouselItems}
605 | </Carousel>
606 | `);
607 |
608 | // Verify carousel renders
609 | await expect(page.getByRole("region").getByRole("tab")).toHaveCount(20);
610 | });
611 |
612 | test("component works without indicators and controls", async ({ page, initTestBed }) => {
613 | await initTestBed(`
614 | <Carousel indicators="false" controls="false">
615 | <CarouselItem>Slide 1</CarouselItem>
616 | <CarouselItem>Slide 2</CarouselItem>
617 | </Carousel>
618 | `);
619 |
620 | // Verify carousel container renders
621 | await expect(page.getByRole("region")).toBeVisible();
622 |
623 | // Verify indicators are not visible
624 | await expect(page.getByRole("region").getByRole("tablist")).not.toBeVisible();
625 |
626 | // Verify controls are not visible
627 | await expect(
628 | page.getByRole("region").getByRole("button", { name: "Previous Slide" }),
629 | ).not.toBeVisible();
630 | await expect(
631 | page.getByRole("region").getByRole("button", { name: "Next Slide" }),
632 | ).not.toBeVisible();
633 | });
634 | });
635 |
636 | // =============================================================================
637 | // INTEGRATION TESTS
638 | // =============================================================================
639 |
640 | test.describe("Integration", () => {
641 | test("component works correctly with custom content", async ({ page, initTestBed }) => {
642 | await initTestBed(`
643 | <Carousel>
644 | <CarouselItem>
645 | <Card title="Card 1" />
646 | </CarouselItem>
647 | <CarouselItem>
648 | <Card title="Card 2" />
649 | </CarouselItem>
650 | </Carousel>
651 | `);
652 | await expect(page.getByRole("group").first()).toContainText("Card 1");
653 | await expect(page.getByRole("group").last()).toContainText("Card 2");
654 | });
655 |
656 | test("component handles custom control icon on Prev button", async ({ page, initTestBed }) => {
657 | await initTestBed(
658 | `
659 | <Carousel controls="true" prevIcon="test">
660 | <CarouselItem>Slide 1</CarouselItem>
661 | <CarouselItem>Slide 2</CarouselItem>
662 | </Carousel>
663 | `,
664 | {
665 | resources: {
666 | "icon.test": "resources/bell.svg",
667 | },
668 | },
669 | );
670 | const useElement = page.getByRole("button", { name: "Previous Slide" }).locator("svg use");
671 | await expect(useElement).toHaveAttribute("href", expect.stringMatching(/#bell/));
672 | });
673 |
674 | test("component handles custom control icon on Next button", async ({ page, initTestBed }) => {
675 | await initTestBed(
676 | `
677 | <Carousel controls="true" nextIcon="test">
678 | <CarouselItem>Slide 1</CarouselItem>
679 | <CarouselItem>Slide 2</CarouselItem>
680 | </Carousel>
681 | `,
682 | {
683 | resources: {
684 | "icon.test": "resources/bell.svg",
685 | },
686 | },
687 | );
688 | const useElement = page.getByRole("button", { name: "Next Slide" }).locator("svg use");
689 | await expect(useElement).toHaveAttribute("href", expect.stringMatching(/#bell/));
690 | });
691 | });
692 |
```
--------------------------------------------------------------------------------
/xmlui/src/components/IFrame/IFrame.spec.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { expect, test } from "../../testing/fixtures";
2 |
3 | // =============================================================================
4 | // BASIC FUNCTIONALITY TESTS
5 | // =============================================================================
6 |
7 | test.describe("Basic Functionality", () => {
8 | test("component renders", async ({ initTestBed, page }) => {
9 | await initTestBed(`<IFrame testId="iframe" />`);
10 | await expect(page.getByTestId("iframe")).toBeVisible();
11 | });
12 |
13 | test("component renders with 'src' property", async ({ initTestBed, page }) => {
14 | await initTestBed(`<IFrame src="https://example.com" testId="iframe" />`);
15 | const iframe = page.getByTestId("iframe");
16 | await expect(iframe).toBeVisible();
17 | await expect(iframe).toHaveAttribute("src", "https://example.com");
18 | });
19 |
20 | test("component renders with 'srcdoc' property", async ({ initTestBed, page }) => {
21 | const htmlContent = "<h1>Test Content</h1><p>This is embedded HTML.</p>";
22 | await initTestBed(`<IFrame srcdoc="${htmlContent}" testId="iframe" />`);
23 | const iframe = page.getByTestId("iframe");
24 | await expect(iframe).toBeVisible();
25 | await expect(iframe).toHaveAttribute("srcdoc", htmlContent);
26 | });
27 |
28 | test.describe("allow property", () => {
29 | test("sets permissions policy", async ({ initTestBed, page }) => {
30 | const allowValue = "camera; microphone; geolocation";
31 | await initTestBed(`<IFrame allow="${allowValue}" testId="iframe" />`);
32 | const iframe = page.getByTestId("iframe");
33 | await expect(iframe).toHaveAttribute("allow", allowValue);
34 | });
35 |
36 | test("handles empty string", async ({ initTestBed, page }) => {
37 | await initTestBed(`<IFrame allow="" testId="iframe" />`);
38 | const iframe = page.getByTestId("iframe");
39 | await expect(iframe).toHaveAttribute("allow", "");
40 | });
41 |
42 | test("handles null gracefully", async ({ initTestBed, page }) => {
43 | await initTestBed(`<IFrame allow="{null}" testId="iframe" />`);
44 | const iframe = page.getByTestId("iframe");
45 | await expect(iframe).not.toHaveAttribute("allow");
46 | });
47 |
48 | test("handles undefined gracefully", async ({ initTestBed, page }) => {
49 | await initTestBed(`<IFrame allow="{undefined}" testId="iframe" />`);
50 | const iframe = page.getByTestId("iframe");
51 | await expect(iframe).not.toHaveAttribute("allow");
52 | });
53 | });
54 |
55 | test.describe("name property", () => {
56 | test("sets iframe name", async ({ initTestBed, page }) => {
57 | await initTestBed(`<IFrame name="myFrame" testId="iframe" />`);
58 | const iframe = page.getByTestId("iframe");
59 | await expect(iframe).toHaveAttribute("name", "myFrame");
60 | });
61 |
62 | test("handles empty string", async ({ initTestBed, page }) => {
63 | await initTestBed(`<IFrame name="" testId="iframe" />`);
64 | const iframe = page.getByTestId("iframe");
65 | await expect(iframe).toHaveAttribute("name", "");
66 | });
67 |
68 | test("handles null gracefully", async ({ initTestBed, page }) => {
69 | await initTestBed(`<IFrame name="{null}" testId="iframe" />`);
70 | const iframe = page.getByTestId("iframe");
71 | await expect(iframe).not.toHaveAttribute("name");
72 | });
73 |
74 | test("handles undefined gracefully", async ({ initTestBed, page }) => {
75 | await initTestBed(`<IFrame name="{undefined}" testId="iframe" />`);
76 | const iframe = page.getByTestId("iframe");
77 | await expect(iframe).not.toHaveAttribute("name");
78 | });
79 |
80 | test("handles unicode characters", async ({ initTestBed, page }) => {
81 | const unicodeName = "测试框架👨👩👧👦";
82 | await initTestBed(`<IFrame name="${unicodeName}" testId="iframe" />`);
83 | const iframe = page.getByTestId("iframe");
84 | await expect(iframe).toHaveAttribute("name", unicodeName);
85 | });
86 | });
87 |
88 | test.describe("referrerPolicy property", () => {
89 | [
90 | "no-referrer",
91 | "no-referrer-when-downgrade",
92 | "origin",
93 | "origin-when-cross-origin",
94 | "same-origin",
95 | "strict-origin",
96 | "strict-origin-when-cross-origin",
97 | "unsafe-url"
98 | ].forEach(policy => {
99 | test(`sets referrerPolicy to '${policy}'`, async ({ initTestBed, page }) => {
100 | await initTestBed(`<IFrame referrerPolicy="${policy}" testId="iframe" />`);
101 | const iframe = page.getByTestId("iframe");
102 | await expect(iframe).toHaveAttribute("referrerpolicy", policy);
103 | });
104 | });
105 |
106 | test("handles invalid value gracefully", async ({ initTestBed, page }) => {
107 | await initTestBed(`<IFrame referrerPolicy="invalid-policy" testId="iframe" />`);
108 | const iframe = page.getByTestId("iframe");
109 | await expect(iframe).toHaveAttribute("referrerpolicy", "invalid-policy");
110 | });
111 |
112 | test("handles null gracefully", async ({ initTestBed, page }) => {
113 | await initTestBed(`<IFrame referrerPolicy="{null}" testId="iframe" />`);
114 | const iframe = page.getByTestId("iframe");
115 | await expect(iframe).not.toHaveAttribute("referrerpolicy");
116 | });
117 | });
118 |
119 | test.describe("sandbox property", () => {
120 | test("sets sandbox restrictions", async ({ initTestBed, page }) => {
121 | const sandboxValue = "allow-scripts allow-same-origin";
122 | await initTestBed(`<IFrame sandbox="${sandboxValue}" testId="iframe" />`);
123 | const iframe = page.getByTestId("iframe");
124 | await expect(iframe).toHaveAttribute("sandbox", sandboxValue);
125 | });
126 |
127 | test("handles empty string for strict sandboxing", async ({ initTestBed, page }) => {
128 | await initTestBed(`<IFrame sandbox="" testId="iframe" />`);
129 | const iframe = page.getByTestId("iframe");
130 | await expect(iframe).toHaveAttribute("sandbox", "");
131 | });
132 |
133 | test("handles single sandbox flag", async ({ initTestBed, page }) => {
134 | await initTestBed(`<IFrame sandbox="allow-scripts" testId="iframe" />`);
135 | const iframe = page.getByTestId("iframe");
136 | await expect(iframe).toHaveAttribute("sandbox", "allow-scripts");
137 | });
138 |
139 | test("handles null gracefully", async ({ initTestBed, page }) => {
140 | await initTestBed(`<IFrame sandbox="{null}" testId="iframe" />`);
141 | const iframe = page.getByTestId("iframe");
142 | await expect(iframe).not.toHaveAttribute("sandbox");
143 | });
144 | });
145 |
146 | test.describe("event handlers", () => {
147 | test("'load' event fires when content loads", async ({ initTestBed, page }) => {
148 | const { testStateDriver } = await initTestBed(`
149 | <IFrame
150 | srcdoc="<h1>Test</h1>"
151 | onLoad="testState = 'loaded'"
152 | testId="iframe"
153 | />
154 | `);
155 |
156 | // Wait for the load event to fire
157 | await expect.poll(testStateDriver.testState).toEqual("loaded");
158 | });
159 |
160 | test("'load' event receives event object", async ({ initTestBed, page }) => {
161 | const { testStateDriver } = await initTestBed(`
162 | <IFrame
163 | srcdoc="<h1>Test</h1>"
164 | onLoad="event => testState = event.type"
165 | testId="iframe"
166 | />
167 | `);
168 |
169 | await expect.poll(testStateDriver.testState).toEqual("load");
170 | });
171 | });
172 |
173 | test.describe("edge case combinations", () => {
174 | test("handles both src and srcdoc (srcdoc should take precedence)", async ({ initTestBed, page }) => {
175 | const htmlContent = "<h1>Srcdoc Content</h1>";
176 | await initTestBed(`<IFrame src="https://example.com" srcdoc="${htmlContent}" testId="iframe" />`);
177 | const iframe = page.getByTestId("iframe");
178 | await expect(iframe).toHaveAttribute("src", "https://example.com");
179 | await expect(iframe).toHaveAttribute("srcdoc", htmlContent);
180 | });
181 |
182 | test("handles all security properties together", async ({ initTestBed, page }) => {
183 | await initTestBed(`
184 | <IFrame
185 | src="https://example.com"
186 | sandbox="allow-scripts allow-same-origin"
187 | allow="camera; microphone"
188 | referrerPolicy="no-referrer"
189 | testId="iframe"
190 | />
191 | `);
192 | const iframe = page.getByTestId("iframe");
193 | await expect(iframe).toHaveAttribute("sandbox", "allow-scripts allow-same-origin");
194 | await expect(iframe).toHaveAttribute("allow", "camera; microphone");
195 | await expect(iframe).toHaveAttribute("referrerpolicy", "no-referrer");
196 | });
197 |
198 | test("handles extremely long URL", async ({ initTestBed, page }) => {
199 | const longUrl = "https://example.com/" + "a".repeat(2000);
200 | await initTestBed(`<IFrame src="${longUrl}" testId="iframe" />`);
201 | const iframe = page.getByTestId("iframe");
202 | await expect(iframe).toHaveAttribute("src", longUrl);
203 | });
204 |
205 | test("handles complex HTML in srcdoc with special characters", async ({ initTestBed, page }) => {
206 | const complexHtml = "<h1>测试</h1><p>Special chars: &lt;&gt;&amp;</p>";
207 | await initTestBed(`<IFrame srcdoc="${complexHtml}" testId="iframe" />`);
208 | const iframe = page.getByTestId("iframe");
209 | await expect(iframe).toBeVisible();
210 | // HTML entities get decoded by the browser, so we expect the decoded version
211 | await expect(iframe).toHaveAttribute("srcdoc", "<h1>测试</h1><p>Special chars: <>&</p>");
212 | });
213 | });
214 | });
215 |
216 | // =============================================================================
217 | // ACCESSIBILITY TESTS
218 | // =============================================================================
219 |
220 | test.describe("Accessibility", () => {
221 | test("has correct element type", async ({ initTestBed, page }) => {
222 | await initTestBed(`<IFrame testId="iframe" />`);
223 | // Note: iframe elements don't have an implicit role, they are identified by tag name
224 | const iframe = page.getByTestId("iframe");
225 | await expect(iframe).toBeVisible();
226 | // Check that it's actually an iframe element
227 | const tagName = await iframe.evaluate(el => el.tagName);
228 | expect(tagName).toBe("IFRAME");
229 | });
230 |
231 | test("maintains focus management", async ({ initTestBed, page }) => {
232 | await initTestBed(`<IFrame testId="iframe" />`);
233 | const iframe = page.getByTestId("iframe");
234 |
235 | // IFrame should be focusable by default
236 | await iframe.focus();
237 | await expect(iframe).toBeFocused();
238 | });
239 |
240 | test("can be identified by name for accessibility tools", async ({ initTestBed, page }) => {
241 | await initTestBed(`<IFrame name="mainContent" testId="iframe" />`);
242 | const iframe = page.getByTestId("iframe");
243 | await expect(iframe).toHaveAttribute("name", "mainContent");
244 | // Name attribute can be used by accessibility tools to identify the frame
245 | });
246 |
247 | test("supports sandbox for secure content embedding", async ({ initTestBed, page }) => {
248 | await initTestBed(`<IFrame sandbox="allow-scripts" testId="iframe" />`);
249 | const iframe = page.getByTestId("iframe");
250 | await expect(iframe).toHaveAttribute("sandbox", "allow-scripts");
251 | // Sandbox provides security restrictions which can help with accessibility concerns
252 | });
253 | });
254 |
255 | // =============================================================================
256 | // THEME VARIABLE TESTS
257 | // =============================================================================
258 |
259 | test.describe("Theme Variables", () => {
260 | test("applies 'width-IFrame' theme variable", async ({ initTestBed, page }) => {
261 | await initTestBed(`<IFrame testId="iframe" />`, {
262 | testThemeVars: { "width-IFrame": "500px" },
263 | });
264 | await expect(page.getByTestId("iframe")).toHaveCSS("width", "500px");
265 | });
266 |
267 | test("applies 'height-IFrame' theme variable", async ({ initTestBed, page }) => {
268 | await initTestBed(`<IFrame testId="iframe" />`, {
269 | testThemeVars: { "height-IFrame": "400px" },
270 | });
271 | await expect(page.getByTestId("iframe")).toHaveCSS("height", "400px");
272 | });
273 |
274 | test("applies 'borderRadius-IFrame' theme variable", async ({ initTestBed, page }) => {
275 | await initTestBed(`<IFrame testId="iframe" />`, {
276 | testThemeVars: { "borderRadius-IFrame": "10px" },
277 | });
278 | await expect(page.getByTestId("iframe")).toHaveCSS("border-radius", "10px");
279 | });
280 |
281 | test("applies 'border-IFrame' theme variable", async ({ initTestBed, page }) => {
282 | await initTestBed(`<IFrame testId="iframe" />`, {
283 | testThemeVars: { "border-IFrame": "2px solid rgb(255, 0, 0)" },
284 | });
285 | await expect(page.getByTestId("iframe")).toHaveCSS("border", "2px solid rgb(255, 0, 0)");
286 | });
287 |
288 | test("applies multiple theme variables together", async ({ initTestBed, page }) => {
289 | await initTestBed(`<IFrame testId="iframe" />`, {
290 | testThemeVars: {
291 | "width-IFrame": "600px",
292 | "height-IFrame": "400px",
293 | "borderRadius-IFrame": "8px",
294 | "border-IFrame": "3px dashed rgb(0, 255, 0)"
295 | },
296 | });
297 | const iframe = page.getByTestId("iframe");
298 | await expect(iframe).toHaveCSS("width", "600px");
299 | await expect(iframe).toHaveCSS("height", "400px");
300 | await expect(iframe).toHaveCSS("border-radius", "8px");
301 | await expect(iframe).toHaveCSS("border", "3px dashed rgb(0, 255, 0)");
302 | });
303 |
304 | test("falls back to default theme variables", async ({ initTestBed, page }) => {
305 | await initTestBed(`<IFrame testId="iframe" />`);
306 | const iframe = page.getByTestId("iframe");
307 |
308 | // Default height should be 300px
309 | await expect(iframe).toHaveCSS("height", "300px");
310 | // Should have some border (exact value depends on theme)
311 | const borderStyle = await iframe.evaluate(el => getComputedStyle(el).border);
312 | expect(borderStyle).toBeTruthy();
313 | // Width might not be 100% in test environment but should have a computed value
314 | const widthStyle = await iframe.evaluate(el => getComputedStyle(el).width);
315 | expect(widthStyle).toBeTruthy();
316 | });
317 | });
318 |
319 | // =============================================================================
320 | // OTHER EDGE CASE TESTS
321 | // =============================================================================
322 |
323 | test.describe("Other Edge Cases", () => {
324 | test("handles no props gracefully", async ({ initTestBed, page }) => {
325 | await initTestBed(`<IFrame testId="iframe" />`);
326 | const iframe = page.getByTestId("iframe");
327 | await expect(iframe).toBeVisible();
328 | // Should not have src or srcdoc attributes
329 | await expect(iframe).not.toHaveAttribute("src");
330 | await expect(iframe).not.toHaveAttribute("srcdoc");
331 | });
332 |
333 | test("handles invalid URL in src", async ({ initTestBed, page }) => {
334 | await initTestBed(`<IFrame src="not-a-valid-url" testId="iframe" />`);
335 | const iframe = page.getByTestId("iframe");
336 | await expect(iframe).toBeVisible();
337 | // The src attribute might be transformed by XMLUI's resource URL handling
338 | const srcValue = await iframe.getAttribute("src");
339 | expect(srcValue).toContain("not-a-valid-url");
340 | });
341 |
342 | test("handles empty srcdoc", async ({ initTestBed, page }) => {
343 | await initTestBed(`<IFrame srcdoc="" testId="iframe" />`);
344 | const iframe = page.getByTestId("iframe");
345 | await expect(iframe).toBeVisible();
346 | await expect(iframe).toHaveAttribute("srcdoc", "");
347 | });
348 |
349 | test("handles malformed HTML in srcdoc", async ({ initTestBed, page }) => {
350 | const malformedHtml = "<div><p>Unclosed tags<span>More content";
351 | await initTestBed(`<IFrame srcdoc="${malformedHtml}" testId="iframe" />`);
352 | const iframe = page.getByTestId("iframe");
353 | await expect(iframe).toBeVisible();
354 | await expect(iframe).toHaveAttribute("srcdoc", malformedHtml);
355 | });
356 |
357 | test("handles very long sandbox value", async ({ initTestBed, page }) => {
358 | const longSandbox = Array(100).fill("allow-scripts").join(" ");
359 | await initTestBed(`<IFrame sandbox="${longSandbox}" testId="iframe" />`);
360 | const iframe = page.getByTestId("iframe");
361 | await expect(iframe).toHaveAttribute("sandbox", longSandbox);
362 | });
363 |
364 | test("maintains performance with multiple iframes", async ({ initTestBed, page }) => {
365 | await initTestBed(`
366 | <Fragment>
367 | <IFrame srcdoc="<h1>Frame 1</h1>" testId="iframe1" />
368 | <IFrame srcdoc="<h1>Frame 2</h1>" testId="iframe2" />
369 | <IFrame srcdoc="<h1>Frame 3</h1>" testId="iframe3" />
370 | </Fragment>
371 | `);
372 |
373 | await expect(page.getByTestId("iframe1")).toBeVisible();
374 | await expect(page.getByTestId("iframe2")).toBeVisible();
375 | await expect(page.getByTestId("iframe3")).toBeVisible();
376 | });
377 |
378 | test("handles data URLs in src", async ({ initTestBed, page }) => {
379 | const dataUrl = "data:text/html,<h1>Data URL Content</h1>";
380 | await initTestBed(`<IFrame src="${dataUrl}" testId="iframe" />`);
381 | const iframe = page.getByTestId("iframe");
382 | await expect(iframe).toBeVisible();
383 | // The URL might be transformed by XMLUI's resource URL handling
384 | const srcValue = await iframe.getAttribute("src");
385 | expect(srcValue).toContain("data:text/html");
386 | });
387 |
388 | test("handles blob URLs in src", async ({ initTestBed, page }) => {
389 | // Blob URLs are typically generated by JavaScript, so we test with a mock
390 | const blobUrl = "blob:https://example.com/12345678-1234-1234-1234-123456789abc";
391 | await initTestBed(`<IFrame src="${blobUrl}" testId="iframe" />`);
392 | const iframe = page.getByTestId("iframe");
393 | await expect(iframe).toBeVisible();
394 | // The URL might be transformed by XMLUI's resource URL handling
395 | const srcValue = await iframe.getAttribute("src");
396 | expect(srcValue).toContain("blob:https://example.com");
397 | });
398 | });
399 |
400 | // =============================================================================
401 | // API TESTS
402 | // =============================================================================
403 |
404 | test.describe("APIs", () => {
405 | test("postMessage sends message to iframe content window", async ({ initTestBed, page }) => {
406 | const { testStateDriver } = await initTestBed(`
407 | <Fragment>
408 | <IFrame
409 | id="testIframe"
410 | srcdoc="
411 | <script>
412 | window.addEventListener('message', (event) => {
413 | window.parent.postMessage({ received: event.data }, '*');
414 | });
415 | </script>
416 | <h1>Test IFrame</h1>
417 | "
418 | testId="iframe" />
419 | <Button
420 | onClick="
421 | testIframe.postMessage({ type: 'test', message: 'Hello IFrame' }, '*');
422 | testState = 'message-sent';
423 | "
424 | label="Send Message"
425 | testId="sendButton" />
426 | </Fragment>
427 | `);
428 |
429 | await page.getByTestId("sendButton").click();
430 | await expect.poll(testStateDriver.testState).toEqual("message-sent");
431 | });
432 |
433 | test("postMessage with custom target origin", async ({ initTestBed, page }) => {
434 | const { testStateDriver } = await initTestBed(`
435 | <Fragment>
436 | <IFrame
437 | id="testIframe"
438 | srcdoc="<h1>Test IFrame</h1>"
439 | testId="iframe" />
440 | <Button
441 | onClick="
442 | testIframe.postMessage('test-message', 'https://example.com');
443 | testState = 'message-with-origin-sent';
444 | "
445 | label="Send Message with Origin"
446 | testId="sendButton" />
447 | </Fragment>
448 | `);
449 |
450 | await page.getByTestId("sendButton").click();
451 | await expect.poll(testStateDriver.testState).toEqual("message-with-origin-sent");
452 | });
453 |
454 | test("getContentWindow returns content window object", async ({ initTestBed, page }) => {
455 | const { testStateDriver } = await initTestBed(`
456 | <Fragment>
457 | <IFrame
458 | id="testIframe"
459 | srcdoc="<h1>Test Content</h1>"
460 | testId="iframe" />
461 | <Button
462 | onClick="
463 | const contentWindow = testIframe.getContentWindow();
464 | testState = {
465 | hasContentWindow: contentWindow !== null,
466 | isWindow: contentWindow && typeof contentWindow.postMessage === 'function'
467 | };
468 | "
469 | label="Get Content Window"
470 | testId="getWindowButton" />
471 | </Fragment>
472 | `);
473 |
474 | await page.getByTestId("getWindowButton").click();
475 |
476 | const result = await testStateDriver.testState();
477 | expect(result.hasContentWindow).toBe(true);
478 | expect(result.isWindow).toBe(true);
479 | });
480 |
481 | test("getContentWindow returns null when iframe not loaded", async ({ initTestBed, page }) => {
482 | const { testStateDriver } = await initTestBed(`
483 | <Fragment>
484 | <IFrame
485 | id="testIframe"
486 | src="about:blank"
487 | testId="iframe" />
488 | <Button
489 | onClick="
490 | const contentWindow = testIframe.getContentWindow();
491 | testState = { isNull: contentWindow === null };
492 | "
493 | label="Get Content Window"
494 | testId="getWindowButton" />
495 | </Fragment>
496 | `);
497 |
498 | // Click immediately before iframe might be fully loaded
499 | await page.getByTestId("getWindowButton").click();
500 |
501 | const result = await testStateDriver.testState();
502 | // Content window should exist even for about:blank
503 | expect(result.isNull).toBe(false);
504 | });
505 |
506 | test("getContentDocument returns content document object", async ({ initTestBed, page }) => {
507 | const { testStateDriver } = await initTestBed(`
508 | <Fragment>
509 | <IFrame
510 | id="testIframe"
511 | srcdoc="<html><head><title>Test Document</title></head><body><h1>Test Content</h1></body></html>"
512 | testId="iframe" />
513 | <Button
514 | onClick="
515 | const contentDoc = testIframe.getContentDocument();
516 | testState = {
517 | hasContentDocument: contentDoc !== null,
518 | documentTitle: contentDoc ? contentDoc.title : null,
519 | isDocument: contentDoc && typeof contentDoc.querySelector === 'function'
520 | };
521 | "
522 | label="Get Content Document"
523 | testId="getDocButton" />
524 | </Fragment>
525 | `);
526 |
527 | // Wait for iframe to load
528 | await page.waitForTimeout(100);
529 | await page.getByTestId("getDocButton").click();
530 |
531 | const result = await testStateDriver.testState();
532 | expect(result.hasContentDocument).toBe(true);
533 | expect(result.documentTitle).toBe("Test Document");
534 | expect(result.isDocument).toBe(true);
535 | });
536 |
537 | test("APIs work with same-origin srcdoc content", async ({ initTestBed, page }) => {
538 | const { testStateDriver } = await initTestBed(`
539 | <Fragment>
540 | <IFrame
541 | id="testIframe"
542 | srcdoc="
543 | <html>
544 | <head><title>API Test Document</title></head>
545 | <body>
546 | <h1>API Test Content</h1>
547 | <script>
548 | window.addEventListener('message', (event) => \\{
549 | if (event.data.type === 'ping') \\{
550 | window.parent.postMessage(\\{ type: 'pong', data: event.data.data }, '*');
551 | }
552 | });
553 | </script>
554 | </body>
555 | </html>
556 | "
557 | testId="iframe" />
558 | <Button
559 | onClick="
560 | const contentWindow = testIframe.getContentWindow();
561 | const contentDoc = testIframe.getContentDocument();
562 | testIframe.postMessage({ type: 'ping', data: 'test-data' }, '*');
563 | testState = {
564 | hasWindow: contentWindow !== null,
565 | hasDocument: contentDoc !== null,
566 | documentTitle: contentDoc ? contentDoc.title : null,
567 | messageSent: true
568 | };
569 | "
570 | label="Test All APIs"
571 | testId="testAllButton" />
572 | </Fragment>
573 | `);
574 |
575 | // Wait for iframe to load
576 | await page.waitForTimeout(100);
577 | await page.getByTestId("testAllButton").click();
578 |
579 | const result = await testStateDriver.testState();
580 | expect(result.hasWindow).toBe(true);
581 | expect(result.hasDocument).toBe(true);
582 | expect(result.documentTitle).toBe("API Test Document");
583 | expect(result.messageSent).toBe(true);
584 | });
585 | });
586 |
```