This is page 82 of 188. Use http://codebase.md/xmlui-org/xmlui/xmlui/tools/vscode/resources/xmlui-markup-syntax-highlighting.png?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .changeset
│ ├── config.json
│ ├── cyan-tools-design.md
│ ├── every-moments-teach.md
│ ├── full-symbols-accept.md
│ └── tricky-zoos-crash.md
├── .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
│ │ │ ├── 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
│ │ │ └── PageNotFound.xmlui
│ │ ├── config.ts
│ │ ├── Main.xmlui
│ │ └── themes
│ │ └── blog-theme.ts
│ └── tsconfig.json
├── CONTRIBUTING.md
├── docs
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── ComponentRefLinks.txt
│ ├── content
│ │ ├── _meta.json
│ │ ├── components
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── APICall.md
│ │ │ ├── App.md
│ │ │ ├── AppHeader.md
│ │ │ ├── AppState.md
│ │ │ ├── AutoComplete.md
│ │ │ ├── Avatar.md
│ │ │ ├── Backdrop.md
│ │ │ ├── Badge.md
│ │ │ ├── BarChart.md
│ │ │ ├── Bookmark.md
│ │ │ ├── Breakout.md
│ │ │ ├── Button.md
│ │ │ ├── Card.md
│ │ │ ├── Carousel.md
│ │ │ ├── ChangeListener.md
│ │ │ ├── Checkbox.md
│ │ │ ├── CHStack.md
│ │ │ ├── ColorPicker.md
│ │ │ ├── Column.md
│ │ │ ├── ContentSeparator.md
│ │ │ ├── CVStack.md
│ │ │ ├── DataSource.md
│ │ │ ├── DateInput.md
│ │ │ ├── DatePicker.md
│ │ │ ├── DonutChart.md
│ │ │ ├── DropdownMenu.md
│ │ │ ├── EmojiSelector.md
│ │ │ ├── ExpandableItem.md
│ │ │ ├── FileInput.md
│ │ │ ├── FileUploadDropZone.md
│ │ │ ├── FlowLayout.md
│ │ │ ├── Footer.md
│ │ │ ├── Form.md
│ │ │ ├── FormItem.md
│ │ │ ├── FormSection.md
│ │ │ ├── Fragment.md
│ │ │ ├── H1.md
│ │ │ ├── H2.md
│ │ │ ├── H3.md
│ │ │ ├── H4.md
│ │ │ ├── H5.md
│ │ │ ├── H6.md
│ │ │ ├── Heading.md
│ │ │ ├── HSplitter.md
│ │ │ ├── HStack.md
│ │ │ ├── Icon.md
│ │ │ ├── IFrame.md
│ │ │ ├── Image.md
│ │ │ ├── Items.md
│ │ │ ├── LabelList.md
│ │ │ ├── Legend.md
│ │ │ ├── LineChart.md
│ │ │ ├── Link.md
│ │ │ ├── List.md
│ │ │ ├── Logo.md
│ │ │ ├── Markdown.md
│ │ │ ├── MenuItem.md
│ │ │ ├── MenuSeparator.md
│ │ │ ├── ModalDialog.md
│ │ │ ├── NavGroup.md
│ │ │ ├── NavLink.md
│ │ │ ├── NavPanel.md
│ │ │ ├── NoResult.md
│ │ │ ├── NumberBox.md
│ │ │ ├── Option.md
│ │ │ ├── Page.md
│ │ │ ├── PageMetaTitle.md
│ │ │ ├── Pages.md
│ │ │ ├── Pagination.md
│ │ │ ├── PasswordInput.md
│ │ │ ├── PieChart.md
│ │ │ ├── ProgressBar.md
│ │ │ ├── Queue.md
│ │ │ ├── RadioGroup.md
│ │ │ ├── RealTimeAdapter.md
│ │ │ ├── Redirect.md
│ │ │ ├── Select.md
│ │ │ ├── Slider.md
│ │ │ ├── Slot.md
│ │ │ ├── SpaceFiller.md
│ │ │ ├── Spinner.md
│ │ │ ├── Splitter.md
│ │ │ ├── Stack.md
│ │ │ ├── StickyBox.md
│ │ │ ├── SubMenuItem.md
│ │ │ ├── Switch.md
│ │ │ ├── TabItem.md
│ │ │ ├── Table.md
│ │ │ ├── TableOfContents.md
│ │ │ ├── Tabs.md
│ │ │ ├── Text.md
│ │ │ ├── TextArea.md
│ │ │ ├── TextBox.md
│ │ │ ├── Theme.md
│ │ │ ├── TimeInput.md
│ │ │ ├── Timer.md
│ │ │ ├── ToneChangerButton.md
│ │ │ ├── ToneSwitch.md
│ │ │ ├── Tooltip.md
│ │ │ ├── Tree.md
│ │ │ ├── VSplitter.md
│ │ │ ├── VStack.md
│ │ │ ├── xmlui-animations
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── Animation.md
│ │ │ │ ├── FadeAnimation.md
│ │ │ │ ├── FadeInAnimation.md
│ │ │ │ ├── FadeOutAnimation.md
│ │ │ │ ├── ScaleAnimation.md
│ │ │ │ └── SlideInAnimation.md
│ │ │ ├── xmlui-pdf
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Pdf.md
│ │ │ ├── xmlui-spreadsheet
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Spreadsheet.md
│ │ │ └── xmlui-website-blocks
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── Carousel.md
│ │ │ ├── HelloMd.md
│ │ │ ├── HeroSection.md
│ │ │ └── ScrollToTop.md
│ │ └── extensions
│ │ ├── _meta.json
│ │ ├── xmlui-animations
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── Animation.md
│ │ │ ├── FadeAnimation.md
│ │ │ ├── FadeInAnimation.md
│ │ │ ├── FadeOutAnimation.md
│ │ │ ├── ScaleAnimation.md
│ │ │ └── SlideInAnimation.md
│ │ └── xmlui-website-blocks
│ │ ├── _meta.json
│ │ ├── _overview.md
│ │ ├── Carousel.md
│ │ ├── HelloMd.md
│ │ ├── HeroSection.md
│ │ └── ScrollToTop.md
│ ├── extensions.ts
│ ├── index.html
│ ├── index.ts
│ ├── package.json
│ ├── public
│ │ ├── feed.rss
│ │ ├── mockServiceWorker.js
│ │ ├── pages
│ │ │ ├── _meta.json
│ │ │ ├── app-structure.md
│ │ │ ├── build-editor-component.md
│ │ │ ├── build-hello-world-component.md
│ │ │ ├── components-intro.md
│ │ │ ├── context-variables.md
│ │ │ ├── forms.md
│ │ │ ├── globals.md
│ │ │ ├── glossary.md
│ │ │ ├── helper-tags.md
│ │ │ ├── hosted-deployment.md
│ │ │ ├── howto
│ │ │ │ ├── assign-a-complex-json-literal-to-a-component-variable.md
│ │ │ │ ├── chain-a-refetch.md
│ │ │ │ ├── control-cache-invalidation.md
│ │ │ │ ├── debounce-user-input-for-api-calls.md
│ │ │ │ ├── debounce-with-changelistener.md
│ │ │ │ ├── debug-a-component.md
│ │ │ │ ├── delay-a-datasource-until-another-datasource-is-ready.md
│ │ │ │ ├── delegate-a-method.md
│ │ │ │ ├── do-custom-form-validation.md
│ │ │ │ ├── expose-a-method-from-a-component.md
│ │ │ │ ├── filter-and-transform-data-from-an-api.md
│ │ │ │ ├── group-items-in-list-by-a-property.md
│ │ │ │ ├── handle-background-operations.md
│ │ │ │ ├── hide-an-element-until-its-datasource-is-ready.md
│ │ │ │ ├── make-a-set-of-equal-width-cards.md
│ │ │ │ ├── make-a-table-responsive.md
│ │ │ │ ├── make-navpanel-width-responsive.md
│ │ │ │ ├── modify-a-value-reported-in-a-column.md
│ │ │ │ ├── paginate-a-list.md
│ │ │ │ ├── pass-data-to-a-modal-dialog.md
│ │ │ │ ├── react-to-button-click-not-keystrokes.md
│ │ │ │ ├── set-the-initial-value-of-a-select-from-fetched-data.md
│ │ │ │ ├── share-a-modaldialog-across-components.md
│ │ │ │ ├── sync-selections-between-table-and-list-views.md
│ │ │ │ ├── update-ui-optimistically.md
│ │ │ │ ├── use-built-in-form-validation.md
│ │ │ │ └── use-the-same-modaldialog-to-add-or-edit.md
│ │ │ ├── howto.md
│ │ │ ├── intro.md
│ │ │ ├── layout.md
│ │ │ ├── markup.md
│ │ │ ├── mcp.md
│ │ │ ├── modal-dialogs.md
│ │ │ ├── news-and-reviews.md
│ │ │ ├── reactive-intro.md
│ │ │ ├── refactoring.md
│ │ │ ├── routing-and-links.md
│ │ │ ├── samples
│ │ │ │ ├── color-palette.xmlui
│ │ │ │ ├── color-values.xmlui
│ │ │ │ ├── shadow-sizes.xmlui
│ │ │ │ ├── spacing-sizes.xmlui
│ │ │ │ ├── swatch.xmlui
│ │ │ │ ├── theme-gallery-brief.xmlui
│ │ │ │ └── theme-gallery.xmlui
│ │ │ ├── scoping.md
│ │ │ ├── scripting.md
│ │ │ ├── styles-and-themes
│ │ │ │ ├── common-units.md
│ │ │ │ ├── layout-props.md
│ │ │ │ ├── theme-variable-defaults.md
│ │ │ │ ├── theme-variables.md
│ │ │ │ └── themes.md
│ │ │ ├── template-properties.md
│ │ │ ├── test.md
│ │ │ ├── tutorial-01.md
│ │ │ ├── tutorial-02.md
│ │ │ ├── tutorial-03.md
│ │ │ ├── tutorial-04.md
│ │ │ ├── tutorial-05.md
│ │ │ ├── tutorial-06.md
│ │ │ ├── tutorial-07.md
│ │ │ ├── tutorial-08.md
│ │ │ ├── tutorial-09.md
│ │ │ ├── tutorial-10.md
│ │ │ ├── tutorial-11.md
│ │ │ ├── tutorial-12.md
│ │ │ ├── universal-properties.md
│ │ │ ├── user-defined-components.md
│ │ │ ├── vscode.md
│ │ │ ├── working-with-markdown.md
│ │ │ ├── working-with-text.md
│ │ │ ├── xmlui-animations
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── Animation.md
│ │ │ │ ├── FadeAnimation.md
│ │ │ │ ├── FadeInAnimation.md
│ │ │ │ ├── FadeOutAnimation.md
│ │ │ │ ├── ScaleAnimation.md
│ │ │ │ └── SlideInAnimation.md
│ │ │ ├── xmlui-charts
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── BarChart.md
│ │ │ │ ├── DonutChart.md
│ │ │ │ ├── LabelList.md
│ │ │ │ ├── Legend.md
│ │ │ │ ├── LineChart.md
│ │ │ │ └── PieChart.md
│ │ │ ├── xmlui-pdf
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Pdf.md
│ │ │ └── xmlui-spreadsheet
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ └── Spreadsheet.md
│ │ ├── resources
│ │ │ ├── devdocs
│ │ │ │ ├── debug-proxy-object-2.png
│ │ │ │ ├── debug-proxy-object.png
│ │ │ │ ├── table_editor_01.png
│ │ │ │ ├── table_editor_02.png
│ │ │ │ ├── table_editor_03.png
│ │ │ │ ├── table_editor_04.png
│ │ │ │ ├── table_editor_05.png
│ │ │ │ ├── table_editor_06.png
│ │ │ │ ├── table_editor_07.png
│ │ │ │ ├── table_editor_08.png
│ │ │ │ ├── table_editor_09.png
│ │ │ │ ├── table_editor_10.png
│ │ │ │ ├── table_editor_11.png
│ │ │ │ ├── table-editor-01.png
│ │ │ │ ├── table-editor-02.png
│ │ │ │ ├── table-editor-03.png
│ │ │ │ ├── table-editor-04.png
│ │ │ │ ├── table-editor-06.png
│ │ │ │ ├── table-editor-07.png
│ │ │ │ ├── table-editor-08.png
│ │ │ │ ├── table-editor-09.png
│ │ │ │ └── xmlui-rendering-of-tiptap-markdown.png
│ │ │ ├── favicon.ico
│ │ │ ├── files
│ │ │ │ ├── clients.json
│ │ │ │ ├── daily-revenue.json
│ │ │ │ ├── dashboard-stats.json
│ │ │ │ ├── demo.xmlui
│ │ │ │ ├── demo.xmlui.xs
│ │ │ │ ├── downloads
│ │ │ │ │ └── downloads.json
│ │ │ │ ├── for-download
│ │ │ │ │ ├── index-with-api.html
│ │ │ │ │ ├── index.html
│ │ │ │ │ ├── mockApi.js
│ │ │ │ │ ├── start-darwin.sh
│ │ │ │ │ ├── start-linux.sh
│ │ │ │ │ ├── start.bat
│ │ │ │ │ └── xmlui
│ │ │ │ │ └── xmlui-standalone.umd.js
│ │ │ │ ├── getting-started
│ │ │ │ │ ├── cl-tutorial-final.zip
│ │ │ │ │ ├── cl-tutorial.zip
│ │ │ │ │ ├── cl-tutorial2.zip
│ │ │ │ │ ├── cl-tutorial3.zip
│ │ │ │ │ ├── cl-tutorial4.zip
│ │ │ │ │ ├── cl-tutorial5.zip
│ │ │ │ │ ├── cl-tutorial6.zip
│ │ │ │ │ ├── getting-started.zip
│ │ │ │ │ ├── hello-xmlui.zip
│ │ │ │ │ ├── xmlui-empty.zip
│ │ │ │ │ └── xmlui-starter.zip
│ │ │ │ ├── howto
│ │ │ │ │ └── component-icons
│ │ │ │ │ └── up-arrow.svg
│ │ │ │ ├── invoices.json
│ │ │ │ ├── monthly-status.json
│ │ │ │ ├── news-and-reviews.json
│ │ │ │ ├── products.json
│ │ │ │ ├── releases.json
│ │ │ │ ├── tutorials
│ │ │ │ │ ├── datasource
│ │ │ │ │ │ └── api.ts
│ │ │ │ │ └── p2do
│ │ │ │ │ ├── api.ts
│ │ │ │ │ └── todo-logo.svg
│ │ │ │ └── xmlui.json
│ │ │ ├── github.svg
│ │ │ ├── images
│ │ │ │ ├── apiaction-tutorial
│ │ │ │ │ ├── add-success.png
│ │ │ │ │ ├── apiaction-param.png
│ │ │ │ │ ├── change-completed.png
│ │ │ │ │ ├── change-in-progress.png
│ │ │ │ │ ├── confirm-delete.png
│ │ │ │ │ ├── data-error.png
│ │ │ │ │ ├── data-progress.png
│ │ │ │ │ ├── data-success.png
│ │ │ │ │ ├── display-1.png
│ │ │ │ │ ├── item-deleted.png
│ │ │ │ │ ├── item-updated.png
│ │ │ │ │ ├── missing-api-key.png
│ │ │ │ │ ├── new-item-added.png
│ │ │ │ │ └── test-message.png
│ │ │ │ ├── chat-api
│ │ │ │ │ └── domain-model.svg
│ │ │ │ ├── components
│ │ │ │ │ ├── image
│ │ │ │ │ │ └── breakfast.jpg
│ │ │ │ │ ├── markdown
│ │ │ │ │ │ └── colors.png
│ │ │ │ │ └── modal
│ │ │ │ │ ├── deep_link_dialog_1.jpg
│ │ │ │ │ └── deep_link_dialog_2.jpg
│ │ │ │ ├── create-apps
│ │ │ │ │ ├── collapsed-vertical.png
│ │ │ │ │ ├── using-forms-warning-dialog.png
│ │ │ │ │ └── using-forms.png
│ │ │ │ ├── datasource-tutorial
│ │ │ │ │ ├── data-with-header.png
│ │ │ │ │ ├── filtered-data.png
│ │ │ │ │ ├── filtered-items.png
│ │ │ │ │ ├── initial-page-items.png
│ │ │ │ │ ├── list-items.png
│ │ │ │ │ ├── next-page-items.png
│ │ │ │ │ ├── no-data.png
│ │ │ │ │ ├── pagination-1.jpg
│ │ │ │ │ ├── pagination-1.png
│ │ │ │ │ ├── polling-1.png
│ │ │ │ │ ├── refetch-data.png
│ │ │ │ │ ├── slow-loading.png
│ │ │ │ │ ├── test-message.png
│ │ │ │ │ ├── Thumbs.db
│ │ │ │ │ ├── unconventional-data.png
│ │ │ │ │ └── unfiltered-items.png
│ │ │ │ ├── flower.jpg
│ │ │ │ ├── get-started
│ │ │ │ │ ├── add-new-contact.png
│ │ │ │ │ ├── app-modified.png
│ │ │ │ │ ├── app-start.png
│ │ │ │ │ ├── app-with-boxes.png
│ │ │ │ │ ├── app-with-toast.png
│ │ │ │ │ ├── boilerplate-structure.png
│ │ │ │ │ ├── cl-initial.png
│ │ │ │ │ ├── cl-start.png
│ │ │ │ │ ├── contact-counts.png
│ │ │ │ │ ├── contact-dialog-title.png
│ │ │ │ │ ├── contact-dialog.png
│ │ │ │ │ ├── contact-menus.png
│ │ │ │ │ ├── contact-predicates.png
│ │ │ │ │ ├── context-menu.png
│ │ │ │ │ ├── dashboard-numbers.png
│ │ │ │ │ ├── default-contact-list.png
│ │ │ │ │ ├── delete-contact.png
│ │ │ │ │ ├── delete-task.png
│ │ │ │ │ ├── detailed-template.png
│ │ │ │ │ ├── edit-contact-details.png
│ │ │ │ │ ├── edited-contact-saved.png
│ │ │ │ │ ├── empty-sections.png
│ │ │ │ │ ├── filter-completed.png
│ │ │ │ │ ├── fullwidth-desktop.png
│ │ │ │ │ ├── fullwidth-mobile.png
│ │ │ │ │ ├── initial-table.png
│ │ │ │ │ ├── items-and-badges.png
│ │ │ │ │ ├── loading-message.png
│ │ │ │ │ ├── new-contact-button.png
│ │ │ │ │ ├── new-contact-saved.png
│ │ │ │ │ ├── no-empty-sections.png
│ │ │ │ │ ├── personal-todo-initial.png
│ │ │ │ │ ├── piechart.png
│ │ │ │ │ ├── review-today.png
│ │ │ │ │ ├── rudimentary-dashboard.png
│ │ │ │ │ ├── section-collapsed.png
│ │ │ │ │ ├── sectioned-items.png
│ │ │ │ │ ├── sections-ordered.png
│ │ │ │ │ ├── spacex-list-with-links.png
│ │ │ │ │ ├── spacex-list.png
│ │ │ │ │ ├── start-personal-todo-1.png
│ │ │ │ │ ├── submit-new-contact.png
│ │ │ │ │ ├── submit-new-task.png
│ │ │ │ │ ├── syntax-highlighting.png
│ │ │ │ │ ├── table-with-badge.png
│ │ │ │ │ ├── template-with-card.png
│ │ │ │ │ ├── test-emulated-api.png
│ │ │ │ │ ├── Thumbs.db
│ │ │ │ │ ├── todo-logo.png
│ │ │ │ │ └── xmlui-tools.png
│ │ │ │ ├── HelloApp.png
│ │ │ │ ├── HelloApp2.png
│ │ │ │ ├── logos
│ │ │ │ │ ├── xmlui1.svg
│ │ │ │ │ ├── xmlui2.svg
│ │ │ │ │ ├── xmlui3.svg
│ │ │ │ │ ├── xmlui4.svg
│ │ │ │ │ ├── xmlui5.svg
│ │ │ │ │ ├── xmlui6.svg
│ │ │ │ │ └── xmlui7.svg
│ │ │ │ ├── pdf
│ │ │ │ │ └── dummy-pdf.jpg
│ │ │ │ ├── rendering-engine
│ │ │ │ │ ├── AppEngine-flow.svg
│ │ │ │ │ ├── Component.svg
│ │ │ │ │ ├── CompoundComponent.svg
│ │ │ │ │ ├── RootComponent.svg
│ │ │ │ │ └── tree-with-containers.svg
│ │ │ │ ├── reviewers-guide
│ │ │ │ │ ├── AppEngine-flow.svg
│ │ │ │ │ └── incbutton-in-action.png
│ │ │ │ ├── tools
│ │ │ │ │ └── boilerplate-structure.png
│ │ │ │ ├── try.svg
│ │ │ │ ├── tutorial
│ │ │ │ │ ├── app-chat-history.png
│ │ │ │ │ ├── app-content-placeholder.png
│ │ │ │ │ ├── app-header-and-content.png
│ │ │ │ │ ├── app-links-channel-selected.png
│ │ │ │ │ ├── app-links-click.png
│ │ │ │ │ ├── app-navigation.png
│ │ │ │ │ ├── finished-ex01.png
│ │ │ │ │ ├── finished-ex02.png
│ │ │ │ │ ├── hello.png
│ │ │ │ │ ├── splash-screen-advanced.png
│ │ │ │ │ ├── splash-screen-after-click.png
│ │ │ │ │ ├── splash-screen-centered.png
│ │ │ │ │ ├── splash-screen-events.png
│ │ │ │ │ ├── splash-screen-expression.png
│ │ │ │ │ ├── splash-screen-reuse-after.png
│ │ │ │ │ ├── splash-screen-reuse-before.png
│ │ │ │ │ └── splash-screen.png
│ │ │ │ └── tutorial-01.png
│ │ │ ├── llms.txt
│ │ │ ├── logo-dark.svg
│ │ │ ├── logo.svg
│ │ │ ├── pg-popout.svg
│ │ │ └── xmlui-logo.svg
│ │ ├── serve.json
│ │ └── web.config
│ ├── scripts
│ │ ├── download-latest-xmlui.js
│ │ ├── generate-rss.js
│ │ ├── get-releases.js
│ │ └── utils.js
│ ├── src
│ │ ├── components
│ │ │ ├── BlogOverview.xmlui
│ │ │ ├── BlogPage.xmlui
│ │ │ ├── Boxes.xmlui
│ │ │ ├── Breadcrumb.xmlui
│ │ │ ├── ChangeLog.xmlui
│ │ │ ├── ColorPalette.xmlui
│ │ │ ├── DocumentLinks.xmlui
│ │ │ ├── DocumentPage.xmlui
│ │ │ ├── DocumentPageNoTOC.xmlui
│ │ │ ├── Icons.xmlui
│ │ │ ├── IncButton.xmlui
│ │ │ ├── IncButton2.xmlui
│ │ │ ├── NameValue.xmlui
│ │ │ ├── PageNotFound.xmlui
│ │ │ ├── PaletteItem.xmlui
│ │ │ ├── Palettes.xmlui
│ │ │ ├── SectionHeader.xmlui
│ │ │ ├── TBD.xmlui
│ │ │ ├── Test.xmlui
│ │ │ ├── ThemesIntro.xmlui
│ │ │ ├── ThousandThemes.xmlui
│ │ │ ├── TubeStops.xmlui
│ │ │ ├── TubeStops.xmlui.xs
│ │ │ └── TwoColumnCode.xmlui
│ │ ├── config.ts
│ │ ├── Main.xmlui
│ │ └── themes
│ │ ├── docs-theme.ts
│ │ ├── earthtone.ts
│ │ ├── xmlui-gray-on-default.ts
│ │ ├── xmlui-green-on-default.ts
│ │ └── xmlui-orange-on-default.ts
│ └── tsconfig.json
├── LICENSE
├── package-lock.json
├── package.json
├── packages
│ ├── tsconfig.json
│ ├── xmlui-animations
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── Animation.tsx
│ │ ├── AnimationNative.tsx
│ │ ├── FadeAnimation.tsx
│ │ ├── FadeInAnimation.tsx
│ │ ├── FadeOutAnimation.tsx
│ │ ├── index.tsx
│ │ ├── ScaleAnimation.tsx
│ │ └── SlideInAnimation.tsx
│ ├── xmlui-devtools
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ ├── src
│ │ │ ├── devtools
│ │ │ │ ├── DevTools.tsx
│ │ │ │ ├── DevToolsNative.module.scss
│ │ │ │ ├── DevToolsNative.tsx
│ │ │ │ ├── ModalDialog.module.scss
│ │ │ │ ├── ModalDialog.tsx
│ │ │ │ ├── ModalVisibilityContext.tsx
│ │ │ │ ├── Tooltip.module.scss
│ │ │ │ ├── Tooltip.tsx
│ │ │ │ └── utils.ts
│ │ │ ├── editor
│ │ │ │ └── Editor.tsx
│ │ │ └── index.tsx
│ │ └── vite.config-overrides.ts
│ ├── xmlui-hello-world
│ │ ├── .gitignore
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── HelloWorld.module.scss
│ │ ├── HelloWorld.tsx
│ │ ├── HelloWorldNative.tsx
│ │ └── index.tsx
│ ├── xmlui-os-frames
│ │ ├── .gitignore
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── index.tsx
│ │ ├── IPhoneFrame.module.scss
│ │ ├── IPhoneFrame.tsx
│ │ ├── MacOSAppFrame.module.scss
│ │ ├── MacOSAppFrame.tsx
│ │ ├── WindowsAppFrame.module.scss
│ │ └── WindowsAppFrame.tsx
│ ├── xmlui-pdf
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ ├── components
│ │ │ │ └── Pdf.xmlui
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── index.tsx
│ │ ├── LazyPdfNative.tsx
│ │ ├── Pdf.module.scss
│ │ └── Pdf.tsx
│ ├── xmlui-playground
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── hooks
│ │ │ ├── usePlayground.ts
│ │ │ └── useToast.ts
│ │ ├── index.tsx
│ │ ├── playground
│ │ │ ├── Box.module.scss
│ │ │ ├── Box.tsx
│ │ │ ├── CodeSelector.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/content/components/TextArea.md:
--------------------------------------------------------------------------------
```markdown
1 | # TextArea [#textarea]
2 |
3 | `TextArea` provides a multiline text input area.
4 |
5 | It is often used in forms, see [this guide](/forms) for details.
6 |
7 | ## Properties [#properties]
8 |
9 | ### `autoFocus` (default: false) [#autofocus-default-false]
10 |
11 | If this property is set to `true`, the component gets the focus automatically when displayed.
12 |
13 | ### `autoSize` (default: false) [#autosize-default-false]
14 |
15 | If set to `true`, this boolean property enables the `TextArea` to resize automatically based on the number of lines inside it.
16 |
17 | > **Note**: If either `autoSize`, `maxRows` or `minRows` is set, the `rows` prop has no effect.
18 |
19 | Write multiple lines in the `TextArea` in the demo below to see how it resizes automatically.
20 |
21 | ```xmlui-pg copy display name="Example: autoSize" height="240px"
22 | <App>
23 | <TextArea autoSize="true" />
24 | </App>
25 | ```
26 |
27 | ### `enabled` (default: true) [#enabled-default-true]
28 |
29 | This boolean property value indicates whether the component responds to user events (`true`) or not (`false`).
30 |
31 | ```xmlui-pg copy display name="Example: enabled"
32 | <App>
33 | <TextArea enabled="false" />
34 | </App>
35 | ```
36 |
37 | ### `enterSubmits` (default: true) [#entersubmits-default-true]
38 |
39 | This optional boolean property indicates whether pressing the `Enter` key on the keyboard prompts the parent `Form` component to submit.
40 |
41 | Press `Enter` after writing something in the `TextArea` in the demo below.
42 | See [Using Forms](/forms) for details.
43 |
44 | ```xmlui-pg copy display name="Example: enterSubmits"
45 | <App>
46 | <Form onSubmit="toast.success(JSON.stringify(address.value))">
47 | <TextArea
48 | id="address"
49 | enterSubmits="true"
50 | initialValue="Suzy Queue, 4455 Landing Lange, APT 4, Louisville, KY 40018-1234" />
51 | </Form>
52 | </App>
53 | ```
54 |
55 | ### `escResets` (default: false) [#escresets-default-false]
56 |
57 | This boolean property indicates whether the TextArea contents should be reset when pressing the ESC key.
58 |
59 | ### `initialValue` [#initialvalue]
60 |
61 | This property sets the component's initial value.
62 |
63 | The initial value displayed in the input field.
64 |
65 | ```xmlui-pg copy display name="Example: initialValue"
66 | <App>
67 | <TextArea initialValue="Example text" />
68 | </App>
69 | ```
70 |
71 | ### `maxLength` [#maxlength]
72 |
73 | This property sets the maximum length of the input it accepts.
74 |
75 | ### `maxRows` [#maxrows]
76 |
77 | This optional property sets the maximum number of text rows the `TextArea` can grow. If not set, the number of rows is unlimited.
78 |
79 | > **Note**: If either `autoSize`, `maxRows` or `minRows` is set, the `rows` prop has no effect.
80 |
81 | ```xmlui-pg copy {3} display name="Example: maxRows" height="160px"
82 | <App>
83 | <TextArea
84 | maxRows="3"
85 | initialValue="Lorem ipsum dolor sit amet,
86 | consectetur adipiscing elit,
87 | sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
88 | Ut enim ad minim veniam,
89 | quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." />
90 | </App>
91 | ```
92 |
93 | ### `minRows` [#minrows]
94 |
95 | This optional property sets the minimum number of text rows the `TextArea` can shrink. If not set, the minimum number of rows is 1.
96 |
97 | > **Note**: If either `autoSize`, `maxRows` or `minRows` is set, the `rows` prop has no effect.
98 |
99 | ```xmlui-pg copy display name="Example: minRows" height="200px"
100 | <App>
101 | <TextArea minRows="3" initialValue="Lorem ipsum dolor sit amet..." />
102 | </App>
103 | ```
104 |
105 | ### `placeholder` [#placeholder]
106 |
107 | An optional placeholder text that is visible in the input field when its empty.
108 |
109 | ```xmlui-pg copy display name="Example: placeholder"
110 | <App>
111 | <TextArea placeholder="This is a placeholder" />
112 | </App>
113 | ```
114 |
115 | ### `readOnly` (default: false) [#readonly-default-false]
116 |
117 | Set this property to `true` to disallow changing the component value.
118 |
119 | ```xmlui-pg copy display name="Example: readOnly"
120 | <App>
121 | <TextArea initialValue="Example text" readOnly="{true}" />
122 | </App>
123 | ```
124 |
125 | ### `required` (default: false) [#required-default-false]
126 |
127 | Set this property to `true` to indicate it must have a value before submitting the containing form.
128 |
129 | ### `resize` [#resize]
130 |
131 | This optional property specifies in which dimensions can the `TextArea` be resized by the user.
132 |
133 | Available values:
134 |
135 | | Value | Description |
136 | | --- | --- |
137 | | `(undefined)` | No resizing |
138 | | `horizontal` | Can only resize horizontally |
139 | | `vertical` | Can only resize vertically |
140 | | `both` | Can resize in both dimensions |
141 |
142 | If you allow resizing, the `TextArea` turns off automatic sizing.
143 |
144 | When you allow vertical resizing, you can limit the sizable range according to `minRows` and `maxRows`.
145 |
146 | Drag the small resize indicators at the bottom right on each of the controls in the demo.
147 |
148 | ```xmlui-pg copy display name="Example: resize" height="300px"
149 | <App>
150 | <TextArea resize="vertical" minRows="1" maxRows="8" />
151 | <TextArea resize="both" />
152 | </App>
153 | ```
154 |
155 | ### `rows` (default: 2) [#rows-default-2]
156 |
157 | Specifies the number of rows the component initially has.
158 |
159 | > **Note**: If either `autoSize`, `maxRows` or `minRows` is set, the `rows` prop has no effect.
160 |
161 | ```xmlui-pg copy display name="Example: rows"
162 | <App>
163 | <TextArea rows="10" />
164 | </App>
165 | ```
166 |
167 | ### `validationStatus` (default: "none") [#validationstatus-default-none]
168 |
169 | This property allows you to set the validation status of the input component.
170 |
171 | Available values:
172 |
173 | | Value | Description |
174 | | --- | --- |
175 | | `valid` | Visual indicator for an input that is accepted |
176 | | `warning` | Visual indicator for an input that produced a warning |
177 | | `error` | Visual indicator for an input that produced an error |
178 |
179 | This prop is used to visually indicate status changes reacting to form field validation.
180 |
181 | ```xmlui-pg copy display name="Example: validationStatus"
182 | <App>
183 | <TextArea />
184 | <TextArea validationStatus="valid" />
185 | <TextArea validationStatus="warning" />
186 | <TextArea validationStatus="error" />
187 | </App>
188 | ```
189 |
190 | ## Events [#events]
191 |
192 | ### `didChange` [#didchange]
193 |
194 | This event is triggered when value of TextArea has changed.
195 |
196 | Write in the input field and see how the `Text` underneath it is updated in parallel.
197 |
198 | ```xmlui-pg copy display name="Example: didChange"
199 | <App var.field="">
200 | <TextArea
201 | initialValue="{field}"
202 | onDidChange="(val) => field = val"
203 | />
204 | <Text value="{field}" />
205 | </App>
206 | ```
207 |
208 | ### `gotFocus` [#gotfocus]
209 |
210 | This event is triggered when the TextArea has received the focus.
211 |
212 | Clicking on the `TextArea` in the example demo changes the label text.
213 | Note how clicking elsewhere resets the text to the original.
214 |
215 | ```xmlui-pg copy display name="Example: gotFocus/lostFocus"
216 | <App>
217 | <TextArea
218 | initialValue="{focused === true ? 'I got focused!' : 'I lost focus...'}"
219 | onGotFocus="focused = true"
220 | onLostFocus="focused = false"
221 | var.focused="{false}" />
222 | </App>
223 | ```
224 |
225 | ### `lostFocus` [#lostfocus]
226 |
227 | This event is triggered when the TextArea has lost the focus.
228 |
229 | ## Exposed Methods [#exposed-methods]
230 |
231 | ### `focus` [#focus]
232 |
233 | This method sets the focus on the `TextArea` component.
234 |
235 | **Signature**: `focus(): void`
236 |
237 | ```xmlui-pg copy display name="Example: focus"
238 | <App>
239 | <Button label="Trigger Focus" onClick="inputComponent.focus()" />
240 | <TextArea id="inputComponent" />
241 | </App>
242 | ```
243 |
244 | ### `setValue` [#setvalue]
245 |
246 | You can use this method to set the component's current value programmatically (`true`: checked, `false`: unchecked).
247 |
248 | ```xmlui-pg copy display name="Example: setValue"
249 | <App var.changes="">
250 | <TextArea
251 | id="inputField"
252 | readOnly="true"
253 | onDidChange="changes++" />
254 | <HStack>
255 | <Button
256 | label="Check"
257 | onClick="inputField.setValue('example ')" />
258 | <Text value="Number of changes: {changes}" />
259 | </HStack>
260 | </App>
261 | ```
262 |
263 | ### `value` [#value]
264 |
265 | You can query the component's value. If no value is set, it will retrieve `undefined`.
266 |
267 | **Signature**: `get value(): string | undefined`
268 |
269 | In the example below, typing in the `TextArea` will also display the length of the text typed into it above the field:
270 |
271 | ```xmlui-pg copy display name="Example: value"
272 | <App>
273 | <Text value="TextArea content length: {inputComponent.value.length}" />
274 | <TextArea id="inputComponent" />
275 | </App>
276 | ```
277 |
278 | ## Parts [#parts]
279 |
280 | The component has some parts that can be styled through layout properties and theme variables separately:
281 |
282 | - **`endAdornment`**: The adornment displayed at the end of the text area.
283 | - **`input`**: The text area input.
284 | - **`label`**: The label displayed for the text area.
285 | - **`startAdornment`**: The adornment displayed at the start of the text area.
286 |
287 | ## Styling [#styling]
288 |
289 | ### Theme Variables [#theme-variables]
290 |
291 | | Variable | Default Value (Light) | Default Value (Dark) |
292 | | --- | --- | --- |
293 | | [backgroundColor](../styles-and-themes/common-units/#color)-TextArea--default | *none* | *none* |
294 | | [backgroundColor](../styles-and-themes/common-units/#color)-TextArea--default--focus | *none* | *none* |
295 | | [backgroundColor](../styles-and-themes/common-units/#color)-TextArea--default--hover | *none* | *none* |
296 | | [backgroundColor](../styles-and-themes/common-units/#color)-TextArea--disabled | *none* | *none* |
297 | | [backgroundColor](../styles-and-themes/common-units/#color)-TextArea--error | *none* | *none* |
298 | | [backgroundColor](../styles-and-themes/common-units/#color)-TextArea--error--focus | *none* | *none* |
299 | | [backgroundColor](../styles-and-themes/common-units/#color)-TextArea--error--hover | *none* | *none* |
300 | | [backgroundColor](../styles-and-themes/common-units/#color)-TextArea--success | *none* | *none* |
301 | | [backgroundColor](../styles-and-themes/common-units/#color)-TextArea--success--focus | *none* | *none* |
302 | | [backgroundColor](../styles-and-themes/common-units/#color)-TextArea--success--hover | *none* | *none* |
303 | | [backgroundColor](../styles-and-themes/common-units/#color)-TextArea--warning | *none* | *none* |
304 | | [backgroundColor](../styles-and-themes/common-units/#color)-TextArea--warning--focus | *none* | *none* |
305 | | [backgroundColor](../styles-and-themes/common-units/#color)-TextArea--warning--hover | *none* | *none* |
306 | | [borderColor](../styles-and-themes/common-units/#color)-TextArea--default | *none* | *none* |
307 | | [borderColor](../styles-and-themes/common-units/#color)-TextArea--default--focus | *none* | *none* |
308 | | [borderColor](../styles-and-themes/common-units/#color)-TextArea--default--hover | *none* | *none* |
309 | | [borderColor](../styles-and-themes/common-units/#color)-TextArea--disabled | *none* | *none* |
310 | | [borderColor](../styles-and-themes/common-units/#color)-TextArea--error | *none* | *none* |
311 | | [borderColor](../styles-and-themes/common-units/#color)-TextArea--error--focus | *none* | *none* |
312 | | [borderColor](../styles-and-themes/common-units/#color)-TextArea--error--hover | *none* | *none* |
313 | | [borderColor](../styles-and-themes/common-units/#color)-TextArea--success | *none* | *none* |
314 | | [borderColor](../styles-and-themes/common-units/#color)-TextArea--success--focus | *none* | *none* |
315 | | [borderColor](../styles-and-themes/common-units/#color)-TextArea--success--hover | *none* | *none* |
316 | | [borderColor](../styles-and-themes/common-units/#color)-TextArea--warning | *none* | *none* |
317 | | [borderColor](../styles-and-themes/common-units/#color)-TextArea--warning--focus | *none* | *none* |
318 | | [borderColor](../styles-and-themes/common-units/#color)-TextArea--warning--hover | *none* | *none* |
319 | | [borderRadius](../styles-and-themes/common-units/#border-rounding)-TextArea--default | *none* | *none* |
320 | | [borderRadius](../styles-and-themes/common-units/#border-rounding)-TextArea--error | *none* | *none* |
321 | | [borderRadius](../styles-and-themes/common-units/#border-rounding)-TextArea--success | *none* | *none* |
322 | | [borderRadius](../styles-and-themes/common-units/#border-rounding)-TextArea--warning | *none* | *none* |
323 | | [borderStyle](../styles-and-themes/common-units/#border-style)-TextArea--default | *none* | *none* |
324 | | [borderStyle](../styles-and-themes/common-units/#border-style)-TextArea--error | *none* | *none* |
325 | | [borderStyle](../styles-and-themes/common-units/#border-style)-TextArea--success | *none* | *none* |
326 | | [borderStyle](../styles-and-themes/common-units/#border-style)-TextArea--warning | *none* | *none* |
327 | | [borderWidth](../styles-and-themes/common-units/#size)-TextArea--default | *none* | *none* |
328 | | [borderWidth](../styles-and-themes/common-units/#size)-TextArea--error | *none* | *none* |
329 | | [borderWidth](../styles-and-themes/common-units/#size)-TextArea--success | *none* | *none* |
330 | | [borderWidth](../styles-and-themes/common-units/#size)-TextArea--warning | *none* | *none* |
331 | | [boxShadow](../styles-and-themes/common-units/#boxShadow)-TextArea--default | *none* | *none* |
332 | | [boxShadow](../styles-and-themes/common-units/#boxShadow)-TextArea--default--focus | *none* | *none* |
333 | | [boxShadow](../styles-and-themes/common-units/#boxShadow)-TextArea--default--hover | *none* | *none* |
334 | | [boxShadow](../styles-and-themes/common-units/#boxShadow)-TextArea--error | *none* | *none* |
335 | | [boxShadow](../styles-and-themes/common-units/#boxShadow)-TextArea--error--focus | *none* | *none* |
336 | | [boxShadow](../styles-and-themes/common-units/#boxShadow)-TextArea--error--hover | *none* | *none* |
337 | | [boxShadow](../styles-and-themes/common-units/#boxShadow)-TextArea--success | *none* | *none* |
338 | | [boxShadow](../styles-and-themes/common-units/#boxShadow)-TextArea--success--focus | *none* | *none* |
339 | | [boxShadow](../styles-and-themes/common-units/#boxShadow)-TextArea--success--hover | *none* | *none* |
340 | | [boxShadow](../styles-and-themes/common-units/#boxShadow)-TextArea--warning | *none* | *none* |
341 | | [boxShadow](../styles-and-themes/common-units/#boxShadow)-TextArea--warning--focus | *none* | *none* |
342 | | [boxShadow](../styles-and-themes/common-units/#boxShadow)-TextArea--warning--hover | *none* | *none* |
343 | | [fontSize](../styles-and-themes/common-units/#size)-placeholder-TextArea--default | *none* | *none* |
344 | | [fontSize](../styles-and-themes/common-units/#size)-placeholder-TextArea--error | *none* | *none* |
345 | | [fontSize](../styles-and-themes/common-units/#size)-placeholder-TextArea--success | *none* | *none* |
346 | | [fontSize](../styles-and-themes/common-units/#size)-placeholder-TextArea--warning | *none* | *none* |
347 | | [fontSize](../styles-and-themes/common-units/#size)-TextArea--default | *none* | *none* |
348 | | [fontSize](../styles-and-themes/common-units/#size)-TextArea--error | *none* | *none* |
349 | | [fontSize](../styles-and-themes/common-units/#size)-TextArea--success | *none* | *none* |
350 | | [fontSize](../styles-and-themes/common-units/#size)-TextArea--warning | *none* | *none* |
351 | | [outlineColor](../styles-and-themes/common-units/#color)-TextArea--default--focus | *none* | *none* |
352 | | [outlineColor](../styles-and-themes/common-units/#color)-TextArea--error--focus | *none* | *none* |
353 | | [outlineColor](../styles-and-themes/common-units/#color)-TextArea--success--focus | *none* | *none* |
354 | | [outlineColor](../styles-and-themes/common-units/#color)-TextArea--warning--focus | *none* | *none* |
355 | | [outlineOffset](../styles-and-themes/common-units/#size)-TextArea--default--focus | *none* | *none* |
356 | | [outlineOffset](../styles-and-themes/common-units/#size)-TextArea--error--focus | *none* | *none* |
357 | | [outlineOffset](../styles-and-themes/common-units/#size)-TextArea--success--focus | *none* | *none* |
358 | | [outlineOffset](../styles-and-themes/common-units/#size)-TextArea--warning--focus | *none* | *none* |
359 | | [outlineStyle](../styles-and-themes/common-units/#border)-TextArea--default--focus | *none* | *none* |
360 | | [outlineStyle](../styles-and-themes/common-units/#border)-TextArea--error--focus | *none* | *none* |
361 | | [outlineStyle](../styles-and-themes/common-units/#border)-TextArea--success--focus | *none* | *none* |
362 | | [outlineStyle](../styles-and-themes/common-units/#border)-TextArea--warning--focus | *none* | *none* |
363 | | [outlineWidth](../styles-and-themes/common-units/#size)-TextArea--default--focus | *none* | *none* |
364 | | [outlineWidth](../styles-and-themes/common-units/#size)-TextArea--error--focus | *none* | *none* |
365 | | [outlineWidth](../styles-and-themes/common-units/#size)-TextArea--success--focus | *none* | *none* |
366 | | [outlineWidth](../styles-and-themes/common-units/#size)-TextArea--warning--focus | *none* | *none* |
367 | | [padding](../styles-and-themes/common-units/#size)-TextArea | *none* | *none* |
368 | | [paddingBottom](../styles-and-themes/common-units/#size)-TextArea | *none* | *none* |
369 | | [paddingHorizontal](../styles-and-themes/common-units/#size)-TextArea | $space-2 | $space-2 |
370 | | [paddingLeft](../styles-and-themes/common-units/#size)-TextArea | *none* | *none* |
371 | | [paddingRight](../styles-and-themes/common-units/#size)-TextArea | *none* | *none* |
372 | | [paddingTop](../styles-and-themes/common-units/#size)-TextArea | *none* | *none* |
373 | | [paddingVertical](../styles-and-themes/common-units/#size)-TextArea | $space-2 | $space-2 |
374 | | [textColor](../styles-and-themes/common-units/#color)-placeholder-TextArea--default | *none* | *none* |
375 | | [textColor](../styles-and-themes/common-units/#color)-placeholder-TextArea--error | *none* | *none* |
376 | | [textColor](../styles-and-themes/common-units/#color)-placeholder-TextArea--success | *none* | *none* |
377 | | [textColor](../styles-and-themes/common-units/#color)-placeholder-TextArea--warning | *none* | *none* |
378 | | [textColor](../styles-and-themes/common-units/#color)-TextArea--default | *none* | *none* |
379 | | [textColor](../styles-and-themes/common-units/#color)-TextArea--default--focus | *none* | *none* |
380 | | [textColor](../styles-and-themes/common-units/#color)-TextArea--default--hover | *none* | *none* |
381 | | [textColor](../styles-and-themes/common-units/#color)-TextArea--disabled | *none* | *none* |
382 | | [textColor](../styles-and-themes/common-units/#color)-TextArea--error | *none* | *none* |
383 | | [textColor](../styles-and-themes/common-units/#color)-TextArea--error--focus | *none* | *none* |
384 | | [textColor](../styles-and-themes/common-units/#color)-TextArea--error--hover | *none* | *none* |
385 | | [textColor](../styles-and-themes/common-units/#color)-TextArea--success | *none* | *none* |
386 | | [textColor](../styles-and-themes/common-units/#color)-TextArea--success--focus | *none* | *none* |
387 | | [textColor](../styles-and-themes/common-units/#color)-TextArea--success--hover | *none* | *none* |
388 | | [textColor](../styles-and-themes/common-units/#color)-TextArea--warning | *none* | *none* |
389 | | [textColor](../styles-and-themes/common-units/#color)-TextArea--warning--focus | *none* | *none* |
390 | | [textColor](../styles-and-themes/common-units/#color)-TextArea--warning--hover | *none* | *none* |
391 |
```
--------------------------------------------------------------------------------
/xmlui/src/components/ResponsiveBar/ResponsiveBarNative.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import React, {
2 | forwardRef,
3 | useRef,
4 | useEffect,
5 | useState,
6 | type CSSProperties,
7 | type ReactNode,
8 | type ReactElement,
9 | } from "react";
10 | import classnames from "classnames";
11 |
12 | import styles from "./ResponsiveBar.module.scss";
13 | import { useResizeObserver } from "../../components-core/utils/hooks";
14 | import { DropdownMenu, MenuItem } from "../DropdownMenu/DropdownMenuNative";
15 |
16 | /**
17 | * ResponsiveBar Component - Adaptive Layout with Overflow Management
18 | *
19 | * The ResponsiveBar is a sophisticated layout component that automatically manages space
20 | * by moving child components that don't fit into a dropdown menu. It supports both horizontal
21 | * and vertical orientations, making it ideal for toolbars, navigation bars, sidebars, and
22 | * action panels that need to adapt to different screen sizes and container dimensions.
23 | *
24 | * ## How It Works:
25 | *
26 | * ### Orientation Support
27 | * - **Horizontal**: Items are arranged left-to-right, overflow is based on container width
28 | * - **Vertical**: Items are arranged top-to-bottom, overflow is based on container height
29 | * - Uses the parent container's available space (width for horizontal, height for vertical)
30 | *
31 | * ### Two-Phase Rendering System
32 | * The component uses a two-phase rendering approach to accurately calculate layout:
33 | *
34 | * **Phase 1 - Measurement Phase:**
35 | * - Renders all child components invisibly (visibility: hidden, opacity: 0)
36 | * - Measures the actual size of each child component using getBoundingClientRect()
37 | * - For horizontal: measures width; for vertical: measures height
38 | * - Also measures the dropdown button size for accurate overflow calculations
39 | * - This phase ensures we have precise measurements before making layout decisions
40 | *
41 | * **Phase 2 - Layout Phase:**
42 | * - Uses the measured dimensions to calculate which items fit in the available space
43 | * - Renders visible items in the main container and overflow items in a dropdown menu
44 | * - Accounts for gaps between items and the space needed for the dropdown button
45 | *
46 | * ### Overflow Logic
47 | * The overflow calculation works as follows:
48 | * 1. Calculate total size needed for all items (including gaps)
49 | * 2. If all items fit: show all items, no dropdown
50 | * 3. If overflow needed: calculate how many items can fit alongside the dropdown button
51 | * 4. Ensure at least one item is always visible (even if it means showing dropdown with one item)
52 | * 5. Account for gaps between items and the gap before the dropdown button
53 | *
54 | * ### Responsive Behavior
55 | * - Uses ResizeObserver to detect container size changes and recalculate layout
56 | * - Monitors width changes for horizontal orientation, height changes for vertical
57 | * - Prevents infinite loops by only triggering recalculation during the layout phase
58 | * - Debounces calculations to avoid excessive re-renders during rapid resize events
59 | *
60 | * ### Performance Optimizations
61 | * - Stable children array using React.useMemo to prevent unnecessary re-measurements
62 | * - Reference tracking to detect actual changes vs. rendering artifacts
63 | * - Calculation throttling to prevent excessive DOM measurements
64 | * - Layout completion tracking to ignore temporary children changes during layout updates
65 | *
66 | * ### Key Features
67 | * - **Dual orientation support**: Works in both horizontal and vertical layouts
68 | * - **Automatic overflow management**: Items that don't fit are moved to a dropdown
69 | * - **Configurable gaps**: Consistent spacing between items and dropdown
70 | * - **Custom overflow icon**: Customizable dropdown trigger button icon
71 | * - **Responsive**: Automatically adapts to container size changes
72 | * - **Accessible**: Uses proper dropdown menu with keyboard navigation
73 | * - **Performance optimized**: Minimal re-renders and efficient DOM measurements
74 | *
75 | * @example
76 | * ```tsx
77 | * // Horizontal toolbar
78 | * <ResponsiveBar orientation="horizontal" gap={8} overflowIcon="menu">
79 | * <Button>Action 1</Button>
80 | * <Button>Action 2</Button>
81 | * <Button>Action 3</Button>
82 | * </ResponsiveBar>
83 | *
84 | * // Vertical sidebar
85 | * <ResponsiveBar orientation="vertical" gap={4} overflowIcon="moreVertical">
86 | * <NavItem>Home</NavItem>
87 | * <NavItem>Settings</NavItem>
88 | * <NavItem>Profile</NavItem>
89 | * </ResponsiveBar>
90 | * ```
91 | */
92 |
93 | type ResponsiveBarProps = {
94 | children?: ReactNode;
95 | overflowIcon?: string;
96 | gap?: number; // Gap between children in pixels
97 | orientation?: "horizontal" | "vertical"; // Layout direction
98 | style?: CSSProperties;
99 | className?: string;
100 | onClick?: () => void;
101 | [key: string]: any; // For test props
102 | };
103 |
104 | interface LayoutState {
105 | visibleItems: ReactElement[];
106 | overflowItems: ReactElement[];
107 | }
108 |
109 | // Helper component to avoid duplication of DropdownMenu properties
110 | const ResponsiveBarDropdown = ({
111 | overflowIcon,
112 | children,
113 | className
114 | }: {
115 | overflowIcon: string;
116 | children: ReactNode;
117 | className?: string;
118 | }) => (
119 | <div className={className}>
120 | <DropdownMenu label="More options" triggerButtonIcon={overflowIcon}>
121 | {children}
122 | </DropdownMenu>
123 | </div>
124 | );
125 |
126 | export const defaultResponsiveBarProps = {
127 | overflowIcon: "ellipsisHorizontal:ResponsiveBar",
128 | gap: 0,
129 | orientation: "horizontal" as const,
130 | };
131 |
132 | export const ResponsiveBar = forwardRef<HTMLDivElement, ResponsiveBarProps>(function ResponsiveBar(
133 | {
134 | children,
135 | overflowIcon = defaultResponsiveBarProps.overflowIcon,
136 | gap = defaultResponsiveBarProps.gap,
137 | orientation = defaultResponsiveBarProps.orientation,
138 | style,
139 | className,
140 | onClick,
141 | ...rest
142 | }: ResponsiveBarProps,
143 | ref,
144 | ) {
145 | const containerRef = useRef<HTMLDivElement>(null);
146 | const measurementDropdownRef = useRef<HTMLDivElement>(null);
147 | const isCalculatingRef = useRef(false);
148 | const lastContainerSize = useRef(0); // Renamed for clarity - stores width OR height
149 | const lastChildrenCount = useRef(0);
150 | const lastChildrenRef = useRef<ReactNode>(null);
151 | const layoutCompletedRef = useRef(false);
152 |
153 | // Two-phase rendering state
154 | const [isInMeasurementPhase, setIsInMeasurementPhase] = useState(true);
155 | const [measuredWidths, setMeasuredWidths] = useState<number[]>([]);
156 |
157 | // Simple layout state
158 | const [layout, setLayout] = useState<LayoutState>({
159 | visibleItems: [],
160 | overflowItems: [],
161 | });
162 |
163 | // Convert children to array for processing - use stable reference
164 | const childrenArray = React.useMemo(() => {
165 | const result = React.Children.toArray(children).filter((child): child is ReactElement =>
166 | React.isValidElement(child),
167 | );
168 | return result;
169 | }, [children]);
170 |
171 | // Stable children count to prevent unnecessary re-measurements
172 | const childrenCount = childrenArray.length;
173 |
174 | // Measurement phase - measure all items in the same container
175 | const measureItems = () => {
176 | if (!containerRef.current) return;
177 |
178 | const items: HTMLElement[] = [];
179 |
180 | // Get the visibleItems container (during measurement phase, it has inline visibility styles)
181 | const measurementContainer = containerRef.current.querySelector(
182 | `.${styles.visibleItems}`,
183 | ) as HTMLElement;
184 |
185 | if (measurementContainer) {
186 | // Get all direct child divs within the measurement container
187 | const childDivs = Array.from(measurementContainer.children) as HTMLElement[];
188 |
189 | childDivs.forEach((div) => {
190 | const child = div.firstElementChild as HTMLElement;
191 | if (child) {
192 | items.push(child);
193 | }
194 | });
195 | }
196 |
197 | const measurements = items.map((item) => {
198 | const rect = item.getBoundingClientRect();
199 | // For horizontal orientation, use width; for vertical, use height
200 | return orientation === "horizontal" ? rect.width : rect.height;
201 | });
202 |
203 | setMeasuredWidths(measurements);
204 | setIsInMeasurementPhase(false);
205 | };
206 |
207 | // Calculate overflow layout with pre-measured dimensions
208 | const calculateOverflowLayout = () => {
209 | if (
210 | isCalculatingRef.current ||
211 | !containerRef.current ||
212 | childrenArray.length === 0 ||
213 | measuredWidths.length === 0
214 | ) {
215 | return;
216 | }
217 |
218 | const container = containerRef.current;
219 | const containerRect = container.getBoundingClientRect();
220 | const containerSize = orientation === "horizontal" ? containerRect.width : containerRect.height;
221 | const lastSize = lastContainerSize.current;
222 |
223 | if (containerSize === 0 || containerSize === lastSize) {
224 | return; // Not ready yet or no change
225 | }
226 |
227 | // Debug logging
228 | console.log(`ResponsiveBar ${orientation}: containerSize=${containerSize}, children=${childrenArray.length}`);
229 |
230 | isCalculatingRef.current = true;
231 | lastContainerSize.current = containerSize;
232 |
233 | // Use the gap prop instead of computed style
234 | const gapValue = gap;
235 |
236 | // First, calculate if all items fit without any dropdown
237 | let totalSize = 0;
238 |
239 | for (let i = 0; i < childrenArray.length; i++) {
240 | const childSize = measuredWidths[i];
241 | if (!childSize) continue;
242 |
243 | const gapSize = i > 0 ? gapValue : 0;
244 | totalSize += gapSize + childSize;
245 | }
246 |
247 | console.log(`Total size needed: ${totalSize}, available: ${containerSize}`);
248 |
249 | let visibleItems: ReactElement[];
250 | let overflowItems: ReactElement[];
251 |
252 | // If all items fit, show all
253 | if (totalSize <= containerSize) {
254 | visibleItems = childrenArray;
255 | overflowItems = [];
256 | } else {
257 | // Need overflow - measure actual dropdown size
258 | let dropdownSize = orientation === "horizontal" ? 147 : 47; // Different fallback for vertical
259 |
260 | // First try existing dropdown in the visible layout
261 | const existingDropdown = container.querySelector(
262 | `.${styles.overflowDropdown}`,
263 | ) as HTMLElement;
264 | if (existingDropdown) {
265 | const dropdownRect = existingDropdown.getBoundingClientRect();
266 | dropdownSize = orientation === "horizontal" ? dropdownRect.width : dropdownRect.height;
267 | }
268 | // Then try the measurement dropdown
269 | else if (measurementDropdownRef.current) {
270 | const dropdownRect = measurementDropdownRef.current.getBoundingClientRect();
271 | dropdownSize = orientation === "horizontal" ? dropdownRect.width : dropdownRect.height;
272 | }
273 |
274 | console.log(`Dropdown size: ${dropdownSize}`);
275 |
276 | let accumulatedSize = 0;
277 | let visibleCount = 0;
278 |
279 | for (let i = 0; i < childrenArray.length; i++) {
280 | const childSize = measuredWidths[i];
281 | if (!childSize) continue;
282 |
283 | const gapSize = i > 0 ? gapValue : 0;
284 | const proposedSize = accumulatedSize + gapSize + childSize;
285 |
286 | // Check if this item would fit alongside the dropdown
287 | // When we have overflow: visibleItems + dropdown = visibleCount items + visibleCount gaps
288 | // The proposedSize already accounts for (visibleCount-1) gaps between items
289 | // We need to add 1 more gap for the space before the dropdown
290 | const totalSizeWithDropdown = proposedSize + gapValue + dropdownSize;
291 |
292 | console.log(`Item ${i}: size=${childSize}, proposed=${proposedSize}, withDropdown=${totalSizeWithDropdown}`);
293 |
294 | if (totalSizeWithDropdown <= containerSize) {
295 | accumulatedSize = proposedSize;
296 | visibleCount++;
297 | } else {
298 | // This item doesn't fit when considering the dropdown gap, so stop here
299 | break;
300 | }
301 | }
302 |
303 | // Verify our calculation
304 | if (visibleCount > 0) {
305 | let verifyWidth = 0;
306 | for (let i = 0; i < visibleCount; i++) {
307 | verifyWidth += measuredWidths[i];
308 | }
309 | // When we have overflow: visibleCount items + dropdown need visibleCount gaps total
310 | // (visibleCount-1 gaps between items + 1 gap before dropdown)
311 | const totalGaps = visibleCount * gapValue;
312 | }
313 |
314 | // Also check actual visible item widths for comparison
315 | const visibleContainer = containerRef.current?.querySelector(
316 | `.${styles.visibleItems}`,
317 | ) as HTMLElement;
318 | if (visibleContainer && visibleContainer.children.length > 0) {
319 | let actualTotalWidth = 0;
320 | for (let i = 0; i < Math.min(visibleContainer.children.length, visibleCount); i++) {
321 | const actualWidth = (visibleContainer.children[i] as HTMLElement).offsetWidth;
322 | actualTotalWidth += actualWidth;
323 | }
324 | }
325 |
326 | // Ensure we move at least one item to overflow if we're showing dropdown
327 | if (visibleCount >= childrenArray.length) {
328 | // If all items would fit with dropdown space, don't show dropdown
329 | visibleItems = childrenArray;
330 | overflowItems = [];
331 | console.log(`All items fit, no dropdown needed`);
332 | } else if (visibleCount === 0) {
333 | // If no items fit, show at least one visible and rest in overflow
334 | visibleItems = childrenArray.slice(0, 1);
335 | overflowItems = childrenArray.slice(1);
336 | console.log(`No items fit, forcing one visible: visible=1, overflow=${overflowItems.length}`);
337 | } else {
338 | visibleItems = childrenArray.slice(0, visibleCount);
339 | overflowItems = childrenArray.slice(visibleCount);
340 | console.log(`Split items: visible=${visibleItems.length}, overflow=${overflowItems.length}`);
341 | }
342 | }
343 |
344 | console.log(`Final layout: visible=${visibleItems.length}, overflow=${overflowItems.length}`);
345 |
346 | // Only update if there's an actual change
347 | if (
348 | visibleItems.length !== layout.visibleItems.length ||
349 | overflowItems.length !== layout.overflowItems.length
350 | ) {
351 | setLayout({
352 | visibleItems,
353 | overflowItems,
354 | });
355 | }
356 |
357 | // Allow future calculations
358 | setTimeout(() => {
359 | isCalculatingRef.current = false;
360 | }, 50);
361 | };
362 |
363 | // Phase 1: Measure items when children actually change
364 | useEffect(() => {
365 |
366 | // Ignore children changes that happen immediately after layout completion
367 | if (layoutCompletedRef.current) {
368 | setTimeout(() => {
369 | layoutCompletedRef.current = false;
370 | }, 100);
371 | return;
372 | }
373 |
374 | if (childrenCount !== lastChildrenCount.current) {
375 | lastChildrenCount.current = childrenCount;
376 | setIsInMeasurementPhase(true);
377 | setMeasuredWidths([]);
378 | }
379 | }, [childrenCount]);
380 |
381 | // Phase 1: Trigger measurement after render
382 | useEffect(() => {
383 | if (isInMeasurementPhase) {
384 | const timer = setTimeout(() => {
385 | measureItems();
386 | }, 10);
387 | return () => clearTimeout(timer);
388 | }
389 | }, [isInMeasurementPhase, childrenCount]);
390 |
391 | // Phase 2: Calculate layout when measurements are ready
392 | useEffect(() => {
393 | if (!isInMeasurementPhase && measuredWidths.length > 0) {
394 | calculateOverflowLayout();
395 | // Mark that layout just completed to ignore immediate children changes
396 | layoutCompletedRef.current = true;
397 | }
398 | }, [isInMeasurementPhase, measuredWidths]);
399 |
400 | // Monitor container size changes - only in phase 2 to avoid infinite loops
401 | useResizeObserver(containerRef, () => {
402 | if (!isInMeasurementPhase && containerRef.current) {
403 | const containerRect = containerRef.current.getBoundingClientRect();
404 | const currentSize = orientation === "horizontal" ? containerRect.width : containerRect.height;
405 |
406 | if (currentSize !== lastContainerSize.current) {
407 | calculateOverflowLayout();
408 | }
409 | }
410 | });
411 |
412 | return (
413 | <div
414 | ref={(el) => {
415 | containerRef.current = el;
416 | if (typeof ref === "function") {
417 | ref(el);
418 | } else if (ref) {
419 | ref.current = el;
420 | }
421 | }}
422 | className={classnames(
423 | styles.responsiveBar,
424 | orientation === "vertical" ? styles.vertical : styles.horizontal,
425 | className
426 | )}
427 | style={{
428 | ...style,
429 | gap: `${gap}px`, // Gap between visibleItems and overflowDropdown
430 | flexDirection: orientation === "horizontal" ? "row" : "column",
431 | height: orientation === "vertical" ? "100%" : undefined,
432 | width: orientation === "horizontal" ? "100%" : undefined,
433 | }}
434 | onClick={onClick}
435 | {...rest}
436 | >
437 | {isInMeasurementPhase ? (
438 | // Phase 1: Render all items invisibly for measurement - identical structure to layout phase
439 | <>
440 | <div
441 | className={classnames(
442 | styles.visibleItems,
443 | orientation === "vertical" ? styles.vertical : styles.horizontal
444 | )}
445 | style={{
446 | gap: `${gap}px`, // Gap between items same as layout phase
447 | visibility: "hidden",
448 | opacity: 0,
449 | pointerEvents: "none",
450 | flexDirection: orientation === "horizontal" ? "row" : "column",
451 | }}
452 | >
453 | {childrenArray.map((child, index) => (
454 | <div key={`item-${index}`}>{child}</div>
455 | ))}
456 | </div>
457 |
458 | {/* Measurement dropdown - rendered in same location as visible dropdown */}
459 | <div
460 | ref={measurementDropdownRef}
461 | style={{
462 | visibility: "hidden",
463 | opacity: 0,
464 | pointerEvents: "none",
465 | }}
466 | >
467 | <ResponsiveBarDropdown
468 | overflowIcon={overflowIcon}
469 | className={styles.overflowDropdown}
470 | >
471 | {childrenArray.length > 0 && <MenuItem>{childrenArray[0]}</MenuItem>}
472 | </ResponsiveBarDropdown>
473 | </div>
474 | </>
475 | ) : (
476 | // Phase 2: Render final layout
477 | <>
478 | <div
479 | className={classnames(
480 | styles.visibleItems,
481 | orientation === "vertical" ? styles.vertical : styles.horizontal
482 | )}
483 | style={{
484 | gap: `${gap}px`, // Gap between visible items
485 | flexDirection: orientation === "horizontal" ? "row" : "column",
486 | }}
487 | >
488 | {childrenArray.map((child, index) => {
489 | const isVisible =
490 | layout.visibleItems.length > 0 ? index < layout.visibleItems.length : true;
491 | return (
492 | <div key={`item-${index}`} style={{ display: isVisible ? "block" : "none" }}>
493 | {child}
494 | </div>
495 | );
496 | })}
497 | </div>
498 |
499 | {/* Overflow dropdown */}
500 | {layout.overflowItems.length > 0 && (
501 | <ResponsiveBarDropdown
502 | overflowIcon={overflowIcon}
503 | className={styles.overflowDropdown}
504 | >
505 | {layout.overflowItems.map((item, index) => (
506 | <MenuItem key={index}>{item}</MenuItem>
507 | ))}
508 | </ResponsiveBarDropdown>
509 | )}
510 | </>
511 | )}
512 | </div>
513 | );
514 | });
515 |
```
--------------------------------------------------------------------------------
/xmlui/src/components-core/rendering/StateContainer.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import type {
2 | MutableRefObject,
3 | ReactNode,
4 | RefObject} from "react";
5 | import {
6 | forwardRef,
7 | memo,
8 | useCallback,
9 | useMemo,
10 | useReducer,
11 | useRef,
12 | useState,
13 | } from "react";
14 | import produce from "immer";
15 | import { cloneDeep, isEmpty, isPlainObject, merge, pick } from "lodash-es";
16 | import memoizeOne from "memoize-one";
17 | import { useLocation, useParams, useSearchParams } from "@remix-run/react";
18 |
19 | import type { ParentRenderContext } from "../../abstractions/ComponentDefs";
20 | import type { ContainerState } from "../../abstractions/ContainerDefs";
21 | import type { LayoutContext } from "../../abstractions/RendererDefs";
22 | import type { ContainerDispatcher, MemoedVars } from "../abstractions/ComponentRenderer";
23 | import { ContainerActionKind } from "./containers";
24 | import type {
25 | CodeDeclaration,
26 | ModuleErrors} from "../script-runner/ScriptingSourceTree";
27 | import {
28 | T_ARROW_EXPRESSION,
29 | } from "../script-runner/ScriptingSourceTree";
30 | import { EMPTY_OBJECT } from "../constants";
31 | import { collectFnVarDeps } from "../rendering/collectFnVarDeps";
32 | import { createContainerReducer } from "../rendering/reducer";
33 | import { useDebugView } from "../DebugViewProvider";
34 | import { ErrorBoundary } from "../rendering/ErrorBoundary";
35 | import { collectVariableDependencies } from "../script-runner/visitors";
36 | import { useReferenceTrackedApi, useShallowCompareMemoize } from "../utils/hooks";
37 | import { Container } from "./Container";
38 | import { PARSED_MARK_PROP } from "../../parsers/scripting/code-behind-collect";
39 | import { useAppContext } from "../AppContext";
40 | import { parseParameterString } from "../script-runner/ParameterParser";
41 | import { evalBinding } from "../script-runner/eval-tree-sync";
42 | import { extractParam } from "../utils/extractParam";
43 | import { pickFromObject, shallowCompare } from "../utils/misc";
44 | import type {
45 | ComponentApi,
46 | ContainerWrapperDef,
47 | RegisterComponentApiFnInner,
48 | StatePartChangedFn,
49 | } from "./ContainerWrapper";
50 | import { useLinkInfoContext } from "../../components/App/LinkInfoContext";
51 |
52 | // --- Properties of the MemoizedErrorProneContainer component
53 | type Props = {
54 | node: ContainerWrapperDef;
55 | resolvedKey?: string;
56 | parentState: ContainerState;
57 | parentStatePartChanged: StatePartChangedFn;
58 | parentRegisterComponentApi: RegisterComponentApiFnInner;
59 | parentDispatch: ContainerDispatcher;
60 | parentRenderContext?: ParentRenderContext;
61 | layoutContextRef: MutableRefObject<LayoutContext | undefined>;
62 | uidInfoRef?: RefObject<Record<string, any>>;
63 | isImplicit?: boolean;
64 | children?: ReactNode;
65 | };
66 |
67 | // A React component that wraps a view container into an error boundary
68 | // (it's a named function inside the memo, this way it will be visible with that name in the react devtools)
69 | export const StateContainer = memo(
70 | forwardRef(function StateContainer(
71 | {
72 | node,
73 | resolvedKey,
74 | parentState,
75 | parentStatePartChanged,
76 | parentRegisterComponentApi,
77 | parentDispatch,
78 | parentRenderContext,
79 | layoutContextRef,
80 | uidInfoRef,
81 | isImplicit,
82 | children,
83 | ...rest
84 | }: Props,
85 | ref,
86 | ) {
87 | const [version, setVersion] = useState(0);
88 | const routingParams = useRoutingParams();
89 | const memoedVars = useRef<MemoedVars>(new Map());
90 |
91 | const stateFromOutside = useShallowCompareMemoize(
92 | useMemo(() => {
93 | return extractScopedState(parentState, node.uses);
94 | }, [node.uses, parentState]),
95 | );
96 |
97 | // --- All state manipulation happens through the container reducer, which is created here.
98 | // --- This reducer allow collecting state changes for debugging purposes. The `debugView`
99 | // --- contains the debug configuration; it may enable (or disable) logging.
100 | const debugView = useDebugView();
101 | const containerReducer = createContainerReducer(debugView);
102 | const [componentState, dispatch] = useReducer(containerReducer, EMPTY_OBJECT);
103 |
104 | // --- The exposed APIs of components are also the part of the state.
105 | const [componentApis, setComponentApis] = useState<Record<symbol, ComponentApi>>(EMPTY_OBJECT);
106 |
107 | const componentStateWithApis = useShallowCompareMemoize(
108 | useMemo(() => {
109 | const ret = { ...componentState };
110 |
111 | // Get set of registered API keys - only these should have reducer state exposed as string keys
112 | const registeredApiKeys = new Set(
113 | Object.getOwnPropertySymbols(componentApis)
114 | .map(s => s.description)
115 | .filter((d): d is string => d !== undefined)
116 | );
117 |
118 | for (const stateKey of Object.getOwnPropertySymbols(componentState)) {
119 | const value = componentState[stateKey];
120 | if (stateKey.description) {
121 | // Only copy reducer state to string keys for APIs registered in THIS container
122 | // This prevents child containers from inheriting reducer state for parent APIs
123 | if (registeredApiKeys.has(stateKey.description)) {
124 | ret[stateKey.description] = value;
125 | }
126 | }
127 | }
128 | if (Reflect.ownKeys(componentApis).length === 0) {
129 | //skip containers with no registered apis
130 | return ret;
131 | }
132 | for (const componentApiKey of Object.getOwnPropertySymbols(componentApis)) {
133 | const value = componentApis[componentApiKey];
134 | if (componentApiKey.description) {
135 | const key = componentApiKey.description;
136 | ret[key] = { ...(ret[key] || {}), ...value };
137 | }
138 | ret[componentApiKey] = { ...ret[componentApiKey], ...value };
139 | }
140 | return ret;
141 | }, [componentState, componentApis]),
142 | );
143 |
144 | const localVarsStateContext = useCombinedState(
145 | stateFromOutside,
146 | componentStateWithApis,
147 | node.contextVars,
148 | );
149 | const parsedScriptPart = node.scriptCollected;
150 | if (parsedScriptPart?.moduleErrors && !isEmpty(parsedScriptPart.moduleErrors)) {
151 | throw new CodeBehindParseError(parsedScriptPart.moduleErrors);
152 | }
153 |
154 | if (node.scriptError && !isEmpty(node.scriptError)) {
155 | throw new CodeBehindParseError(node.scriptError);
156 | }
157 | const referenceTrackedApi = useReferenceTrackedApi(componentState);
158 |
159 | const varDefinitions = useShallowCompareMemoize({
160 | ...parsedScriptPart?.functions,
161 | ...node.functions,
162 | ...parsedScriptPart?.vars,
163 | ...node.vars,
164 | });
165 |
166 | //first: collection function (arrowExpressions) dependencies
167 | // -> do it until there's no function dep, only var deps
168 | const functionDeps = useMemo(() => {
169 | const fnDeps: Record<string, Array<string>> = {};
170 | Object.entries(varDefinitions).forEach(([key, value]) => {
171 | if (isParsedValue(value) && value.tree.type === T_ARROW_EXPRESSION) {
172 | fnDeps[key] = collectVariableDependencies(value.tree, referenceTrackedApi);
173 | }
174 | });
175 | return collectFnVarDeps(fnDeps);
176 | }, [referenceTrackedApi, varDefinitions]);
177 |
178 | // then resolve vars and replace function deps with the collected deps for that function
179 | //first resolve round (we do 2, to make sure that the order of the definitions doesn't cause problems)
180 | // e.g. 'testFn' uses $props, but $props is not resolved yet
181 | const preResolvedLocalVars = useVars(
182 | varDefinitions,
183 | functionDeps,
184 | localVarsStateContext,
185 | useRef<MemoedVars>(new Map()),
186 | );
187 | const localVarsStateContextWithPreResolvedLocalVars = useShallowCompareMemoize({
188 | ...preResolvedLocalVars,
189 | ...localVarsStateContext,
190 | });
191 |
192 | const resolvedLocalVars = useVars(
193 | varDefinitions,
194 | functionDeps,
195 | localVarsStateContextWithPreResolvedLocalVars,
196 | memoedVars,
197 | );
198 |
199 | const mergedWithVars = useMergedState(resolvedLocalVars, componentStateWithApis);
200 | const combinedState = useCombinedState(
201 | stateFromOutside, // Parent state (lower priority) - allows local vars to shadow
202 | node.contextVars, // Context vars like $item
203 | mergedWithVars, // Local vars and component state (higher priority) - enables shadowing
204 | routingParams,
205 | );
206 |
207 | const registerComponentApi: RegisterComponentApiFnInner = useCallback((uid, api) => {
208 | setComponentApis(
209 | produce((draft) => {
210 | if (!draft[uid]) {
211 | draft[uid] = {};
212 | }
213 | Object.entries(api).forEach(([key, value]) => {
214 | if (draft[uid][key] !== value) {
215 | draft[uid][key] = value;
216 | }
217 | });
218 | }),
219 | );
220 | }, []);
221 |
222 | const componentStateRef = useRef(componentStateWithApis);
223 |
224 | const statePartChanged: StatePartChangedFn = useCallback(
225 | (pathArray, newValue, target, action) => {
226 | const key = pathArray[0];
227 | if (key in componentStateRef.current || key in resolvedLocalVars) {
228 | // --- Sign that a state field (or a part of it) has changed
229 | dispatch({
230 | type: ContainerActionKind.STATE_PART_CHANGED,
231 | payload: {
232 | path: pathArray,
233 | value: newValue,
234 | target,
235 | actionType: action,
236 | localVars: resolvedLocalVars,
237 | },
238 | });
239 | } else {
240 | if (!node.uses || node.uses.includes(key)) {
241 | parentStatePartChanged(pathArray, newValue, target, action);
242 | }
243 | }
244 | },
245 | [resolvedLocalVars, node.uses, parentStatePartChanged],
246 | );
247 |
248 | return (
249 | <ErrorBoundary node={node} location={"container"}>
250 | <Container
251 | resolvedKey={resolvedKey}
252 | node={node}
253 | componentState={combinedState}
254 | dispatch={dispatch}
255 | parentDispatch={parentDispatch}
256 | setVersion={setVersion}
257 | version={version}
258 | statePartChanged={statePartChanged}
259 | registerComponentApi={registerComponentApi}
260 | parentRegisterComponentApi={parentRegisterComponentApi}
261 | layoutContextRef={layoutContextRef}
262 | parentRenderContext={parentRenderContext}
263 | memoedVarsRef={memoedVars}
264 | isImplicit={isImplicit}
265 | ref={ref}
266 | uidInfoRef={uidInfoRef}
267 | {...rest}
268 | >
269 | {children}
270 | </Container>
271 | </ErrorBoundary>
272 | );
273 | }),
274 | );
275 |
276 | const useRoutingParams = () => {
277 | const [queryParams] = useSearchParams();
278 | const routeParams = useParams();
279 | const location = useLocation();
280 | const linkInfoContext = useLinkInfoContext();
281 | const linkInfo = useMemo(() => {
282 | return linkInfoContext?.linkMap?.get(location.pathname) || EMPTY_OBJECT;
283 | }, [linkInfoContext?.linkMap, location.pathname]);
284 |
285 | const queryParamsMap = useMemo(() => {
286 | const result: Record<string, any> = {};
287 | for (const [key, value] of Array.from(queryParams.entries())) {
288 | result[key] = value;
289 | }
290 | return result;
291 | }, [queryParams]);
292 |
293 | return useMemo(() => {
294 | return {
295 | $pathname: location.pathname,
296 | $routeParams: routeParams,
297 | $queryParams: queryParamsMap,
298 | $linkInfo: linkInfo,
299 | };
300 | }, [linkInfo, location.pathname, queryParamsMap, routeParams]);
301 | };
302 |
303 | // Extracts the `state` property values defined in a component definition's `uses` property. It uses the specified
304 | // `appContext` when resolving the state values.
305 | function extractScopedState(
306 | parentState: ContainerState,
307 | uses?: string[],
308 | ): ContainerState | undefined {
309 | if (!uses) {
310 | return parentState;
311 | }
312 | if (uses.length === 0) {
313 | return EMPTY_OBJECT;
314 | }
315 | return pick(parentState, uses);
316 | }
317 |
318 | // This hook combines state properties in a list of states so that a particular state property in a higher
319 | // argument index overrides the same-named state property in a lower argument index.
320 | function useCombinedState(...states: (ContainerState | undefined)[]) {
321 | const combined: ContainerState = useMemo(() => {
322 | let ret: ContainerState = {};
323 | states.forEach((state = EMPTY_OBJECT) => {
324 | if (state !== EMPTY_OBJECT) {
325 | ret = { ...ret, ...state };
326 | }
327 | });
328 | return ret;
329 | }, [states]);
330 | return useShallowCompareMemoize(combined);
331 | }
332 |
333 | // This hook combines state properties in a list of states so that a particular state property in a higher
334 | // argument index merges into the same-named state property in a lower argument index.
335 |
336 | // This hook combines state properties in a list of states so that a particular state property in a higher
337 | // argument index merges into the same-named state property in a lower argument index.
338 | function useMergedState(localVars: ContainerState, componentState: ContainerState) {
339 | const merged = useMemo(() => {
340 | const ret = { ...localVars };
341 | Reflect.ownKeys(componentState).forEach((key) => {
342 | const value = componentState[key];
343 | if (ret[key] === undefined) {
344 | ret[key] = value;
345 | } else {
346 | if (
347 | (isPlainObject(ret[key]) && isPlainObject(value)) ||
348 | (Array.isArray(ret[key]) && Array.isArray(value))
349 | ) {
350 | ret[key] = merge(cloneDeep(ret[key]), value);
351 | } else {
352 | ret[key] = value;
353 | }
354 | }
355 | });
356 | return ret;
357 | }, [localVars, componentState]);
358 | return useShallowCompareMemoize(merged);
359 | }
360 |
361 | // This hook resolves variables to their current value (using binding expression evaluation)
362 | function useVars(
363 | vars: ContainerState = EMPTY_OBJECT,
364 | fnDeps: Record<string, Array<string>> = EMPTY_OBJECT,
365 | componentState: ContainerState,
366 | memoedVars: MutableRefObject<MemoedVars>,
367 | ): ContainerState {
368 | const appContext = useAppContext();
369 | const referenceTrackedApi = useReferenceTrackedApi(componentState);
370 |
371 | const resolvedVars = useMemo(() => {
372 | const ret: any = {};
373 |
374 | Object.entries(vars).forEach(([key, value]) => {
375 | if (key === "$props") {
376 | // --- We already resolved props in a compound component
377 | ret[key] = value;
378 | } else {
379 | if (!isParsedValue(value) && typeof value !== "string") {
380 | ret[key] = value;
381 | } else {
382 | // --- Resolve each variable's value, without going into the details of arrays and objects
383 | if (!memoedVars.current.has(value)) {
384 | memoedVars.current.set(value, {
385 | getDependencies: memoizeOne((value, referenceTrackedApi) => {
386 | if (isParsedValue(value)) {
387 | return collectVariableDependencies(value.tree, referenceTrackedApi);
388 | }
389 | const params = parseParameterString(value);
390 | let ret = new Set<string>();
391 | params.forEach((param) => {
392 | if (param.type === "expression") {
393 | ret = new Set([
394 | ...ret,
395 | ...collectVariableDependencies(param.value, referenceTrackedApi),
396 | ]);
397 | }
398 | });
399 | return Array.from(ret);
400 | }),
401 | obtainValue: memoizeOne(
402 | (value, state, appContext, strict, deps, appContextDeps) => {
403 | try {
404 | return isParsedValue(value)
405 | ? evalBinding(value.tree, {
406 | localContext: state,
407 | appContext,
408 | options: {
409 | defaultToOptionalMemberAccess: true,
410 | },
411 | })
412 | : extractParam(state, value, appContext, strict);
413 | } catch (e) {
414 | console.log(state);
415 | throw new ParseVarError(value, e);
416 | }
417 | },
418 | (
419 | [
420 | _newExpression,
421 | _newState,
422 | _newAppContext,
423 | _newStrict,
424 | newDeps,
425 | newAppContextDeps,
426 | ],
427 | [
428 | _lastExpression,
429 | _lastState,
430 | _lastAppContext,
431 | _lastStrict,
432 | lastDeps,
433 | lastAppContextDeps,
434 | ],
435 | ) => {
436 | return (
437 | shallowCompare(newDeps, lastDeps) &&
438 | shallowCompare(newAppContextDeps, lastAppContextDeps)
439 | );
440 | },
441 | ),
442 | });
443 | }
444 | const stateContext: ContainerState = { ...ret, ...componentState };
445 |
446 | let dependencies: Array<string> = [];
447 | if (fnDeps[key]) {
448 | dependencies = fnDeps[key];
449 | } else {
450 | memoedVars.current
451 | .get(value)!
452 | .getDependencies(value, referenceTrackedApi)
453 | .forEach((dep) => {
454 | if (fnDeps[dep]) {
455 | dependencies.push(...fnDeps[dep]);
456 | } else {
457 | dependencies.push(dep);
458 | }
459 | });
460 | dependencies = [...new Set(dependencies)];
461 | }
462 | const stateDepValues = pickFromObject(stateContext, dependencies);
463 | const appContextDepValues = pickFromObject(appContext, dependencies);
464 |
465 | ret[key] = memoedVars.current
466 | .get(value)!
467 | .obtainValue(
468 | value,
469 | stateContext,
470 | appContext,
471 | true,
472 | stateDepValues,
473 | appContextDepValues,
474 | );
475 | }
476 | }
477 | });
478 | return ret;
479 | }, [appContext, componentState, fnDeps, memoedVars, referenceTrackedApi, vars]);
480 |
481 | return useShallowCompareMemoize(resolvedVars);
482 | }
483 |
484 | class CodeBehindParseError extends Error {
485 | constructor(errors: ModuleErrors) {
486 | const mainErrors = errors["Main"] || [];
487 | const messages = mainErrors.map((errorMessage) => {
488 | let ret = `${errorMessage.code} : ${errorMessage.text}`;
489 | const posInfo = [];
490 | if (errorMessage.line !== undefined) {
491 | posInfo.push(`line:${errorMessage.line}`);
492 | }
493 | if (errorMessage.column !== undefined) {
494 | posInfo.push(`column:${errorMessage.column}`);
495 | }
496 | if (posInfo.length) {
497 | ret = `${ret} (${posInfo.join(", ")})`;
498 | }
499 | return ret;
500 | });
501 | super(messages.join("\n"));
502 | Object.setPrototypeOf(this, CodeBehindParseError.prototype);
503 | }
504 | }
505 |
506 | class ParseVarError extends Error {
507 | constructor(varName: string, originalError: any) {
508 | super(`Error on var: ${varName} - ${originalError?.message || "unknown"}`);
509 | }
510 | }
511 |
512 | //true if it's coming from a code behind or a script tag
513 | function isParsedValue(value: any): value is CodeDeclaration {
514 | return value && typeof value === "object" && value[PARSED_MARK_PROP];
515 | }
516 |
```
--------------------------------------------------------------------------------
/xmlui/src/components/Charts/PieChart/PieChart.spec.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { expect, test } from "../../../testing/fixtures";
2 |
3 | // Test data helpers - using proper XMLUI data format
4 | const sampleData = `[
5 | { name: 'Desktop', value: 400, fill: '#8884d8' },
6 | { name: 'Mobile', value: 300, fill: '#82ca9d' },
7 | { name: 'Tablet', value: 200, fill: '#ffc658' },
8 | { name: 'Other', value: 100, fill: '#ff7300' }
9 | ]`;
10 |
11 | const emptyData = `[]`;
12 |
13 | const singlePointData = `[
14 | { name: 'Desktop', value: 400 }
15 | ]`;
16 |
17 | const largeDataset = `[
18 | { name: 'Category A', value: 400 },
19 | { name: 'Category B', value: 300 },
20 | { name: 'Category C', value: 200 },
21 | { name: 'Category D', value: 100 },
22 | { name: 'Category E', value: 150 },
23 | { name: 'Category F', value: 250 },
24 | { name: 'Category G', value: 180 }
25 | ]`;
26 |
27 | // Chart selectors - PieChart specific
28 | const chartRoot = ".recharts-responsive-container";
29 | const chartSvg = ".recharts-surface";
30 | const pieSelector = ".recharts-pie";
31 | const pieSectorSelector = ".recharts-pie-sector";
32 | const legendSelector = ".recharts-legend-wrapper";
33 | const tooltipSelector = ".recharts-tooltip-wrapper";
34 | const labelListSelector = ".recharts-label-list";
35 |
36 | // --- Smoke Tests
37 |
38 | test.describe("smoke tests", { tag: "@smoke" }, () => {
39 | test("component renders with basic props", async ({ initTestBed, page }) => {
40 | await initTestBed(`
41 | <PieChart
42 | nameKey="name"
43 | dataKey="value"
44 | data="{${sampleData}}"
45 | width="400px"
46 | height="400px"
47 | />
48 | `);
49 |
50 | await page.waitForSelector(chartRoot, { timeout: 10000 });
51 | await expect(page.locator(chartRoot)).toBeVisible();
52 | });
53 |
54 | test("renders pie sectors for data points", async ({ initTestBed, page }) => {
55 | await initTestBed(`
56 | <PieChart
57 | nameKey="name"
58 | dataKey="value"
59 | data="{${sampleData}}"
60 | width="400px"
61 | height="400px"
62 | />
63 | `);
64 |
65 | await page.waitForSelector(chartRoot, { timeout: 10000 });
66 | // Should have 4 pie sectors for 4 data points
67 | await expect(page.locator(pieSectorSelector)).toHaveCount(4);
68 | });
69 |
70 | test("renders pie chart with correct structure", async ({ initTestBed, page }) => {
71 | await initTestBed(`
72 | <PieChart
73 | nameKey="name"
74 | dataKey="value"
75 | data="{${sampleData}}"
76 | width="400px"
77 | height="400px"
78 | />
79 | `);
80 |
81 | await page.waitForSelector(chartRoot, { timeout: 10000 });
82 | await expect(page.locator(pieSelector)).toBeVisible();
83 | await expect(page.locator(chartSvg)).toBeVisible();
84 | });
85 | });
86 |
87 | // --- Data Handling Tests
88 |
89 | test.describe("data handling", () => {
90 | test("renders with empty data array", async ({ initTestBed, page }) => {
91 | await initTestBed(`
92 | <PieChart
93 | nameKey="name"
94 | dataKey="value"
95 | data="{${emptyData}}"
96 | width="400px"
97 | height="400px"
98 | />
99 | `);
100 |
101 | await page.waitForSelector(chartRoot, { timeout: 10000 });
102 | await expect(page.locator(pieSectorSelector)).toHaveCount(0);
103 | });
104 |
105 | test("renders with single data point", async ({ initTestBed, page }) => {
106 | await initTestBed(`
107 | <PieChart
108 | nameKey="name"
109 | dataKey="value"
110 | data="{${singlePointData}}"
111 | width="400px"
112 | height="400px"
113 | />
114 | `);
115 |
116 | await page.waitForSelector(chartRoot, { timeout: 10000 });
117 | await expect(page.locator(pieSectorSelector)).toHaveCount(1);
118 | });
119 |
120 | test("handles non-array data gracefully", async ({ initTestBed, page }) => {
121 | await initTestBed(`
122 | <PieChart
123 | nameKey="name"
124 | dataKey="value"
125 | data="{null}"
126 | width="400px"
127 | height="400px"
128 | />
129 | `);
130 |
131 | await page.waitForSelector(chartRoot, { timeout: 10000 });
132 | await expect(page.locator(pieSectorSelector)).toHaveCount(0);
133 | });
134 |
135 | test("handles large datasets", async ({ initTestBed, page }) => {
136 | await initTestBed(`
137 | <PieChart
138 | nameKey="name"
139 | dataKey="value"
140 | data="{${largeDataset}}"
141 | width="400px"
142 | height="400px"
143 | />
144 | `);
145 |
146 | await page.waitForSelector(chartRoot, { timeout: 10000 });
147 | await expect(page.locator(pieSectorSelector)).toHaveCount(7);
148 | });
149 | });
150 |
151 | // --- Legend Tests
152 |
153 | test.describe("legend", () => {
154 | test("legend is hidden by default", async ({ initTestBed, page }) => {
155 | await initTestBed(`
156 | <PieChart
157 | nameKey="name"
158 | dataKey="value"
159 | data="{${sampleData}}"
160 | width="400px"
161 | height="400px"
162 | />
163 | `);
164 |
165 | await page.waitForSelector(chartRoot, { timeout: 10000 });
166 | await expect(page.locator(legendSelector)).not.toBeVisible();
167 | });
168 |
169 | test("legend is shown when showLegend is true", async ({ initTestBed, page }) => {
170 | await initTestBed(`
171 | <PieChart
172 | nameKey="name"
173 | dataKey="value"
174 | data="{${sampleData}}"
175 | showLegend
176 | width="400px"
177 | height="400px"
178 | />
179 | `);
180 |
181 | await page.waitForSelector(chartRoot, { timeout: 10000 });
182 | await expect(page.locator(legendSelector)).toBeVisible();
183 | });
184 | });
185 |
186 | // --- Tooltip Tests
187 |
188 | test.describe("tooltip", () => {
189 | test("tooltip appears on hover by default", async ({ initTestBed, page }) => {
190 | await initTestBed(`
191 | <PieChart
192 | nameKey="name"
193 | dataKey="value"
194 | data="{${sampleData}}"
195 | width="400px"
196 | height="400px"
197 | />
198 | `);
199 |
200 | await page.waitForSelector(chartRoot, { timeout: 10000 });
201 | const pieSector = page.locator(pieSectorSelector).first();
202 | await pieSector.hover();
203 |
204 | // Wait for tooltip to appear
205 | await page.waitForTimeout(500);
206 | await expect(page.locator(tooltipSelector)).toBeVisible();
207 | });
208 |
209 | test("tooltip shows correct data on hover", async ({ initTestBed, page }) => {
210 | await initTestBed(`
211 | <PieChart
212 | nameKey="name"
213 | dataKey="value"
214 | data="{${sampleData}}"
215 | width="400px"
216 | height="400px"
217 | />
218 | `);
219 |
220 | await page.waitForSelector(chartRoot, { timeout: 10000 });
221 | const pieSector = page.locator(pieSectorSelector).first();
222 | await pieSector.hover();
223 |
224 | await page.waitForTimeout(500);
225 | const tooltip = page.locator(tooltipSelector);
226 | await expect(tooltip).toBeVisible();
227 | // Tooltip should contain data from the first sector
228 | await expect(tooltip).toContainText("Desktop");
229 | await expect(tooltip).toContainText("400");
230 | });
231 | });
232 |
233 | // --- Label Tests
234 |
235 | test.describe("labels", () => {
236 | test("labels are shown by default (showLabel=true)", async ({ initTestBed, page }) => {
237 | await initTestBed(`
238 | <PieChart
239 | nameKey="name"
240 | dataKey="value"
241 | data="{${sampleData}}"
242 | width="400px"
243 | height="400px"
244 | />
245 | `);
246 |
247 | await page.waitForSelector(chartRoot, { timeout: 10000 });
248 | // Labels should be rendered as text elements
249 | const labels = page.locator('text').filter({ hasText: /Desktop|Mobile|Tablet|Other/ });
250 | await expect(labels).toHaveCount(4);
251 | });
252 |
253 | test("labels are hidden when showLabel is false", async ({ initTestBed, page }) => {
254 | await initTestBed(`
255 | <PieChart
256 | nameKey="name"
257 | dataKey="value"
258 | data="{${sampleData}}"
259 | showLabel="{false}"
260 | width="400px"
261 | height="400px"
262 | />
263 | `);
264 |
265 | await page.waitForSelector(chartRoot, { timeout: 10000 });
266 | // Should have pie sectors but no labels
267 | await expect(page.locator(pieSectorSelector)).toHaveCount(4);
268 | const labels = page.locator('text').filter({ hasText: /Desktop|Mobile|Tablet|Other/ });
269 | await expect(labels).toHaveCount(0);
270 | });
271 |
272 | test("label list is hidden by default", async ({ initTestBed, page }) => {
273 | await initTestBed(`
274 | <PieChart
275 | nameKey="name"
276 | dataKey="value"
277 | data="{${sampleData}}"
278 | width="400px"
279 | height="400px"
280 | />
281 | `);
282 |
283 | await page.waitForSelector(chartRoot, { timeout: 10000 });
284 | // When showLabelList is false (default), there should be no LabelList elements
285 | // We check for the absence of the recharts-label-list class
286 | await expect(page.locator(labelListSelector)).toHaveCount(0);
287 | });
288 |
289 | test("label list is shown when showLabelList is true", async ({ initTestBed, page }) => {
290 | await initTestBed(`
291 | <PieChart
292 | nameKey="name"
293 | dataKey="value"
294 | data="{${sampleData}}"
295 | showLabel="{false}"
296 | showLabelList
297 | width="400px"
298 | height="400px"
299 | />
300 | `);
301 |
302 | await page.waitForSelector(chartRoot, { timeout: 10000 });
303 | // LabelList creates text elements with the label content
304 | const labelTexts = page.locator('text').filter({ hasText: /Desktop|Mobile|Tablet|Other/ });
305 | await expect(labelTexts).toHaveCount(4);
306 | });
307 |
308 | test("label list position can be configured", async ({ initTestBed, page }) => {
309 | await initTestBed(`
310 | <PieChart
311 | nameKey="name"
312 | dataKey="value"
313 | data="{${sampleData}}"
314 | showLabel="{false}"
315 | showLabelList
316 | labelListPosition="outside"
317 | width="400px"
318 | height="400px"
319 | />
320 | `);
321 |
322 | await page.waitForSelector(chartRoot, { timeout: 10000 });
323 | // LabelList creates text elements with the label content
324 | const labelTexts = page.locator('text').filter({ hasText: /Desktop|Mobile|Tablet|Other/ });
325 | await expect(labelTexts).toHaveCount(4);
326 | });
327 | });
328 |
329 | // --- Radius Tests (PieChart specific)
330 |
331 | test.describe("radius configuration", () => {
332 | test("renders with default outer radius", async ({ initTestBed, page }) => {
333 | await initTestBed(`
334 | <PieChart
335 | nameKey="name"
336 | dataKey="value"
337 | data="{${sampleData}}"
338 | width="400px"
339 | height="400px"
340 | />
341 | `);
342 |
343 | await page.waitForSelector(chartRoot, { timeout: 10000 });
344 | await expect(page.locator(pieSectorSelector)).toHaveCount(4);
345 | });
346 |
347 | test("renders with custom outer radius", async ({ initTestBed, page }) => {
348 | await initTestBed(`
349 | <PieChart
350 | nameKey="name"
351 | dataKey="value"
352 | data="{${sampleData}}"
353 | outerRadius="{100}"
354 | width="400px"
355 | height="400px"
356 | />
357 | `);
358 |
359 | await page.waitForSelector(chartRoot, { timeout: 10000 });
360 | await expect(page.locator(pieSectorSelector)).toHaveCount(4);
361 | });
362 |
363 | test("renders as donut chart with inner radius", async ({ initTestBed, page }) => {
364 | await initTestBed(`
365 | <PieChart
366 | nameKey="name"
367 | dataKey="value"
368 | data="{${sampleData}}"
369 | innerRadius="{50}"
370 | width="400px"
371 | height="400px"
372 | />
373 | `);
374 |
375 | await page.waitForSelector(chartRoot, { timeout: 10000 });
376 | await expect(page.locator(pieSectorSelector)).toHaveCount(4);
377 | // With inner radius, it becomes a donut chart
378 | });
379 |
380 | test("renders with both inner and outer radius", async ({ initTestBed, page }) => {
381 | await initTestBed(`
382 | <PieChart
383 | nameKey="name"
384 | dataKey="value"
385 | data="{${sampleData}}"
386 | innerRadius="{40}"
387 | outerRadius="{80}"
388 | width="400px"
389 | height="400px"
390 | />
391 | `);
392 |
393 | await page.waitForSelector(chartRoot, { timeout: 10000 });
394 | await expect(page.locator(pieSectorSelector)).toHaveCount(4);
395 | });
396 | });
397 |
398 | // --- Responsive Behavior Tests
399 |
400 | test.describe("responsive behavior", () => {
401 | test("renders in small containers", async ({ initTestBed, page }) => {
402 | await initTestBed(`
403 | <PieChart
404 | nameKey="name"
405 | dataKey="value"
406 | data="{${sampleData}}"
407 | width="200px"
408 | height="200px"
409 | />
410 | `);
411 |
412 | await page.waitForSelector(chartRoot, { timeout: 10000 });
413 | await expect(page.locator(chartRoot)).toBeVisible();
414 | await expect(page.locator(pieSectorSelector)).toHaveCount(4);
415 | });
416 |
417 | test("renders in very small containers", async ({ initTestBed, page }) => {
418 | await initTestBed(`
419 | <PieChart
420 | nameKey="name"
421 | dataKey="value"
422 | data="{${sampleData}}"
423 | width="100px"
424 | height="100px"
425 | />
426 | `);
427 |
428 | await page.waitForSelector(chartRoot, { timeout: 10000 });
429 | await expect(page.locator(chartRoot)).toBeVisible();
430 | await expect(page.locator(pieSectorSelector)).toHaveCount(4);
431 | });
432 |
433 | test("renders in large containers", async ({ initTestBed, page }) => {
434 | await initTestBed(`
435 | <PieChart
436 | nameKey="name"
437 | dataKey="value"
438 | data="{${sampleData}}"
439 | width="800px"
440 | height="600px"
441 | />
442 | `);
443 |
444 | await page.waitForSelector(chartRoot, { timeout: 10000 });
445 | await expect(page.locator(chartRoot)).toBeVisible();
446 | await expect(page.locator(pieSectorSelector)).toHaveCount(4);
447 | });
448 |
449 | test("handles rectangular containers", async ({ initTestBed, page }) => {
450 | await initTestBed(`
451 | <PieChart
452 | nameKey="name"
453 | dataKey="value"
454 | data="{${sampleData}}"
455 | width="600px"
456 | height="300px"
457 | />
458 | `);
459 |
460 | await page.waitForSelector(chartRoot, { timeout: 10000 });
461 | await expect(page.locator(chartRoot)).toBeVisible();
462 | await expect(page.locator(pieSectorSelector)).toHaveCount(4);
463 | });
464 | });
465 |
466 | // --- Interaction Tests
467 |
468 | test.describe("interaction", () => {
469 | test("pie sectors are interactive on hover", async ({ initTestBed, page }) => {
470 | await initTestBed(`
471 | <PieChart
472 | nameKey="name"
473 | dataKey="value"
474 | data="{${sampleData}}"
475 | width="400px"
476 | height="400px"
477 | />
478 | `);
479 |
480 | await page.waitForSelector(chartRoot, { timeout: 10000 });
481 | const pieSector = page.locator(pieSectorSelector).first();
482 |
483 | // Hover should trigger active shape rendering
484 | await pieSector.hover();
485 | await page.waitForTimeout(300);
486 |
487 | // The sector should still be visible (active shape effect)
488 | await expect(pieSector).toBeVisible();
489 | });
490 |
491 | test("multiple sectors can be hovered", async ({ initTestBed, page }) => {
492 | await initTestBed(`
493 | <PieChart
494 | nameKey="name"
495 | dataKey="value"
496 | data="{${sampleData}}"
497 | width="400px"
498 | height="400px"
499 | />
500 | `);
501 |
502 | await page.waitForSelector(chartRoot, { timeout: 10000 });
503 | const sectors = page.locator(pieSectorSelector);
504 |
505 | // Hover over different sectors
506 | await sectors.nth(0).hover();
507 | await page.waitForTimeout(200);
508 | await sectors.nth(1).hover();
509 | await page.waitForTimeout(200);
510 | await sectors.nth(2).hover();
511 | await page.waitForTimeout(200);
512 |
513 | // All sectors should still be present
514 | await expect(sectors).toHaveCount(4);
515 | });
516 | });
517 |
518 | // --- Children Support Tests
519 |
520 | test.describe("children support", () => {
521 | test("renders with child components", async ({ initTestBed, page }) => {
522 | await initTestBed(`
523 | <PieChart
524 | nameKey="name"
525 | dataKey="value"
526 | data="{${sampleData}}"
527 | width="400px"
528 | height="400px"
529 | >
530 | <Text>Chart Title</Text>
531 | </PieChart>
532 | `);
533 |
534 | await page.waitForSelector(chartRoot, { timeout: 10000 });
535 | await expect(page.locator(chartRoot)).toBeVisible();
536 | await expect(page.locator(pieSectorSelector)).toHaveCount(4);
537 | await expect(page.getByText("Chart Title")).toBeVisible();
538 | });
539 | });
540 |
541 | // --- Edge Cases
542 |
543 | test.describe("edge cases", () => {
544 | test("handles missing dataKey gracefully", async ({ initTestBed, page }) => {
545 | await initTestBed(`
546 | <PieChart
547 | nameKey="name"
548 | data="{${sampleData}}"
549 | width="400px"
550 | height="400px"
551 | />
552 | `);
553 |
554 | await page.waitForSelector(chartRoot, { timeout: 10000 });
555 | // Should still render the container but may not have sectors
556 | await expect(page.locator(chartRoot)).toBeVisible();
557 | });
558 |
559 | test("handles missing nameKey gracefully", async ({ initTestBed, page }) => {
560 | await initTestBed(`
561 | <PieChart
562 | dataKey="value"
563 | data="{${sampleData}}"
564 | width="400px"
565 | height="400px"
566 | />
567 | `);
568 |
569 | await page.waitForSelector(chartRoot, { timeout: 10000 });
570 | await expect(page.locator(chartRoot)).toBeVisible();
571 | });
572 |
573 | test("handles data with missing values", async ({ initTestBed, page }) => {
574 | const dataWithMissingValues = `[
575 | { name: 'Desktop', value: 400 },
576 | { name: 'Mobile', value: null },
577 | { name: 'Tablet', value: 200 },
578 | { name: 'Other' }
579 | ]`;
580 |
581 | await initTestBed(`
582 | <PieChart
583 | nameKey="name"
584 | dataKey="value"
585 | data="{${dataWithMissingValues}}"
586 | width="400px"
587 | height="400px"
588 | />
589 | `);
590 |
591 | await page.waitForSelector(chartRoot, { timeout: 10000 });
592 | // Should render sectors for valid data points
593 | await expect(page.locator(chartRoot)).toBeVisible();
594 | });
595 |
596 | test("handles data with zero values", async ({ initTestBed, page }) => {
597 | const dataWithZeros = `[
598 | { name: 'Desktop', value: 400 },
599 | { name: 'Mobile', value: 0 },
600 | { name: 'Tablet', value: 200 },
601 | { name: 'Other', value: 0 }
602 | ]`;
603 |
604 | await initTestBed(`
605 | <PieChart
606 | nameKey="name"
607 | dataKey="value"
608 | data="{${dataWithZeros}}"
609 | width="400px"
610 | height="400px"
611 | />
612 | `);
613 |
614 | await page.waitForSelector(chartRoot, { timeout: 10000 });
615 | await expect(page.locator(chartRoot)).toBeVisible();
616 | // Should handle zero values appropriately
617 | });
618 |
619 | test("handles negative values", async ({ initTestBed, page }) => {
620 | const dataWithNegatives = `[
621 | { name: 'Desktop', value: 400 },
622 | { name: 'Mobile', value: -100 },
623 | { name: 'Tablet', value: 200 },
624 | { name: 'Other', value: 100 }
625 | ]`;
626 |
627 | await initTestBed(`
628 | <PieChart
629 | nameKey="name"
630 | dataKey="value"
631 | data="{${dataWithNegatives}}"
632 | width="400px"
633 | height="400px"
634 | />
635 | `);
636 |
637 | await page.waitForSelector(chartRoot, { timeout: 10000 });
638 | await expect(page.locator(chartRoot)).toBeVisible();
639 | // Should handle negative values (though they may not render as sectors)
640 | });
641 |
642 | test("handles very large values", async ({ initTestBed, page }) => {
643 | const dataWithLargeValues = `[
644 | { name: 'Desktop', value: 1000000 },
645 | { name: 'Mobile', value: 2000000 },
646 | { name: 'Tablet', value: 500000 },
647 | { name: 'Other', value: 100000 }
648 | ]`;
649 |
650 | await initTestBed(`
651 | <PieChart
652 | nameKey="name"
653 | dataKey="value"
654 | data="{${dataWithLargeValues}}"
655 | width="400px"
656 | height="400px"
657 | />
658 | `);
659 |
660 | await page.waitForSelector(chartRoot, { timeout: 10000 });
661 | await expect(page.locator(chartRoot)).toBeVisible();
662 | await expect(page.locator(pieSectorSelector)).toHaveCount(4);
663 | });
664 | });
665 |
```