This is page 79 of 145. Use http://codebase.md/xmlui-org/xmlui/xmlui/tools/vscode/resources/xmlui-markup-syntax-highlighting.png?lines=false&page={x} to view the full context.
# Directory Structure
```
├── .changeset
│ └── config.json
├── .eslintrc.cjs
├── .github
│ ├── build-checklist.png
│ ├── ISSUE_TEMPLATE
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ └── workflows
│ ├── deploy-blog-optimized.yml
│ ├── deploy-blog-swa.yml
│ ├── deploy-blog.yml
│ ├── deploy-docs-optimized.yml
│ ├── deploy-docs-swa.yml
│ ├── deploy-docs.yml
│ ├── prepare-versions.yml
│ ├── release-packages.yml
│ ├── run-all-tests.yml
│ └── run-smoke-tests.yml
├── .gitignore
├── .prettierrc.js
├── .vscode
│ ├── launch.json
│ └── settings.json
├── blog
│ ├── .gitignore
│ ├── .gitkeep
│ ├── CHANGELOG.md
│ ├── extensions.ts
│ ├── index.html
│ ├── index.ts
│ ├── package.json
│ ├── public
│ │ ├── blog
│ │ │ ├── images
│ │ │ │ ├── an-advanced-codefence.gif
│ │ │ │ ├── an-advanced-codefence.mp4
│ │ │ │ ├── blog-page-component.png
│ │ │ │ ├── blog-scrabble.png
│ │ │ │ ├── codefence-runner.png
│ │ │ │ ├── integrated-blog-search.png
│ │ │ │ ├── lorem-ipsum.png
│ │ │ │ ├── playground-checkbox-source.png
│ │ │ │ ├── playground.png
│ │ │ │ ├── use-xmlui-mcp-to-find-a-howto.png
│ │ │ │ └── xmlui-demo-gallery.png
│ │ │ ├── introducing-xmlui.md
│ │ │ ├── lorem-ipsum.md
│ │ │ ├── newest-post.md
│ │ │ ├── older-post.md
│ │ │ ├── xmlui-playground.md
│ │ │ └── xmlui-powered-blog.md
│ │ ├── mockServiceWorker.js
│ │ ├── resources
│ │ │ ├── favicon.ico
│ │ │ ├── files
│ │ │ │ └── for-download
│ │ │ │ └── xmlui
│ │ │ │ └── xmlui-standalone.umd.js
│ │ │ ├── github.svg
│ │ │ ├── icons
│ │ │ │ ├── github.svg
│ │ │ │ └── rss.svg
│ │ │ ├── llms.txt
│ │ │ ├── logo-dark.svg
│ │ │ ├── logo.svg
│ │ │ ├── pg-popout.svg
│ │ │ ├── rss.svg
│ │ │ └── xmlui-logo.svg
│ │ ├── serve.json
│ │ ├── staticwebapp.config.json
│ │ └── web.config
│ ├── scripts
│ │ ├── download-latest-xmlui.js
│ │ ├── generate-rss.js
│ │ ├── get-releases.js
│ │ └── utils.js
│ ├── src
│ │ ├── components
│ │ │ ├── BlogOverview.xmlui
│ │ │ ├── BlogPage.xmlui
│ │ │ ├── LinkButton.xmlui
│ │ │ ├── PageNotFound.xmlui
│ │ │ └── Separator.xmlui
│ │ ├── config.ts
│ │ ├── Main.xmlui
│ │ └── themes
│ │ └── blog-theme.ts
│ └── tsconfig.json
├── CONTRIBUTING.md
├── docs
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── ComponentRefLinks.txt
│ ├── content
│ │ ├── _meta.json
│ │ ├── components
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── APICall.md
│ │ │ ├── App.md
│ │ │ ├── AppHeader.md
│ │ │ ├── AppState.md
│ │ │ ├── AutoComplete.md
│ │ │ ├── Avatar.md
│ │ │ ├── Backdrop.md
│ │ │ ├── Badge.md
│ │ │ ├── BarChart.md
│ │ │ ├── Bookmark.md
│ │ │ ├── Breakout.md
│ │ │ ├── Button.md
│ │ │ ├── Card.md
│ │ │ ├── Carousel.md
│ │ │ ├── ChangeListener.md
│ │ │ ├── Checkbox.md
│ │ │ ├── CHStack.md
│ │ │ ├── ColorPicker.md
│ │ │ ├── Column.md
│ │ │ ├── ContentSeparator.md
│ │ │ ├── CVStack.md
│ │ │ ├── DataSource.md
│ │ │ ├── DateInput.md
│ │ │ ├── DatePicker.md
│ │ │ ├── DonutChart.md
│ │ │ ├── DropdownMenu.md
│ │ │ ├── EmojiSelector.md
│ │ │ ├── ExpandableItem.md
│ │ │ ├── FileInput.md
│ │ │ ├── FileUploadDropZone.md
│ │ │ ├── FlowLayout.md
│ │ │ ├── Footer.md
│ │ │ ├── Form.md
│ │ │ ├── FormItem.md
│ │ │ ├── FormSection.md
│ │ │ ├── Fragment.md
│ │ │ ├── H1.md
│ │ │ ├── H2.md
│ │ │ ├── H3.md
│ │ │ ├── H4.md
│ │ │ ├── H5.md
│ │ │ ├── H6.md
│ │ │ ├── Heading.md
│ │ │ ├── HSplitter.md
│ │ │ ├── HStack.md
│ │ │ ├── Icon.md
│ │ │ ├── IFrame.md
│ │ │ ├── Image.md
│ │ │ ├── Items.md
│ │ │ ├── LabelList.md
│ │ │ ├── Legend.md
│ │ │ ├── LineChart.md
│ │ │ ├── Link.md
│ │ │ ├── List.md
│ │ │ ├── Logo.md
│ │ │ ├── Markdown.md
│ │ │ ├── MenuItem.md
│ │ │ ├── MenuSeparator.md
│ │ │ ├── ModalDialog.md
│ │ │ ├── NavGroup.md
│ │ │ ├── NavLink.md
│ │ │ ├── NavPanel.md
│ │ │ ├── NoResult.md
│ │ │ ├── NumberBox.md
│ │ │ ├── Option.md
│ │ │ ├── Page.md
│ │ │ ├── PageMetaTitle.md
│ │ │ ├── Pages.md
│ │ │ ├── Pagination.md
│ │ │ ├── PasswordInput.md
│ │ │ ├── PieChart.md
│ │ │ ├── ProgressBar.md
│ │ │ ├── Queue.md
│ │ │ ├── RadioGroup.md
│ │ │ ├── RealTimeAdapter.md
│ │ │ ├── Redirect.md
│ │ │ ├── Select.md
│ │ │ ├── Slider.md
│ │ │ ├── Slot.md
│ │ │ ├── SpaceFiller.md
│ │ │ ├── Spinner.md
│ │ │ ├── Splitter.md
│ │ │ ├── Stack.md
│ │ │ ├── StickyBox.md
│ │ │ ├── SubMenuItem.md
│ │ │ ├── Switch.md
│ │ │ ├── TabItem.md
│ │ │ ├── Table.md
│ │ │ ├── TableOfContents.md
│ │ │ ├── Tabs.md
│ │ │ ├── Text.md
│ │ │ ├── TextArea.md
│ │ │ ├── TextBox.md
│ │ │ ├── Theme.md
│ │ │ ├── TimeInput.md
│ │ │ ├── Timer.md
│ │ │ ├── ToneChangerButton.md
│ │ │ ├── ToneSwitch.md
│ │ │ ├── Tooltip.md
│ │ │ ├── Tree.md
│ │ │ ├── VSplitter.md
│ │ │ ├── VStack.md
│ │ │ ├── xmlui-animations
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── Animation.md
│ │ │ │ ├── FadeAnimation.md
│ │ │ │ ├── FadeInAnimation.md
│ │ │ │ ├── FadeOutAnimation.md
│ │ │ │ ├── ScaleAnimation.md
│ │ │ │ └── SlideInAnimation.md
│ │ │ ├── xmlui-pdf
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Pdf.md
│ │ │ ├── xmlui-spreadsheet
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Spreadsheet.md
│ │ │ └── xmlui-website-blocks
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── Carousel.md
│ │ │ ├── HelloMd.md
│ │ │ ├── HeroSection.md
│ │ │ └── ScrollToTop.md
│ │ └── extensions
│ │ ├── _meta.json
│ │ ├── xmlui-animations
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── Animation.md
│ │ │ ├── FadeAnimation.md
│ │ │ ├── FadeInAnimation.md
│ │ │ ├── FadeOutAnimation.md
│ │ │ ├── ScaleAnimation.md
│ │ │ └── SlideInAnimation.md
│ │ └── xmlui-website-blocks
│ │ ├── _meta.json
│ │ ├── _overview.md
│ │ ├── Carousel.md
│ │ ├── FancyButton.md
│ │ ├── HeroSection.md
│ │ └── ScrollToTop.md
│ ├── extensions.ts
│ ├── index.html
│ ├── index.ts
│ ├── package.json
│ ├── public
│ │ ├── feed.rss
│ │ ├── mockServiceWorker.js
│ │ ├── pages
│ │ │ ├── _meta.json
│ │ │ ├── app-structure.md
│ │ │ ├── build-editor-component.md
│ │ │ ├── build-hello-world-component.md
│ │ │ ├── components-intro.md
│ │ │ ├── context-variables.md
│ │ │ ├── forms.md
│ │ │ ├── globals.md
│ │ │ ├── glossary.md
│ │ │ ├── helper-tags.md
│ │ │ ├── hosted-deployment.md
│ │ │ ├── howto
│ │ │ │ ├── assign-a-complex-json-literal-to-a-component-variable.md
│ │ │ │ ├── chain-a-refetch.md
│ │ │ │ ├── control-cache-invalidation.md
│ │ │ │ ├── debounce-user-input-for-api-calls.md
│ │ │ │ ├── debounce-with-changelistener.md
│ │ │ │ ├── debug-a-component.md
│ │ │ │ ├── delay-a-datasource-until-another-datasource-is-ready.md
│ │ │ │ ├── delegate-a-method.md
│ │ │ │ ├── do-custom-form-validation.md
│ │ │ │ ├── expose-a-method-from-a-component.md
│ │ │ │ ├── filter-and-transform-data-from-an-api.md
│ │ │ │ ├── group-items-in-list-by-a-property.md
│ │ │ │ ├── handle-background-operations.md
│ │ │ │ ├── hide-an-element-until-its-datasource-is-ready.md
│ │ │ │ ├── make-a-set-of-equal-width-cards.md
│ │ │ │ ├── make-a-table-responsive.md
│ │ │ │ ├── make-navpanel-width-responsive.md
│ │ │ │ ├── modify-a-value-reported-in-a-column.md
│ │ │ │ ├── paginate-a-list.md
│ │ │ │ ├── pass-data-to-a-modal-dialog.md
│ │ │ │ ├── react-to-button-click-not-keystrokes.md
│ │ │ │ ├── set-the-initial-value-of-a-select-from-fetched-data.md
│ │ │ │ ├── share-a-modaldialog-across-components.md
│ │ │ │ ├── sync-selections-between-table-and-list-views.md
│ │ │ │ ├── update-ui-optimistically.md
│ │ │ │ ├── use-built-in-form-validation.md
│ │ │ │ └── use-the-same-modaldialog-to-add-or-edit.md
│ │ │ ├── howto.md
│ │ │ ├── intro.md
│ │ │ ├── layout.md
│ │ │ ├── markup.md
│ │ │ ├── mcp.md
│ │ │ ├── modal-dialogs.md
│ │ │ ├── news-and-reviews.md
│ │ │ ├── reactive-intro.md
│ │ │ ├── refactoring.md
│ │ │ ├── routing-and-links.md
│ │ │ ├── samples
│ │ │ │ ├── color-palette.xmlui
│ │ │ │ ├── color-values.xmlui
│ │ │ │ ├── shadow-sizes.xmlui
│ │ │ │ ├── spacing-sizes.xmlui
│ │ │ │ ├── swatch.xmlui
│ │ │ │ ├── theme-gallery-brief.xmlui
│ │ │ │ └── theme-gallery.xmlui
│ │ │ ├── scoping.md
│ │ │ ├── scripting.md
│ │ │ ├── styles-and-themes
│ │ │ │ ├── common-units.md
│ │ │ │ ├── layout-props.md
│ │ │ │ ├── theme-variable-defaults.md
│ │ │ │ ├── theme-variables.md
│ │ │ │ └── themes.md
│ │ │ ├── template-properties.md
│ │ │ ├── test.md
│ │ │ ├── tutorial-01.md
│ │ │ ├── tutorial-02.md
│ │ │ ├── tutorial-03.md
│ │ │ ├── tutorial-04.md
│ │ │ ├── tutorial-05.md
│ │ │ ├── tutorial-06.md
│ │ │ ├── tutorial-07.md
│ │ │ ├── tutorial-08.md
│ │ │ ├── tutorial-09.md
│ │ │ ├── tutorial-10.md
│ │ │ ├── tutorial-11.md
│ │ │ ├── tutorial-12.md
│ │ │ ├── universal-properties.md
│ │ │ ├── user-defined-components.md
│ │ │ ├── vscode.md
│ │ │ ├── working-with-markdown.md
│ │ │ ├── working-with-text.md
│ │ │ ├── xmlui-animations
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── Animation.md
│ │ │ │ ├── FadeAnimation.md
│ │ │ │ ├── FadeInAnimation.md
│ │ │ │ ├── FadeOutAnimation.md
│ │ │ │ ├── ScaleAnimation.md
│ │ │ │ └── SlideInAnimation.md
│ │ │ ├── xmlui-charts
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── BarChart.md
│ │ │ │ ├── DonutChart.md
│ │ │ │ ├── LabelList.md
│ │ │ │ ├── Legend.md
│ │ │ │ ├── LineChart.md
│ │ │ │ └── PieChart.md
│ │ │ ├── xmlui-pdf
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Pdf.md
│ │ │ └── xmlui-spreadsheet
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ └── Spreadsheet.md
│ │ ├── resources
│ │ │ ├── devdocs
│ │ │ │ ├── debug-proxy-object-2.png
│ │ │ │ ├── debug-proxy-object.png
│ │ │ │ ├── table_editor_01.png
│ │ │ │ ├── table_editor_02.png
│ │ │ │ ├── table_editor_03.png
│ │ │ │ ├── table_editor_04.png
│ │ │ │ ├── table_editor_05.png
│ │ │ │ ├── table_editor_06.png
│ │ │ │ ├── table_editor_07.png
│ │ │ │ ├── table_editor_08.png
│ │ │ │ ├── table_editor_09.png
│ │ │ │ ├── table_editor_10.png
│ │ │ │ ├── table_editor_11.png
│ │ │ │ ├── table-editor-01.png
│ │ │ │ ├── table-editor-02.png
│ │ │ │ ├── table-editor-03.png
│ │ │ │ ├── table-editor-04.png
│ │ │ │ ├── table-editor-06.png
│ │ │ │ ├── table-editor-07.png
│ │ │ │ ├── table-editor-08.png
│ │ │ │ ├── table-editor-09.png
│ │ │ │ └── xmlui-rendering-of-tiptap-markdown.png
│ │ │ ├── favicon.ico
│ │ │ ├── files
│ │ │ │ ├── clients.json
│ │ │ │ ├── daily-revenue.json
│ │ │ │ ├── dashboard-stats.json
│ │ │ │ ├── demo.xmlui
│ │ │ │ ├── demo.xmlui.xs
│ │ │ │ ├── downloads
│ │ │ │ │ └── downloads.json
│ │ │ │ ├── for-download
│ │ │ │ │ ├── index-with-api.html
│ │ │ │ │ ├── index.html
│ │ │ │ │ ├── mockApi.js
│ │ │ │ │ ├── start-darwin.sh
│ │ │ │ │ ├── start-linux.sh
│ │ │ │ │ ├── start.bat
│ │ │ │ │ └── xmlui
│ │ │ │ │ └── xmlui-standalone.umd.js
│ │ │ │ ├── getting-started
│ │ │ │ │ ├── cl-tutorial-final.zip
│ │ │ │ │ ├── cl-tutorial.zip
│ │ │ │ │ ├── cl-tutorial2.zip
│ │ │ │ │ ├── cl-tutorial3.zip
│ │ │ │ │ ├── cl-tutorial4.zip
│ │ │ │ │ ├── cl-tutorial5.zip
│ │ │ │ │ ├── cl-tutorial6.zip
│ │ │ │ │ ├── getting-started.zip
│ │ │ │ │ ├── hello-xmlui.zip
│ │ │ │ │ ├── xmlui-empty.zip
│ │ │ │ │ └── xmlui-starter.zip
│ │ │ │ ├── howto
│ │ │ │ │ └── component-icons
│ │ │ │ │ └── up-arrow.svg
│ │ │ │ ├── invoices.json
│ │ │ │ ├── monthly-status.json
│ │ │ │ ├── news-and-reviews.json
│ │ │ │ ├── products.json
│ │ │ │ ├── releases.json
│ │ │ │ ├── tutorials
│ │ │ │ │ ├── datasource
│ │ │ │ │ │ └── api.ts
│ │ │ │ │ └── p2do
│ │ │ │ │ ├── api.ts
│ │ │ │ │ └── todo-logo.svg
│ │ │ │ └── xmlui.json
│ │ │ ├── icons
│ │ │ │ ├── github.svg
│ │ │ │ └── rss.svg
│ │ │ ├── images
│ │ │ │ ├── apiaction-tutorial
│ │ │ │ │ ├── add-success.png
│ │ │ │ │ ├── apiaction-param.png
│ │ │ │ │ ├── change-completed.png
│ │ │ │ │ ├── change-in-progress.png
│ │ │ │ │ ├── confirm-delete.png
│ │ │ │ │ ├── data-error.png
│ │ │ │ │ ├── data-progress.png
│ │ │ │ │ ├── data-success.png
│ │ │ │ │ ├── display-1.png
│ │ │ │ │ ├── item-deleted.png
│ │ │ │ │ ├── item-updated.png
│ │ │ │ │ ├── missing-api-key.png
│ │ │ │ │ ├── new-item-added.png
│ │ │ │ │ └── test-message.png
│ │ │ │ ├── chat-api
│ │ │ │ │ └── domain-model.svg
│ │ │ │ ├── components
│ │ │ │ │ ├── image
│ │ │ │ │ │ └── breakfast.jpg
│ │ │ │ │ ├── markdown
│ │ │ │ │ │ └── colors.png
│ │ │ │ │ └── modal
│ │ │ │ │ ├── deep_link_dialog_1.jpg
│ │ │ │ │ └── deep_link_dialog_2.jpg
│ │ │ │ ├── create-apps
│ │ │ │ │ ├── collapsed-vertical.png
│ │ │ │ │ ├── using-forms-warning-dialog.png
│ │ │ │ │ └── using-forms.png
│ │ │ │ ├── datasource-tutorial
│ │ │ │ │ ├── data-with-header.png
│ │ │ │ │ ├── filtered-data.png
│ │ │ │ │ ├── filtered-items.png
│ │ │ │ │ ├── initial-page-items.png
│ │ │ │ │ ├── list-items.png
│ │ │ │ │ ├── next-page-items.png
│ │ │ │ │ ├── no-data.png
│ │ │ │ │ ├── pagination-1.jpg
│ │ │ │ │ ├── pagination-1.png
│ │ │ │ │ ├── polling-1.png
│ │ │ │ │ ├── refetch-data.png
│ │ │ │ │ ├── slow-loading.png
│ │ │ │ │ ├── test-message.png
│ │ │ │ │ ├── Thumbs.db
│ │ │ │ │ ├── unconventional-data.png
│ │ │ │ │ └── unfiltered-items.png
│ │ │ │ ├── flower.jpg
│ │ │ │ ├── get-started
│ │ │ │ │ ├── add-new-contact.png
│ │ │ │ │ ├── app-modified.png
│ │ │ │ │ ├── app-start.png
│ │ │ │ │ ├── app-with-boxes.png
│ │ │ │ │ ├── app-with-toast.png
│ │ │ │ │ ├── boilerplate-structure.png
│ │ │ │ │ ├── cl-initial.png
│ │ │ │ │ ├── cl-start.png
│ │ │ │ │ ├── contact-counts.png
│ │ │ │ │ ├── contact-dialog-title.png
│ │ │ │ │ ├── contact-dialog.png
│ │ │ │ │ ├── contact-menus.png
│ │ │ │ │ ├── contact-predicates.png
│ │ │ │ │ ├── context-menu.png
│ │ │ │ │ ├── dashboard-numbers.png
│ │ │ │ │ ├── default-contact-list.png
│ │ │ │ │ ├── delete-contact.png
│ │ │ │ │ ├── delete-task.png
│ │ │ │ │ ├── detailed-template.png
│ │ │ │ │ ├── edit-contact-details.png
│ │ │ │ │ ├── edited-contact-saved.png
│ │ │ │ │ ├── empty-sections.png
│ │ │ │ │ ├── filter-completed.png
│ │ │ │ │ ├── fullwidth-desktop.png
│ │ │ │ │ ├── fullwidth-mobile.png
│ │ │ │ │ ├── initial-table.png
│ │ │ │ │ ├── items-and-badges.png
│ │ │ │ │ ├── loading-message.png
│ │ │ │ │ ├── new-contact-button.png
│ │ │ │ │ ├── new-contact-saved.png
│ │ │ │ │ ├── no-empty-sections.png
│ │ │ │ │ ├── personal-todo-initial.png
│ │ │ │ │ ├── piechart.png
│ │ │ │ │ ├── review-today.png
│ │ │ │ │ ├── rudimentary-dashboard.png
│ │ │ │ │ ├── section-collapsed.png
│ │ │ │ │ ├── sectioned-items.png
│ │ │ │ │ ├── sections-ordered.png
│ │ │ │ │ ├── spacex-list-with-links.png
│ │ │ │ │ ├── spacex-list.png
│ │ │ │ │ ├── start-personal-todo-1.png
│ │ │ │ │ ├── submit-new-contact.png
│ │ │ │ │ ├── submit-new-task.png
│ │ │ │ │ ├── syntax-highlighting.png
│ │ │ │ │ ├── table-with-badge.png
│ │ │ │ │ ├── template-with-card.png
│ │ │ │ │ ├── test-emulated-api.png
│ │ │ │ │ ├── Thumbs.db
│ │ │ │ │ ├── todo-logo.png
│ │ │ │ │ └── xmlui-tools.png
│ │ │ │ ├── HelloApp.png
│ │ │ │ ├── HelloApp2.png
│ │ │ │ ├── logos
│ │ │ │ │ ├── xmlui1.svg
│ │ │ │ │ ├── xmlui2.svg
│ │ │ │ │ ├── xmlui3.svg
│ │ │ │ │ ├── xmlui4.svg
│ │ │ │ │ ├── xmlui5.svg
│ │ │ │ │ ├── xmlui6.svg
│ │ │ │ │ └── xmlui7.svg
│ │ │ │ ├── pdf
│ │ │ │ │ └── dummy-pdf.jpg
│ │ │ │ ├── rendering-engine
│ │ │ │ │ ├── AppEngine-flow.svg
│ │ │ │ │ ├── Component.svg
│ │ │ │ │ ├── CompoundComponent.svg
│ │ │ │ │ ├── RootComponent.svg
│ │ │ │ │ └── tree-with-containers.svg
│ │ │ │ ├── reviewers-guide
│ │ │ │ │ ├── AppEngine-flow.svg
│ │ │ │ │ └── incbutton-in-action.png
│ │ │ │ ├── tools
│ │ │ │ │ └── boilerplate-structure.png
│ │ │ │ ├── try.svg
│ │ │ │ ├── tutorial
│ │ │ │ │ ├── app-chat-history.png
│ │ │ │ │ ├── app-content-placeholder.png
│ │ │ │ │ ├── app-header-and-content.png
│ │ │ │ │ ├── app-links-channel-selected.png
│ │ │ │ │ ├── app-links-click.png
│ │ │ │ │ ├── app-navigation.png
│ │ │ │ │ ├── finished-ex01.png
│ │ │ │ │ ├── finished-ex02.png
│ │ │ │ │ ├── hello.png
│ │ │ │ │ ├── splash-screen-advanced.png
│ │ │ │ │ ├── splash-screen-after-click.png
│ │ │ │ │ ├── splash-screen-centered.png
│ │ │ │ │ ├── splash-screen-events.png
│ │ │ │ │ ├── splash-screen-expression.png
│ │ │ │ │ ├── splash-screen-reuse-after.png
│ │ │ │ │ ├── splash-screen-reuse-before.png
│ │ │ │ │ └── splash-screen.png
│ │ │ │ └── tutorial-01.png
│ │ │ ├── llms.txt
│ │ │ ├── logo-dark.svg
│ │ │ ├── logo.svg
│ │ │ ├── pg-popout.svg
│ │ │ └── xmlui-logo.svg
│ │ ├── serve.json
│ │ ├── staticwebapp.config.json
│ │ └── web.config
│ ├── scripts
│ │ ├── download-latest-xmlui.js
│ │ ├── generate-rss.js
│ │ ├── get-releases.js
│ │ └── utils.js
│ ├── src
│ │ ├── components
│ │ │ ├── BlogOverview.xmlui
│ │ │ ├── BlogPage.xmlui
│ │ │ ├── Boxes.xmlui
│ │ │ ├── Breadcrumb.xmlui
│ │ │ ├── ChangeLog.xmlui
│ │ │ ├── ColorPalette.xmlui
│ │ │ ├── DocumentLinks.xmlui
│ │ │ ├── DocumentPage.xmlui
│ │ │ ├── DocumentPageNoTOC.xmlui
│ │ │ ├── Icons.xmlui
│ │ │ ├── IncButton.xmlui
│ │ │ ├── IncButton2.xmlui
│ │ │ ├── LinkButton.xmlui
│ │ │ ├── NameValue.xmlui
│ │ │ ├── PageNotFound.xmlui
│ │ │ ├── PaletteItem.xmlui
│ │ │ ├── Palettes.xmlui
│ │ │ ├── SectionHeader.xmlui
│ │ │ ├── Separator.xmlui
│ │ │ ├── TBD.xmlui
│ │ │ ├── Test.xmlui
│ │ │ ├── ThemesIntro.xmlui
│ │ │ ├── ThousandThemes.xmlui
│ │ │ ├── TubeStops.xmlui
│ │ │ ├── TubeStops.xmlui.xs
│ │ │ └── TwoColumnCode.xmlui
│ │ ├── config.ts
│ │ ├── Main.xmlui
│ │ └── themes
│ │ ├── docs-theme.ts
│ │ ├── earthtone.ts
│ │ ├── xmlui-gray-on-default.ts
│ │ ├── xmlui-green-on-default.ts
│ │ └── xmlui-orange-on-default.ts
│ └── tsconfig.json
├── LICENSE
├── package-lock.json
├── package.json
├── packages
│ ├── tsconfig.json
│ ├── xmlui-animations
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── Animation.tsx
│ │ ├── AnimationNative.tsx
│ │ ├── FadeAnimation.tsx
│ │ ├── FadeInAnimation.tsx
│ │ ├── FadeOutAnimation.tsx
│ │ ├── index.tsx
│ │ ├── ScaleAnimation.tsx
│ │ └── SlideInAnimation.tsx
│ ├── xmlui-devtools
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ ├── src
│ │ │ ├── devtools
│ │ │ │ ├── DevTools.tsx
│ │ │ │ ├── DevToolsNative.module.scss
│ │ │ │ ├── DevToolsNative.tsx
│ │ │ │ ├── ModalDialog.module.scss
│ │ │ │ ├── ModalDialog.tsx
│ │ │ │ ├── ModalVisibilityContext.tsx
│ │ │ │ ├── Tooltip.module.scss
│ │ │ │ ├── Tooltip.tsx
│ │ │ │ └── utils.ts
│ │ │ ├── editor
│ │ │ │ └── Editor.tsx
│ │ │ └── index.tsx
│ │ └── vite.config-overrides.ts
│ ├── xmlui-hello-world
│ │ ├── .gitignore
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── HelloWorld.module.scss
│ │ ├── HelloWorld.tsx
│ │ ├── HelloWorldNative.tsx
│ │ └── index.tsx
│ ├── xmlui-os-frames
│ │ ├── .gitignore
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── index.tsx
│ │ ├── IPhoneFrame.module.scss
│ │ ├── IPhoneFrame.tsx
│ │ ├── MacOSAppFrame.module.scss
│ │ ├── MacOSAppFrame.tsx
│ │ ├── WindowsAppFrame.module.scss
│ │ └── WindowsAppFrame.tsx
│ ├── xmlui-pdf
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ ├── components
│ │ │ │ └── Pdf.xmlui
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── index.tsx
│ │ ├── LazyPdfNative.tsx
│ │ ├── Pdf.module.scss
│ │ └── Pdf.tsx
│ ├── xmlui-playground
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── hooks
│ │ │ ├── usePlayground.ts
│ │ │ └── useToast.ts
│ │ ├── index.tsx
│ │ ├── playground
│ │ │ ├── Box.module.scss
│ │ │ ├── Box.tsx
│ │ │ ├── CodeSelector.module.scss
│ │ │ ├── CodeSelector.tsx
│ │ │ ├── ConfirmationDialog.module.scss
│ │ │ ├── ConfirmationDialog.tsx
│ │ │ ├── Editor.tsx
│ │ │ ├── Header.module.scss
│ │ │ ├── Header.tsx
│ │ │ ├── Playground.tsx
│ │ │ ├── PlaygroundContent.module.scss
│ │ │ ├── PlaygroundContent.tsx
│ │ │ ├── PlaygroundNative.module.scss
│ │ │ ├── PlaygroundNative.tsx
│ │ │ ├── Preview.tsx
│ │ │ ├── StandalonePlayground.tsx
│ │ │ ├── StandalonePlaygroundNative.module.scss
│ │ │ ├── StandalonePlaygroundNative.tsx
│ │ │ ├── ThemeSwitcher.module.scss
│ │ │ ├── ThemeSwitcher.tsx
│ │ │ └── utils.ts
│ │ ├── providers
│ │ │ ├── Toast.module.scss
│ │ │ └── ToastProvider.tsx
│ │ ├── state
│ │ │ └── store.ts
│ │ ├── themes
│ │ │ └── theme.ts
│ │ └── utils
│ │ └── helpers.ts
│ ├── xmlui-search
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── index.tsx
│ │ ├── Search.module.scss
│ │ └── Search.tsx
│ ├── xmlui-spreadsheet
│ │ ├── .gitignore
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── index.tsx
│ │ ├── Spreadsheet.tsx
│ │ └── SpreadsheetNative.tsx
│ └── xmlui-website-blocks
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── demo
│ │ ├── components
│ │ │ ├── HeroBackgroundBreakoutPage.xmlui
│ │ │ ├── HeroBackgroundsPage.xmlui
│ │ │ ├── HeroContentsPage.xmlui
│ │ │ ├── HeroTextAlignPage.xmlui
│ │ │ ├── HeroTextPage.xmlui
│ │ │ └── HeroTonesPage.xmlui
│ │ ├── Main.xmlui
│ │ └── themes
│ │ └── default.ts
│ ├── index.html
│ ├── index.ts
│ ├── meta
│ │ └── componentsMetadata.ts
│ ├── package.json
│ ├── public
│ │ └── resources
│ │ ├── building.jpg
│ │ └── xmlui-logo.svg
│ └── src
│ ├── Carousel
│ │ ├── Carousel.module.scss
│ │ ├── Carousel.tsx
│ │ ├── CarouselContext.tsx
│ │ └── CarouselNative.tsx
│ ├── FancyButton
│ │ ├── FancyButton.module.scss
│ │ ├── FancyButton.tsx
│ │ └── FancyButton.xmlui
│ ├── Hello
│ │ ├── Hello.tsx
│ │ ├── Hello.xmlui
│ │ └── Hello.xmlui.xs
│ ├── HeroSection
│ │ ├── HeroSection.module.scss
│ │ ├── HeroSection.spec.ts
│ │ ├── HeroSection.tsx
│ │ └── HeroSectionNative.tsx
│ ├── index.tsx
│ ├── ScrollToTop
│ │ ├── ScrollToTop.module.scss
│ │ ├── ScrollToTop.tsx
│ │ └── ScrollToTopNative.tsx
│ └── vite-env.d.ts
├── playwright.config.ts
├── README.md
├── tools
│ ├── codefence
│ │ └── xmlui-code-fence-docs.md
│ ├── create-app
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── create-app.ts
│ │ ├── helpers
│ │ │ ├── copy.ts
│ │ │ ├── get-pkg-manager.ts
│ │ │ ├── git.ts
│ │ │ ├── install.ts
│ │ │ ├── is-folder-empty.ts
│ │ │ ├── is-writeable.ts
│ │ │ ├── make-dir.ts
│ │ │ └── validate-pkg.ts
│ │ ├── index.ts
│ │ ├── package.json
│ │ ├── templates
│ │ │ ├── default
│ │ │ │ └── ts
│ │ │ │ ├── gitignore
│ │ │ │ ├── index.html
│ │ │ │ ├── index.ts
│ │ │ │ ├── public
│ │ │ │ │ ├── mockServiceWorker.js
│ │ │ │ │ ├── resources
│ │ │ │ │ │ ├── favicon.ico
│ │ │ │ │ │ └── xmlui-logo.svg
│ │ │ │ │ └── serve.json
│ │ │ │ └── src
│ │ │ │ ├── components
│ │ │ │ │ ├── ApiAware.xmlui
│ │ │ │ │ ├── Home.xmlui
│ │ │ │ │ ├── IncButton.xmlui
│ │ │ │ │ └── PagePanel.xmlui
│ │ │ │ ├── config.ts
│ │ │ │ └── Main.xmlui
│ │ │ ├── index.ts
│ │ │ └── types.ts
│ │ └── tsconfig.json
│ ├── create-xmlui-hello-world
│ │ ├── index.js
│ │ └── package.json
│ └── vscode
│ ├── .gitignore
│ ├── .vscode
│ │ ├── launch.json
│ │ └── tasks.json
│ ├── .vscodeignore
│ ├── build.sh
│ ├── CHANGELOG.md
│ ├── esbuild.js
│ ├── eslint.config.mjs
│ ├── formatter-docs.md
│ ├── generate-test-sample.sh
│ ├── LICENSE.md
│ ├── package-lock.json
│ ├── package.json
│ ├── README.md
│ ├── resources
│ │ ├── xmlui-logo.png
│ │ └── xmlui-markup-syntax-highlighting.png
│ ├── src
│ │ ├── extension.ts
│ │ └── server.ts
│ ├── syntaxes
│ │ └── xmlui.tmLanguage.json
│ ├── test-samples
│ │ └── sample.xmlui
│ ├── tsconfig.json
│ └── tsconfig.tsbuildinfo
├── turbo.json
└── xmlui
├── .gitignore
├── bin
│ ├── bootstrap.cjs
│ ├── bootstrap.js
│ ├── build-lib.ts
│ ├── build.ts
│ ├── index.ts
│ ├── preview.ts
│ ├── start.ts
│ ├── vite-xmlui-plugin.ts
│ └── viteConfig.ts
├── CHANGELOG.md
├── conventions
│ ├── component-qa-checklist.md
│ ├── copilot-conventions.md
│ ├── create-xmlui-components.md
│ ├── mermaid.md
│ ├── testing-conventions.md
│ └── xmlui-in-a-nutshell.md
├── dev-docs
│ ├── accessibility.md
│ ├── build-system.md
│ ├── build-xmlui.md
│ ├── component-behaviors.md
│ ├── component-metadata.md
│ ├── components-with-options.md
│ ├── containers.md
│ ├── data-operations.md
│ ├── glossary.md
│ ├── index.md
│ ├── next
│ │ ├── component-dev-guide.md
│ │ ├── configuration-management-enhancement-summary.md
│ │ ├── documentation-scripts-refactoring-complete-summary.md
│ │ ├── documentation-scripts-refactoring-plan.md
│ │ ├── duplicate-pattern-extraction-summary.md
│ │ ├── error-handling-standardization-summary.md
│ │ ├── generating-component-reference.md
│ │ ├── index.md
│ │ ├── logging-consistency-implementation-summary.md
│ │ ├── project-build.md
│ │ ├── project-structure.md
│ │ ├── theme-context.md
│ │ ├── tiptap-design-considerations.md
│ │ ├── working-with-code.md
│ │ ├── xmlui-runtime-architecture
│ │ └── xmlui-wcag-accessibility-report.md
│ ├── react-fundamentals.md
│ ├── release-method.md
│ ├── standalone-app.md
│ ├── theme-variables-refactoring.md
│ ├── ud-components.md
│ └── xmlui-repo.md
├── package.json
├── scripts
│ ├── coverage-only.js
│ ├── e2e-test-summary.js
│ ├── extract-component-metadata.js
│ ├── generate-docs
│ │ ├── build-downloads-map.mjs
│ │ ├── build-pages-map.mjs
│ │ ├── components-config.json
│ │ ├── configuration-management.mjs
│ │ ├── constants.mjs
│ │ ├── create-theme-files.mjs
│ │ ├── DocsGenerator.mjs
│ │ ├── error-handling.mjs
│ │ ├── extensions-config.json
│ │ ├── folders.mjs
│ │ ├── generate-summary-files.mjs
│ │ ├── get-docs.mjs
│ │ ├── input-handler.mjs
│ │ ├── logger.mjs
│ │ ├── logging-standards.mjs
│ │ ├── MetadataProcessor.mjs
│ │ ├── pattern-utilities.mjs
│ │ └── utils.mjs
│ ├── generate-metadata-markdown.js
│ ├── get-langserver-metadata.js
│ ├── inline-links.mjs
│ └── README-e2e-summary.md
├── src
│ ├── abstractions
│ │ ├── _conventions.md
│ │ ├── ActionDefs.ts
│ │ ├── AppContextDefs.ts
│ │ ├── ComponentDefs.ts
│ │ ├── ContainerDefs.ts
│ │ ├── ExtensionDefs.ts
│ │ ├── FunctionDefs.ts
│ │ ├── RendererDefs.ts
│ │ ├── scripting
│ │ │ ├── BlockScope.ts
│ │ │ ├── Compilation.ts
│ │ │ ├── LogicalThread.ts
│ │ │ ├── LoopScope.ts
│ │ │ ├── modules.ts
│ │ │ ├── ScriptParserError.ts
│ │ │ ├── Token.ts
│ │ │ ├── TryScope.ts
│ │ │ └── TryScopeExp.ts
│ │ └── ThemingDefs.ts
│ ├── components
│ │ ├── _conventions.md
│ │ ├── abstractions.ts
│ │ ├── Accordion
│ │ │ ├── Accordion.md
│ │ │ ├── Accordion.module.scss
│ │ │ ├── Accordion.spec.ts
│ │ │ ├── Accordion.tsx
│ │ │ ├── AccordionContext.tsx
│ │ │ ├── AccordionItem.tsx
│ │ │ ├── AccordionItemNative.tsx
│ │ │ └── AccordionNative.tsx
│ │ ├── Animation
│ │ │ └── AnimationNative.tsx
│ │ ├── APICall
│ │ │ ├── APICall.md
│ │ │ ├── APICall.spec.ts
│ │ │ ├── APICall.tsx
│ │ │ └── APICallNative.tsx
│ │ ├── App
│ │ │ ├── App.md
│ │ │ ├── App.module.scss
│ │ │ ├── App.spec.ts
│ │ │ ├── App.tsx
│ │ │ ├── AppLayoutContext.ts
│ │ │ ├── AppNative.tsx
│ │ │ ├── AppStateContext.ts
│ │ │ ├── doc-resources
│ │ │ │ ├── condensed-sticky.xmlui
│ │ │ │ ├── condensed.xmlui
│ │ │ │ ├── horizontal-sticky.xmlui
│ │ │ │ ├── horizontal.xmlui
│ │ │ │ ├── vertical-full-header.xmlui
│ │ │ │ ├── vertical-sticky.xmlui
│ │ │ │ └── vertical.xmlui
│ │ │ ├── IndexerContext.ts
│ │ │ ├── LinkInfoContext.ts
│ │ │ ├── SearchContext.tsx
│ │ │ ├── Sheet.module.scss
│ │ │ └── Sheet.tsx
│ │ ├── AppHeader
│ │ │ ├── AppHeader.md
│ │ │ ├── AppHeader.module.scss
│ │ │ ├── AppHeader.spec.ts
│ │ │ ├── AppHeader.tsx
│ │ │ └── AppHeaderNative.tsx
│ │ ├── AppState
│ │ │ ├── AppState.md
│ │ │ ├── AppState.spec.ts
│ │ │ ├── AppState.tsx
│ │ │ └── AppStateNative.tsx
│ │ ├── AutoComplete
│ │ │ ├── AutoComplete.md
│ │ │ ├── AutoComplete.module.scss
│ │ │ ├── AutoComplete.spec.ts
│ │ │ ├── AutoComplete.tsx
│ │ │ ├── AutoCompleteContext.tsx
│ │ │ └── AutoCompleteNative.tsx
│ │ ├── Avatar
│ │ │ ├── Avatar.md
│ │ │ ├── Avatar.module.scss
│ │ │ ├── Avatar.spec.ts
│ │ │ ├── Avatar.tsx
│ │ │ └── AvatarNative.tsx
│ │ ├── Backdrop
│ │ │ ├── Backdrop.md
│ │ │ ├── Backdrop.module.scss
│ │ │ ├── Backdrop.spec.ts
│ │ │ ├── Backdrop.tsx
│ │ │ └── BackdropNative.tsx
│ │ ├── Badge
│ │ │ ├── Badge.md
│ │ │ ├── Badge.module.scss
│ │ │ ├── Badge.spec.ts
│ │ │ ├── Badge.tsx
│ │ │ └── BadgeNative.tsx
│ │ ├── Bookmark
│ │ │ ├── Bookmark.md
│ │ │ ├── Bookmark.module.scss
│ │ │ ├── Bookmark.spec.ts
│ │ │ ├── Bookmark.tsx
│ │ │ └── BookmarkNative.tsx
│ │ ├── Breakout
│ │ │ ├── Breakout.module.scss
│ │ │ ├── Breakout.spec.ts
│ │ │ ├── Breakout.tsx
│ │ │ └── BreakoutNative.tsx
│ │ ├── Button
│ │ │ ├── Button-style.spec.ts
│ │ │ ├── Button.md
│ │ │ ├── Button.module.scss
│ │ │ ├── Button.spec.ts
│ │ │ ├── Button.tsx
│ │ │ └── ButtonNative.tsx
│ │ ├── Card
│ │ │ ├── Card.md
│ │ │ ├── Card.module.scss
│ │ │ ├── Card.spec.ts
│ │ │ ├── Card.tsx
│ │ │ └── CardNative.tsx
│ │ ├── Carousel
│ │ │ ├── Carousel.md
│ │ │ ├── Carousel.module.scss
│ │ │ ├── Carousel.spec.ts
│ │ │ ├── Carousel.tsx
│ │ │ ├── CarouselContext.tsx
│ │ │ ├── CarouselItem.tsx
│ │ │ ├── CarouselItemNative.tsx
│ │ │ └── CarouselNative.tsx
│ │ ├── ChangeListener
│ │ │ ├── ChangeListener.md
│ │ │ ├── ChangeListener.spec.ts
│ │ │ ├── ChangeListener.tsx
│ │ │ └── ChangeListenerNative.tsx
│ │ ├── chart-color-schemes.ts
│ │ ├── Charts
│ │ │ ├── AreaChart
│ │ │ │ ├── AreaChart.md
│ │ │ │ ├── AreaChart.spec.ts
│ │ │ │ ├── AreaChart.tsx
│ │ │ │ └── AreaChartNative.tsx
│ │ │ ├── BarChart
│ │ │ │ ├── BarChart.md
│ │ │ │ ├── BarChart.module.scss
│ │ │ │ ├── BarChart.spec.ts
│ │ │ │ ├── BarChart.tsx
│ │ │ │ └── BarChartNative.tsx
│ │ │ ├── DonutChart
│ │ │ │ ├── DonutChart.spec.ts
│ │ │ │ └── DonutChart.tsx
│ │ │ ├── LabelList
│ │ │ │ ├── LabelList.module.scss
│ │ │ │ ├── LabelList.spec.ts
│ │ │ │ ├── LabelList.tsx
│ │ │ │ └── LabelListNative.tsx
│ │ │ ├── Legend
│ │ │ │ ├── Legend.spec.ts
│ │ │ │ ├── Legend.tsx
│ │ │ │ └── LegendNative.tsx
│ │ │ ├── LineChart
│ │ │ │ ├── LineChart.md
│ │ │ │ ├── LineChart.module.scss
│ │ │ │ ├── LineChart.spec.ts
│ │ │ │ ├── LineChart.tsx
│ │ │ │ └── LineChartNative.tsx
│ │ │ ├── PieChart
│ │ │ │ ├── PieChart.md
│ │ │ │ ├── PieChart.spec.ts
│ │ │ │ ├── PieChart.tsx
│ │ │ │ ├── PieChartNative.module.scss
│ │ │ │ └── PieChartNative.tsx
│ │ │ ├── RadarChart
│ │ │ │ ├── RadarChart.md
│ │ │ │ ├── RadarChart.spec.ts
│ │ │ │ ├── RadarChart.tsx
│ │ │ │ └── RadarChartNative.tsx
│ │ │ ├── Tooltip
│ │ │ │ ├── TooltipContent.module.scss
│ │ │ │ ├── TooltipContent.spec.ts
│ │ │ │ └── TooltipContent.tsx
│ │ │ └── utils
│ │ │ ├── abstractions.ts
│ │ │ └── ChartProvider.tsx
│ │ ├── Checkbox
│ │ │ ├── Checkbox.md
│ │ │ ├── Checkbox.spec.ts
│ │ │ └── Checkbox.tsx
│ │ ├── CodeBlock
│ │ │ ├── CodeBlock.module.scss
│ │ │ ├── CodeBlock.spec.ts
│ │ │ ├── CodeBlock.tsx
│ │ │ ├── CodeBlockNative.tsx
│ │ │ └── highlight-code.ts
│ │ ├── collectedComponentMetadata.ts
│ │ ├── ColorPicker
│ │ │ ├── ColorPicker.md
│ │ │ ├── ColorPicker.module.scss
│ │ │ ├── ColorPicker.spec.ts
│ │ │ ├── ColorPicker.tsx
│ │ │ └── ColorPickerNative.tsx
│ │ ├── Column
│ │ │ ├── Column.md
│ │ │ ├── Column.tsx
│ │ │ ├── ColumnNative.tsx
│ │ │ ├── doc-resources
│ │ │ │ └── list-component-data.js
│ │ │ └── TableContext.tsx
│ │ ├── component-utils.ts
│ │ ├── ComponentProvider.tsx
│ │ ├── ComponentRegistryContext.tsx
│ │ ├── container-helpers.tsx
│ │ ├── ContentSeparator
│ │ │ ├── ContentSeparator.md
│ │ │ ├── ContentSeparator.module.scss
│ │ │ ├── ContentSeparator.spec.ts
│ │ │ ├── ContentSeparator.tsx
│ │ │ ├── ContentSeparatorNative.tsx
│ │ │ └── test-padding.xmlui
│ │ ├── DataSource
│ │ │ ├── DataSource.md
│ │ │ └── DataSource.tsx
│ │ ├── DateInput
│ │ │ ├── DateInput.md
│ │ │ ├── DateInput.module.scss
│ │ │ ├── DateInput.spec.ts
│ │ │ ├── DateInput.tsx
│ │ │ └── DateInputNative.tsx
│ │ ├── DatePicker
│ │ │ ├── DatePicker.md
│ │ │ ├── DatePicker.module.scss
│ │ │ ├── DatePicker.spec.ts
│ │ │ ├── DatePicker.tsx
│ │ │ └── DatePickerNative.tsx
│ │ ├── DropdownMenu
│ │ │ ├── DropdownMenu.md
│ │ │ ├── DropdownMenu.module.scss
│ │ │ ├── DropdownMenu.spec.ts
│ │ │ ├── DropdownMenu.tsx
│ │ │ ├── DropdownMenuNative.tsx
│ │ │ ├── MenuItem.md
│ │ │ └── SubMenuItem.md
│ │ ├── EmojiSelector
│ │ │ ├── EmojiSelector.md
│ │ │ ├── EmojiSelector.spec.ts
│ │ │ ├── EmojiSelector.tsx
│ │ │ └── EmojiSelectorNative.tsx
│ │ ├── ExpandableItem
│ │ │ ├── ExpandableItem.module.scss
│ │ │ ├── ExpandableItem.spec.ts
│ │ │ ├── ExpandableItem.tsx
│ │ │ └── ExpandableItemNative.tsx
│ │ ├── FileInput
│ │ │ ├── FileInput.md
│ │ │ ├── FileInput.module.scss
│ │ │ ├── FileInput.spec.ts
│ │ │ ├── FileInput.tsx
│ │ │ └── FileInputNative.tsx
│ │ ├── FileUploadDropZone
│ │ │ ├── FileUploadDropZone.md
│ │ │ ├── FileUploadDropZone.module.scss
│ │ │ ├── FileUploadDropZone.spec.ts
│ │ │ ├── FileUploadDropZone.tsx
│ │ │ └── FileUploadDropZoneNative.tsx
│ │ ├── FlowLayout
│ │ │ ├── FlowLayout.md
│ │ │ ├── FlowLayout.module.scss
│ │ │ ├── FlowLayout.spec.ts
│ │ │ ├── FlowLayout.spec.ts-snapshots
│ │ │ │ └── Edge-cases-boxShadow-is-not-clipped-1-non-smoke-darwin.png
│ │ │ ├── FlowLayout.tsx
│ │ │ └── FlowLayoutNative.tsx
│ │ ├── Footer
│ │ │ ├── Footer.md
│ │ │ ├── Footer.module.scss
│ │ │ ├── Footer.spec.ts
│ │ │ ├── Footer.tsx
│ │ │ └── FooterNative.tsx
│ │ ├── Form
│ │ │ ├── Form.md
│ │ │ ├── Form.module.scss
│ │ │ ├── Form.spec.ts
│ │ │ ├── Form.tsx
│ │ │ ├── formActions.ts
│ │ │ ├── FormContext.ts
│ │ │ └── FormNative.tsx
│ │ ├── FormItem
│ │ │ ├── FormItem.md
│ │ │ ├── FormItem.module.scss
│ │ │ ├── FormItem.spec.ts
│ │ │ ├── FormItem.tsx
│ │ │ ├── FormItemNative.tsx
│ │ │ ├── HelperText.module.scss
│ │ │ ├── HelperText.tsx
│ │ │ ├── ItemWithLabel.tsx
│ │ │ └── Validations.ts
│ │ ├── FormSection
│ │ │ ├── FormSection.md
│ │ │ ├── FormSection.ts
│ │ │ └── FormSection.xmlui
│ │ ├── Fragment
│ │ │ ├── Fragment.spec.ts
│ │ │ └── Fragment.tsx
│ │ ├── Heading
│ │ │ ├── abstractions.ts
│ │ │ ├── H1.md
│ │ │ ├── H1.spec.ts
│ │ │ ├── H2.md
│ │ │ ├── H2.spec.ts
│ │ │ ├── H3.md
│ │ │ ├── H3.spec.ts
│ │ │ ├── H4.md
│ │ │ ├── H4.spec.ts
│ │ │ ├── H5.md
│ │ │ ├── H5.spec.ts
│ │ │ ├── H6.md
│ │ │ ├── H6.spec.ts
│ │ │ ├── Heading.md
│ │ │ ├── Heading.module.scss
│ │ │ ├── Heading.spec.ts
│ │ │ ├── Heading.tsx
│ │ │ └── HeadingNative.tsx
│ │ ├── HoverCard
│ │ │ ├── HoverCard.tsx
│ │ │ └── HovercardNative.tsx
│ │ ├── HtmlTags
│ │ │ ├── HtmlTags.module.scss
│ │ │ ├── HtmlTags.spec.ts
│ │ │ └── HtmlTags.tsx
│ │ ├── Icon
│ │ │ ├── AdmonitionDanger.tsx
│ │ │ ├── AdmonitionInfo.tsx
│ │ │ ├── AdmonitionNote.tsx
│ │ │ ├── AdmonitionTip.tsx
│ │ │ ├── AdmonitionWarning.tsx
│ │ │ ├── ApiIcon.tsx
│ │ │ ├── ArrowDropDown.module.scss
│ │ │ ├── ArrowDropDown.tsx
│ │ │ ├── ArrowDropUp.module.scss
│ │ │ ├── ArrowDropUp.tsx
│ │ │ ├── ArrowLeft.module.scss
│ │ │ ├── ArrowLeft.tsx
│ │ │ ├── ArrowRight.module.scss
│ │ │ ├── ArrowRight.tsx
│ │ │ ├── Attach.tsx
│ │ │ ├── Binding.module.scss
│ │ │ ├── Binding.tsx
│ │ │ ├── BoardIcon.tsx
│ │ │ ├── BoxIcon.tsx
│ │ │ ├── CheckIcon.tsx
│ │ │ ├── ChevronDownIcon.tsx
│ │ │ ├── ChevronLeft.tsx
│ │ │ ├── ChevronRight.tsx
│ │ │ ├── ChevronUpIcon.tsx
│ │ │ ├── CodeFileIcon.tsx
│ │ │ ├── CodeSandbox.tsx
│ │ │ ├── CompactListIcon.tsx
│ │ │ ├── ContentCopyIcon.tsx
│ │ │ ├── DarkToLightIcon.tsx
│ │ │ ├── DatabaseIcon.module.scss
│ │ │ ├── DatabaseIcon.tsx
│ │ │ ├── DocFileIcon.tsx
│ │ │ ├── DocIcon.tsx
│ │ │ ├── DotMenuHorizontalIcon.tsx
│ │ │ ├── DotMenuIcon.tsx
│ │ │ ├── EmailIcon.tsx
│ │ │ ├── EmptyFolderIcon.tsx
│ │ │ ├── ErrorIcon.tsx
│ │ │ ├── ExpressionIcon.tsx
│ │ │ ├── FillPlusCricleIcon.tsx
│ │ │ ├── FilterIcon.tsx
│ │ │ ├── FolderIcon.tsx
│ │ │ ├── GlobeIcon.tsx
│ │ │ ├── HomeIcon.tsx
│ │ │ ├── HyperLinkIcon.tsx
│ │ │ ├── Icon.md
│ │ │ ├── Icon.module.scss
│ │ │ ├── Icon.spec.ts
│ │ │ ├── Icon.tsx
│ │ │ ├── IconNative.tsx
│ │ │ ├── ImageFileIcon.tsx
│ │ │ ├── Inspect.tsx
│ │ │ ├── LightToDark.tsx
│ │ │ ├── LinkIcon.tsx
│ │ │ ├── ListIcon.tsx
│ │ │ ├── LooseListIcon.tsx
│ │ │ ├── MoonIcon.tsx
│ │ │ ├── MoreOptionsIcon.tsx
│ │ │ ├── NoSortIcon.tsx
│ │ │ ├── PDFIcon.tsx
│ │ │ ├── PenIcon.tsx
│ │ │ ├── PhoneIcon.tsx
│ │ │ ├── PhotoIcon.tsx
│ │ │ ├── PlusIcon.tsx
│ │ │ ├── SearchIcon.tsx
│ │ │ ├── ShareIcon.tsx
│ │ │ ├── SortAscendingIcon.tsx
│ │ │ ├── SortDescendingIcon.tsx
│ │ │ ├── StarsIcon.tsx
│ │ │ ├── SunIcon.tsx
│ │ │ ├── svg
│ │ │ │ ├── admonition_danger.svg
│ │ │ │ ├── admonition_info.svg
│ │ │ │ ├── admonition_note.svg
│ │ │ │ ├── admonition_tip.svg
│ │ │ │ ├── admonition_warning.svg
│ │ │ │ ├── api.svg
│ │ │ │ ├── arrow-dropdown.svg
│ │ │ │ ├── arrow-left.svg
│ │ │ │ ├── arrow-right.svg
│ │ │ │ ├── arrow-up.svg
│ │ │ │ ├── attach.svg
│ │ │ │ ├── binding.svg
│ │ │ │ ├── box.svg
│ │ │ │ ├── bulb.svg
│ │ │ │ ├── code-file.svg
│ │ │ │ ├── code-sandbox.svg
│ │ │ │ ├── dark_to_light.svg
│ │ │ │ ├── database.svg
│ │ │ │ ├── doc.svg
│ │ │ │ ├── empty-folder.svg
│ │ │ │ ├── expression.svg
│ │ │ │ ├── eye-closed.svg
│ │ │ │ ├── eye-dark.svg
│ │ │ │ ├── eye.svg
│ │ │ │ ├── file-text.svg
│ │ │ │ ├── filter.svg
│ │ │ │ ├── folder.svg
│ │ │ │ ├── img.svg
│ │ │ │ ├── inspect.svg
│ │ │ │ ├── light_to_dark.svg
│ │ │ │ ├── moon.svg
│ │ │ │ ├── pdf.svg
│ │ │ │ ├── photo.svg
│ │ │ │ ├── share.svg
│ │ │ │ ├── stars.svg
│ │ │ │ ├── sun.svg
│ │ │ │ ├── trending-down.svg
│ │ │ │ ├── trending-level.svg
│ │ │ │ ├── trending-up.svg
│ │ │ │ ├── txt.svg
│ │ │ │ ├── unknown-file.svg
│ │ │ │ ├── unlink.svg
│ │ │ │ └── xls.svg
│ │ │ ├── TableDeleteColumnIcon.tsx
│ │ │ ├── TableDeleteRowIcon.tsx
│ │ │ ├── TableInsertColumnIcon.tsx
│ │ │ ├── TableInsertRowIcon.tsx
│ │ │ ├── TrashIcon.tsx
│ │ │ ├── TrendingDownIcon.tsx
│ │ │ ├── TrendingLevelIcon.tsx
│ │ │ ├── TrendingUpIcon.tsx
│ │ │ ├── TxtIcon.tsx
│ │ │ ├── UnknownFileIcon.tsx
│ │ │ ├── UnlinkIcon.tsx
│ │ │ ├── UserIcon.tsx
│ │ │ ├── WarningIcon.tsx
│ │ │ └── XlsIcon.tsx
│ │ ├── IconProvider.tsx
│ │ ├── IconRegistryContext.tsx
│ │ ├── IFrame
│ │ │ ├── IFrame.md
│ │ │ ├── IFrame.module.scss
│ │ │ ├── IFrame.spec.ts
│ │ │ ├── IFrame.tsx
│ │ │ └── IFrameNative.tsx
│ │ ├── Image
│ │ │ ├── Image.md
│ │ │ ├── Image.module.scss
│ │ │ ├── Image.spec.ts
│ │ │ ├── Image.tsx
│ │ │ └── ImageNative.tsx
│ │ ├── Input
│ │ │ ├── index.ts
│ │ │ ├── InputAdornment.module.scss
│ │ │ ├── InputAdornment.tsx
│ │ │ ├── InputDivider.module.scss
│ │ │ ├── InputDivider.tsx
│ │ │ ├── InputLabel.module.scss
│ │ │ ├── InputLabel.tsx
│ │ │ ├── PartialInput.module.scss
│ │ │ └── PartialInput.tsx
│ │ ├── InspectButton
│ │ │ ├── InspectButton.module.scss
│ │ │ └── InspectButton.tsx
│ │ ├── Items
│ │ │ ├── Items.md
│ │ │ ├── Items.spec.ts
│ │ │ ├── Items.tsx
│ │ │ └── ItemsNative.tsx
│ │ ├── Link
│ │ │ ├── Link.md
│ │ │ ├── Link.module.scss
│ │ │ ├── Link.spec.ts
│ │ │ ├── Link.tsx
│ │ │ └── LinkNative.tsx
│ │ ├── List
│ │ │ ├── doc-resources
│ │ │ │ └── list-component-data.js
│ │ │ ├── List.md
│ │ │ ├── List.module.scss
│ │ │ ├── List.spec.ts
│ │ │ ├── List.tsx
│ │ │ └── ListNative.tsx
│ │ ├── Logo
│ │ │ ├── doc-resources
│ │ │ │ └── xmlui-logo.svg
│ │ │ ├── Logo.md
│ │ │ ├── Logo.tsx
│ │ │ └── LogoNative.tsx
│ │ ├── Markdown
│ │ │ ├── CodeText.module.scss
│ │ │ ├── CodeText.tsx
│ │ │ ├── Markdown.md
│ │ │ ├── Markdown.module.scss
│ │ │ ├── Markdown.spec.ts
│ │ │ ├── Markdown.tsx
│ │ │ ├── MarkdownNative.tsx
│ │ │ ├── parse-binding-expr.ts
│ │ │ └── utils.ts
│ │ ├── metadata-helpers.ts
│ │ ├── ModalDialog
│ │ │ ├── ConfirmationModalContextProvider.tsx
│ │ │ ├── Dialog.module.scss
│ │ │ ├── Dialog.tsx
│ │ │ ├── ModalDialog.md
│ │ │ ├── ModalDialog.module.scss
│ │ │ ├── ModalDialog.spec.ts
│ │ │ ├── ModalDialog.tsx
│ │ │ ├── ModalDialogNative.tsx
│ │ │ └── ModalVisibilityContext.tsx
│ │ ├── NavGroup
│ │ │ ├── NavGroup.md
│ │ │ ├── NavGroup.module.scss
│ │ │ ├── NavGroup.spec.ts
│ │ │ ├── NavGroup.tsx
│ │ │ ├── NavGroupContext.ts
│ │ │ └── NavGroupNative.tsx
│ │ ├── NavLink
│ │ │ ├── NavLink.md
│ │ │ ├── NavLink.module.scss
│ │ │ ├── NavLink.spec.ts
│ │ │ ├── NavLink.tsx
│ │ │ └── NavLinkNative.tsx
│ │ ├── NavPanel
│ │ │ ├── NavPanel.md
│ │ │ ├── NavPanel.module.scss
│ │ │ ├── NavPanel.spec.ts
│ │ │ ├── NavPanel.tsx
│ │ │ └── NavPanelNative.tsx
│ │ ├── NestedApp
│ │ │ ├── AppWithCodeView.module.scss
│ │ │ ├── AppWithCodeView.tsx
│ │ │ ├── AppWithCodeViewNative.tsx
│ │ │ ├── defaultProps.tsx
│ │ │ ├── logo.svg
│ │ │ ├── NestedApp.module.scss
│ │ │ ├── NestedApp.tsx
│ │ │ ├── NestedAppNative.tsx
│ │ │ ├── Tooltip.module.scss
│ │ │ ├── Tooltip.tsx
│ │ │ └── utils.ts
│ │ ├── NoResult
│ │ │ ├── NoResult.md
│ │ │ ├── NoResult.module.scss
│ │ │ ├── NoResult.spec.ts
│ │ │ ├── NoResult.tsx
│ │ │ └── NoResultNative.tsx
│ │ ├── NumberBox
│ │ │ ├── numberbox-abstractions.ts
│ │ │ ├── NumberBox.md
│ │ │ ├── NumberBox.module.scss
│ │ │ ├── NumberBox.spec.ts
│ │ │ ├── NumberBox.tsx
│ │ │ └── NumberBoxNative.tsx
│ │ ├── Option
│ │ │ ├── Option.md
│ │ │ ├── Option.spec.ts
│ │ │ ├── Option.tsx
│ │ │ ├── OptionNative.tsx
│ │ │ └── OptionTypeProvider.tsx
│ │ ├── PageMetaTitle
│ │ │ ├── PageMetaTilteNative.tsx
│ │ │ ├── PageMetaTitle.md
│ │ │ ├── PageMetaTitle.spec.ts
│ │ │ └── PageMetaTitle.tsx
│ │ ├── Pages
│ │ │ ├── Page.md
│ │ │ ├── Pages.md
│ │ │ ├── Pages.module.scss
│ │ │ ├── Pages.tsx
│ │ │ └── PagesNative.tsx
│ │ ├── Pagination
│ │ │ ├── Pagination.md
│ │ │ ├── Pagination.module.scss
│ │ │ ├── Pagination.spec.ts
│ │ │ ├── Pagination.tsx
│ │ │ └── PaginationNative.tsx
│ │ ├── PositionedContainer
│ │ │ ├── PositionedContainer.module.scss
│ │ │ ├── PositionedContainer.tsx
│ │ │ └── PositionedContainerNative.tsx
│ │ ├── ProfileMenu
│ │ │ ├── ProfileMenu.module.scss
│ │ │ └── ProfileMenu.tsx
│ │ ├── ProgressBar
│ │ │ ├── ProgressBar.md
│ │ │ ├── ProgressBar.module.scss
│ │ │ ├── ProgressBar.spec.ts
│ │ │ ├── ProgressBar.tsx
│ │ │ └── ProgressBarNative.tsx
│ │ ├── Queue
│ │ │ ├── Queue.md
│ │ │ ├── Queue.spec.ts
│ │ │ ├── Queue.tsx
│ │ │ ├── queueActions.ts
│ │ │ └── QueueNative.tsx
│ │ ├── RadioGroup
│ │ │ ├── RadioGroup.md
│ │ │ ├── RadioGroup.module.scss
│ │ │ ├── RadioGroup.spec.ts
│ │ │ ├── RadioGroup.tsx
│ │ │ ├── RadioGroupNative.tsx
│ │ │ ├── RadioItem.tsx
│ │ │ └── RadioItemNative.tsx
│ │ ├── RealTimeAdapter
│ │ │ ├── RealTimeAdapter.tsx
│ │ │ └── RealTimeAdapterNative.tsx
│ │ ├── Redirect
│ │ │ ├── Redirect.md
│ │ │ ├── Redirect.spec.ts
│ │ │ └── Redirect.tsx
│ │ ├── ResponsiveBar
│ │ │ ├── README.md
│ │ │ ├── ResponsiveBar.md
│ │ │ ├── ResponsiveBar.module.scss
│ │ │ ├── ResponsiveBar.spec.ts
│ │ │ ├── ResponsiveBar.tsx
│ │ │ └── ResponsiveBarNative.tsx
│ │ ├── Select
│ │ │ ├── HiddenOption.tsx
│ │ │ ├── OptionContext.ts
│ │ │ ├── Select.md
│ │ │ ├── Select.module.scss
│ │ │ ├── Select.spec.ts
│ │ │ ├── Select.tsx
│ │ │ ├── SelectContext.tsx
│ │ │ └── SelectNative.tsx
│ │ ├── SelectionStore
│ │ │ ├── SelectionStore.md
│ │ │ ├── SelectionStore.tsx
│ │ │ └── SelectionStoreNative.tsx
│ │ ├── Slider
│ │ │ ├── Slider.md
│ │ │ ├── Slider.module.scss
│ │ │ ├── Slider.spec.ts
│ │ │ ├── Slider.tsx
│ │ │ └── SliderNative.tsx
│ │ ├── Slot
│ │ │ ├── Slot.md
│ │ │ ├── Slot.spec.ts
│ │ │ └── Slot.ts
│ │ ├── SlotItem.tsx
│ │ ├── SpaceFiller
│ │ │ ├── SpaceFiller.md
│ │ │ ├── SpaceFiller.module.scss
│ │ │ ├── SpaceFiller.spec.ts
│ │ │ ├── SpaceFiller.tsx
│ │ │ └── SpaceFillerNative.tsx
│ │ ├── Spinner
│ │ │ ├── Spinner.md
│ │ │ ├── Spinner.module.scss
│ │ │ ├── Spinner.spec.ts
│ │ │ ├── Spinner.tsx
│ │ │ └── SpinnerNative.tsx
│ │ ├── Splitter
│ │ │ ├── HSplitter.md
│ │ │ ├── HSplitter.spec.ts
│ │ │ ├── Splitter.md
│ │ │ ├── Splitter.module.scss
│ │ │ ├── Splitter.spec.ts
│ │ │ ├── Splitter.tsx
│ │ │ ├── SplitterNative.tsx
│ │ │ ├── utils.ts
│ │ │ ├── VSplitter.md
│ │ │ └── VSplitter.spec.ts
│ │ ├── Stack
│ │ │ ├── CHStack.md
│ │ │ ├── CHStack.spec.ts
│ │ │ ├── CVStack.md
│ │ │ ├── CVStack.spec.ts
│ │ │ ├── HStack.md
│ │ │ ├── HStack.spec.ts
│ │ │ ├── Stack.md
│ │ │ ├── Stack.module.scss
│ │ │ ├── Stack.spec.ts
│ │ │ ├── Stack.tsx
│ │ │ ├── StackNative.tsx
│ │ │ ├── VStack.md
│ │ │ └── VStack.spec.ts
│ │ ├── StickyBox
│ │ │ ├── StickyBox.md
│ │ │ ├── StickyBox.module.scss
│ │ │ ├── StickyBox.tsx
│ │ │ └── StickyBoxNative.tsx
│ │ ├── Switch
│ │ │ ├── Switch.md
│ │ │ ├── Switch.spec.ts
│ │ │ └── Switch.tsx
│ │ ├── Table
│ │ │ ├── doc-resources
│ │ │ │ └── list-component-data.js
│ │ │ ├── react-table-config.d.ts
│ │ │ ├── Table.md
│ │ │ ├── Table.module.scss
│ │ │ ├── Table.spec.ts
│ │ │ ├── Table.tsx
│ │ │ ├── TableNative.tsx
│ │ │ └── useRowSelection.tsx
│ │ ├── TableOfContents
│ │ │ ├── TableOfContents.module.scss
│ │ │ ├── TableOfContents.spec.ts
│ │ │ ├── TableOfContents.tsx
│ │ │ └── TableOfContentsNative.tsx
│ │ ├── Tabs
│ │ │ ├── TabContext.tsx
│ │ │ ├── TabItem.md
│ │ │ ├── TabItem.tsx
│ │ │ ├── TabItemNative.tsx
│ │ │ ├── Tabs.md
│ │ │ ├── Tabs.module.scss
│ │ │ ├── Tabs.spec.ts
│ │ │ ├── Tabs.tsx
│ │ │ └── TabsNative.tsx
│ │ ├── Text
│ │ │ ├── Text.md
│ │ │ ├── Text.module.scss
│ │ │ ├── Text.spec.ts
│ │ │ ├── Text.tsx
│ │ │ └── TextNative.tsx
│ │ ├── TextArea
│ │ │ ├── TextArea.md
│ │ │ ├── TextArea.module.scss
│ │ │ ├── TextArea.spec.ts
│ │ │ ├── TextArea.tsx
│ │ │ ├── TextAreaNative.tsx
│ │ │ ├── TextAreaResizable.tsx
│ │ │ └── useComposedRef.ts
│ │ ├── TextBox
│ │ │ ├── TextBox.md
│ │ │ ├── TextBox.module.scss
│ │ │ ├── TextBox.spec.ts
│ │ │ ├── TextBox.tsx
│ │ │ └── TextBoxNative.tsx
│ │ ├── Theme
│ │ │ ├── NotificationToast.tsx
│ │ │ ├── Theme.md
│ │ │ ├── Theme.module.scss
│ │ │ ├── Theme.spec.ts
│ │ │ ├── Theme.tsx
│ │ │ └── ThemeNative.tsx
│ │ ├── TimeInput
│ │ │ ├── TimeInput.md
│ │ │ ├── TimeInput.module.scss
│ │ │ ├── TimeInput.spec.ts
│ │ │ ├── TimeInput.tsx
│ │ │ ├── TimeInputNative.tsx
│ │ │ └── utils.ts
│ │ ├── Timer
│ │ │ ├── Timer.md
│ │ │ ├── Timer.spec.ts
│ │ │ ├── Timer.tsx
│ │ │ └── TimerNative.tsx
│ │ ├── Toggle
│ │ │ ├── Toggle.module.scss
│ │ │ └── Toggle.tsx
│ │ ├── ToneChangerButton
│ │ │ ├── ToneChangerButton.md
│ │ │ ├── ToneChangerButton.spec.ts
│ │ │ └── ToneChangerButton.tsx
│ │ ├── ToneSwitch
│ │ │ ├── ToneSwitch.md
│ │ │ ├── ToneSwitch.module.scss
│ │ │ ├── ToneSwitch.spec.ts
│ │ │ ├── ToneSwitch.tsx
│ │ │ └── ToneSwitchNative.tsx
│ │ ├── Tooltip
│ │ │ ├── Tooltip.md
│ │ │ ├── Tooltip.module.scss
│ │ │ ├── Tooltip.spec.ts
│ │ │ ├── Tooltip.tsx
│ │ │ └── TooltipNative.tsx
│ │ ├── Tree
│ │ │ ├── testData.ts
│ │ │ ├── Tree-dynamic.spec.ts
│ │ │ ├── Tree-icons.spec.ts
│ │ │ ├── Tree.md
│ │ │ ├── Tree.spec.ts
│ │ │ ├── TreeComponent.module.scss
│ │ │ ├── TreeComponent.tsx
│ │ │ └── TreeNative.tsx
│ │ ├── TreeDisplay
│ │ │ ├── TreeDisplay.md
│ │ │ ├── TreeDisplay.module.scss
│ │ │ ├── TreeDisplay.tsx
│ │ │ └── TreeDisplayNative.tsx
│ │ ├── ValidationSummary
│ │ │ ├── ValidationSummary.module.scss
│ │ │ └── ValidationSummary.tsx
│ │ └── VisuallyHidden.tsx
│ ├── components-core
│ │ ├── abstractions
│ │ │ ├── ComponentRenderer.ts
│ │ │ ├── LoaderRenderer.ts
│ │ │ ├── standalone.ts
│ │ │ └── treeAbstractions.ts
│ │ ├── action
│ │ │ ├── actions.ts
│ │ │ ├── APICall.tsx
│ │ │ ├── FileDownloadAction.tsx
│ │ │ ├── FileUploadAction.tsx
│ │ │ ├── NavigateAction.tsx
│ │ │ └── TimedAction.tsx
│ │ ├── ApiBoundComponent.tsx
│ │ ├── appContext
│ │ │ ├── date-functions.ts
│ │ │ ├── math-function.ts
│ │ │ └── misc-utils.ts
│ │ ├── AppContext.tsx
│ │ ├── behaviors
│ │ │ ├── Behavior.tsx
│ │ │ └── CoreBehaviors.tsx
│ │ ├── component-hooks.ts
│ │ ├── ComponentDecorator.tsx
│ │ ├── ComponentViewer.tsx
│ │ ├── CompoundComponent.tsx
│ │ ├── constants.ts
│ │ ├── DebugViewProvider.tsx
│ │ ├── descriptorHelper.ts
│ │ ├── devtools
│ │ │ ├── InspectorDialog.module.scss
│ │ │ ├── InspectorDialog.tsx
│ │ │ └── InspectorDialogVisibilityContext.tsx
│ │ ├── EngineError.ts
│ │ ├── event-handlers.ts
│ │ ├── InspectorButton.module.scss
│ │ ├── InspectorContext.tsx
│ │ ├── interception
│ │ │ ├── abstractions.ts
│ │ │ ├── ApiInterceptor.ts
│ │ │ ├── ApiInterceptorProvider.tsx
│ │ │ ├── apiInterceptorWorker.ts
│ │ │ ├── Backend.ts
│ │ │ ├── Errors.ts
│ │ │ ├── IndexedDb.ts
│ │ │ ├── initMock.ts
│ │ │ ├── InMemoryDb.ts
│ │ │ ├── ReadonlyCollection.ts
│ │ │ └── useApiInterceptorContext.tsx
│ │ ├── loader
│ │ │ ├── ApiLoader.tsx
│ │ │ ├── DataLoader.tsx
│ │ │ ├── ExternalDataLoader.tsx
│ │ │ ├── Loader.tsx
│ │ │ ├── MockLoaderRenderer.tsx
│ │ │ └── PageableLoader.tsx
│ │ ├── LoaderComponent.tsx
│ │ ├── markup-check.ts
│ │ ├── parts.ts
│ │ ├── renderers.ts
│ │ ├── rendering
│ │ │ ├── AppContent.tsx
│ │ │ ├── AppRoot.tsx
│ │ │ ├── AppWrapper.tsx
│ │ │ ├── buildProxy.ts
│ │ │ ├── collectFnVarDeps.ts
│ │ │ ├── ComponentAdapter.tsx
│ │ │ ├── ComponentWrapper.tsx
│ │ │ ├── Container.tsx
│ │ │ ├── containers.ts
│ │ │ ├── ContainerWrapper.tsx
│ │ │ ├── ErrorBoundary.module.scss
│ │ │ ├── ErrorBoundary.tsx
│ │ │ ├── InvalidComponent.module.scss
│ │ │ ├── InvalidComponent.tsx
│ │ │ ├── nodeUtils.ts
│ │ │ ├── reducer.ts
│ │ │ ├── renderChild.tsx
│ │ │ ├── StandaloneComponent.tsx
│ │ │ ├── StateContainer.tsx
│ │ │ ├── UnknownComponent.module.scss
│ │ │ ├── UnknownComponent.tsx
│ │ │ └── valueExtractor.ts
│ │ ├── reportEngineError.ts
│ │ ├── RestApiProxy.ts
│ │ ├── script-runner
│ │ │ ├── asyncProxy.ts
│ │ │ ├── AttributeValueParser.ts
│ │ │ ├── bannedFunctions.ts
│ │ │ ├── BindingTreeEvaluationContext.ts
│ │ │ ├── eval-tree-async.ts
│ │ │ ├── eval-tree-common.ts
│ │ │ ├── eval-tree-sync.ts
│ │ │ ├── ParameterParser.ts
│ │ │ ├── process-statement-async.ts
│ │ │ ├── process-statement-common.ts
│ │ │ ├── process-statement-sync.ts
│ │ │ ├── ScriptingSourceTree.ts
│ │ │ ├── simplify-expression.ts
│ │ │ ├── statement-queue.ts
│ │ │ └── visitors.ts
│ │ ├── StandaloneApp.tsx
│ │ ├── StandaloneExtensionManager.ts
│ │ ├── TableOfContentsContext.tsx
│ │ ├── theming
│ │ │ ├── _themes.scss
│ │ │ ├── component-layout-resolver.ts
│ │ │ ├── extendThemeUtils.ts
│ │ │ ├── hvar.ts
│ │ │ ├── layout-resolver.ts
│ │ │ ├── parse-layout-props.ts
│ │ │ ├── StyleContext.tsx
│ │ │ ├── StyleRegistry.ts
│ │ │ ├── ThemeContext.tsx
│ │ │ ├── ThemeProvider.tsx
│ │ │ ├── themes
│ │ │ │ ├── base-utils.ts
│ │ │ │ ├── palette.ts
│ │ │ │ ├── root.ts
│ │ │ │ ├── solid.ts
│ │ │ │ ├── theme-colors.ts
│ │ │ │ └── xmlui.ts
│ │ │ ├── themeVars.module.scss
│ │ │ ├── themeVars.ts
│ │ │ ├── transformThemeVars.ts
│ │ │ └── utils.ts
│ │ ├── utils
│ │ │ ├── actionUtils.ts
│ │ │ ├── audio-utils.ts
│ │ │ ├── base64-utils.ts
│ │ │ ├── compound-utils.ts
│ │ │ ├── css-utils.ts
│ │ │ ├── DataLoaderQueryKeyGenerator.ts
│ │ │ ├── date-utils.ts
│ │ │ ├── extractParam.ts
│ │ │ ├── hooks.tsx
│ │ │ ├── LruCache.ts
│ │ │ ├── mergeProps.ts
│ │ │ ├── misc.ts
│ │ │ ├── request-params.ts
│ │ │ ├── statementUtils.ts
│ │ │ └── treeUtils.ts
│ │ └── xmlui-parser.ts
│ ├── index-standalone.ts
│ ├── index.scss
│ ├── index.ts
│ ├── language-server
│ │ ├── server-common.ts
│ │ ├── server-web-worker.ts
│ │ ├── server.ts
│ │ ├── services
│ │ │ ├── common
│ │ │ │ ├── docs-generation.ts
│ │ │ │ ├── lsp-utils.ts
│ │ │ │ ├── metadata-utils.ts
│ │ │ │ └── syntax-node-utilities.ts
│ │ │ ├── completion.ts
│ │ │ ├── diagnostic.ts
│ │ │ ├── format.ts
│ │ │ └── hover.ts
│ │ └── xmlui-metadata-generated.js
│ ├── logging
│ │ ├── LoggerContext.tsx
│ │ ├── LoggerInitializer.tsx
│ │ ├── LoggerService.ts
│ │ └── xmlui.ts
│ ├── logo.svg
│ ├── parsers
│ │ ├── common
│ │ │ ├── GenericToken.ts
│ │ │ ├── InputStream.ts
│ │ │ └── utils.ts
│ │ ├── scripting
│ │ │ ├── code-behind-collect.ts
│ │ │ ├── Lexer.ts
│ │ │ ├── modules.ts
│ │ │ ├── Parser.ts
│ │ │ ├── ParserError.ts
│ │ │ ├── ScriptingNodeTypes.ts
│ │ │ ├── TokenTrait.ts
│ │ │ ├── TokenType.ts
│ │ │ └── tree-visitor.ts
│ │ ├── style-parser
│ │ │ ├── errors.ts
│ │ │ ├── source-tree.ts
│ │ │ ├── StyleInputStream.ts
│ │ │ ├── StyleLexer.ts
│ │ │ ├── StyleParser.ts
│ │ │ └── tokens.ts
│ │ └── xmlui-parser
│ │ ├── CharacterCodes.ts
│ │ ├── diagnostics.ts
│ │ ├── fileExtensions.ts
│ │ ├── index.ts
│ │ ├── lint.ts
│ │ ├── parser.ts
│ │ ├── ParserError.ts
│ │ ├── scanner.ts
│ │ ├── syntax-kind.ts
│ │ ├── syntax-node.ts
│ │ ├── transform.ts
│ │ ├── utils.ts
│ │ ├── xmlui-serializer.ts
│ │ └── xmlui-tree.ts
│ ├── react-app-env.d.ts
│ ├── syntax
│ │ ├── monaco
│ │ │ ├── grammar.monacoLanguage.ts
│ │ │ ├── index.ts
│ │ │ ├── xmlui-dark.ts
│ │ │ ├── xmlui-light.ts
│ │ │ └── xmluiscript.monacoLanguage.ts
│ │ └── textMate
│ │ ├── index.ts
│ │ ├── xmlui-dark.json
│ │ ├── xmlui-light.json
│ │ ├── xmlui.json
│ │ └── xmlui.tmLanguage.json
│ ├── testing
│ │ ├── assertions.ts
│ │ ├── component-test-helpers.ts
│ │ ├── ComponentDrivers.ts
│ │ ├── drivers
│ │ │ ├── DateInputDriver.ts
│ │ │ ├── index.ts
│ │ │ ├── ModalDialogDriver.ts
│ │ │ ├── NumberBoxDriver.ts
│ │ │ ├── TextBoxDriver.ts
│ │ │ ├── TimeInputDriver.ts
│ │ │ ├── TimerDriver.ts
│ │ │ └── TreeDriver.ts
│ │ ├── fixtures.ts
│ │ ├── index.ts
│ │ ├── infrastructure
│ │ │ ├── index.html
│ │ │ ├── main.tsx
│ │ │ ├── public
│ │ │ │ ├── mockServiceWorker.js
│ │ │ │ ├── resources
│ │ │ │ │ ├── bell.svg
│ │ │ │ │ ├── box.svg
│ │ │ │ │ ├── doc.svg
│ │ │ │ │ ├── eye.svg
│ │ │ │ │ ├── flower-640x480.jpg
│ │ │ │ │ ├── sun.svg
│ │ │ │ │ ├── test-image-100x100.jpg
│ │ │ │ │ └── txt.svg
│ │ │ │ └── serve.json
│ │ │ └── TestBed.tsx
│ │ └── themed-app-test-helpers.ts
│ └── vite-env.d.ts
├── tests
│ ├── components
│ │ ├── CodeBlock
│ │ │ └── hightlight-code.test.ts
│ │ ├── playground-pattern.test.ts
│ │ └── Tree
│ │ └── Tree-states.test.ts
│ ├── components-core
│ │ ├── abstractions
│ │ │ └── treeAbstractions.test.ts
│ │ ├── container
│ │ │ └── buildProxy.test.ts
│ │ ├── interception
│ │ │ ├── orderBy.test.ts
│ │ │ ├── ReadOnlyCollection.test.ts
│ │ │ └── request-param-converter.test.ts
│ │ ├── scripts-runner
│ │ │ ├── AttributeValueParser.test.ts
│ │ │ ├── eval-tree-arrow-async.test.ts
│ │ │ ├── eval-tree-arrow.test.ts
│ │ │ ├── eval-tree-func-decl-async.test.ts
│ │ │ ├── eval-tree-func-decl.test.ts
│ │ │ ├── eval-tree-pre-post.test.ts
│ │ │ ├── eval-tree-regression.test.ts
│ │ │ ├── eval-tree.test.ts
│ │ │ ├── function-proxy.test.ts
│ │ │ ├── parser-regression.test.ts
│ │ │ ├── process-event.test.ts
│ │ │ ├── process-function.test.ts
│ │ │ ├── process-implicit-context.test.ts
│ │ │ ├── process-statement-asgn.test.ts
│ │ │ ├── process-statement-destruct.test.ts
│ │ │ ├── process-statement-regs.test.ts
│ │ │ ├── process-statement-sync.test.ts
│ │ │ ├── process-statement.test.ts
│ │ │ ├── process-switch-sync.test.ts
│ │ │ ├── process-switch.test.ts
│ │ │ ├── process-try-sync.test.ts
│ │ │ ├── process-try.test.ts
│ │ │ └── test-helpers.ts
│ │ ├── test-metadata-handler.ts
│ │ ├── theming
│ │ │ ├── border-segments.test.ts
│ │ │ ├── component-layout.resolver.test.ts
│ │ │ ├── layout-property-parser.test.ts
│ │ │ ├── layout-resolver.test.ts
│ │ │ ├── layout-resolver2.test.ts
│ │ │ ├── layout-vp-override.test.ts
│ │ │ └── padding-segments.test.ts
│ │ └── utils
│ │ ├── date-utils.test.ts
│ │ ├── format-human-elapsed-time.test.ts
│ │ └── LruCache.test.ts
│ ├── language-server
│ │ ├── completion.test.ts
│ │ ├── format.test.ts
│ │ ├── hover.test.ts
│ │ └── mockData.ts
│ └── parsers
│ ├── common
│ │ └── input-stream.test.ts
│ ├── markdown
│ │ └── parse-binding-expression.test.ts
│ ├── parameter-parser.test.ts
│ ├── paremeter-parser.test.ts
│ ├── scripting
│ │ ├── eval-tree-arrow.test.ts
│ │ ├── eval-tree-pre-post.test.ts
│ │ ├── eval-tree.test.ts
│ │ ├── function-proxy.test.ts
│ │ ├── lexer-literals.test.ts
│ │ ├── lexer-misc.test.ts
│ │ ├── module-parse.test.ts
│ │ ├── parser-arrow.test.ts
│ │ ├── parser-assignments.test.ts
│ │ ├── parser-binary.test.ts
│ │ ├── parser-destructuring.test.ts
│ │ ├── parser-errors.test.ts
│ │ ├── parser-expressions.test.ts
│ │ ├── parser-function.test.ts
│ │ ├── parser-literals.test.ts
│ │ ├── parser-primary.test.ts
│ │ ├── parser-regex.test.ts
│ │ ├── parser-statements.test.ts
│ │ ├── parser-unary.test.ts
│ │ ├── process-event.test.ts
│ │ ├── process-implicit-context.test.ts
│ │ ├── process-statement-asgn.test.ts
│ │ ├── process-statement-destruct.test.ts
│ │ ├── process-statement-regs.test.ts
│ │ ├── process-statement-sync.test.ts
│ │ ├── process-statement.test.ts
│ │ ├── process-switch-sync.test.ts
│ │ ├── process-switch.test.ts
│ │ ├── process-try-sync.test.ts
│ │ ├── process-try.test.ts
│ │ ├── simplify-expression.test.ts
│ │ ├── statement-hooks.test.ts
│ │ └── test-helpers.ts
│ ├── style-parser
│ │ ├── generateHvarChain.test.ts
│ │ ├── parseHVar.test.ts
│ │ ├── parser.test.ts
│ │ └── tokens.test.ts
│ └── xmlui
│ ├── lint.test.ts
│ ├── parser.test.ts
│ ├── scanner.test.ts
│ ├── transform.attr.test.ts
│ ├── transform.circular.test.ts
│ ├── transform.element.test.ts
│ ├── transform.errors.test.ts
│ ├── transform.escape.test.ts
│ ├── transform.regression.test.ts
│ ├── transform.script.test.ts
│ ├── transform.test.ts
│ └── xmlui.ts
├── tests-e2e
│ ├── api-bound-component-regression.spec.ts
│ ├── api-call-as-extracted-component.spec.ts
│ ├── assign-to-object-or-array-regression.spec.ts
│ ├── binding-regression.spec.ts
│ ├── children-as-template-context-vars.spec.ts
│ ├── compound-component.spec.ts
│ ├── context-vars-regression.spec.ts
│ ├── data-bindings.spec.ts
│ ├── datasource-and-api-usage-in-var.spec.ts
│ ├── datasource-direct-binding.spec.ts
│ ├── datasource-onLoaded-regression.spec.ts
│ ├── modify-array-item-regression.spec.ts
│ ├── namespaces.spec.ts
│ ├── push-to-array-regression.spec.ts
│ ├── screen-breakpoints.spec.ts
│ ├── scripting.spec.ts
│ ├── state-scope-in-pages.spec.ts
│ └── state-var-scopes.spec.ts
├── tsconfig.json
├── tsdown.config.ts
├── vite.config.ts
└── vitest.config.ts
```
# Files
--------------------------------------------------------------------------------
/xmlui/src/components/DatePicker/DatePicker.module.scss:
--------------------------------------------------------------------------------
```scss
@use "../../components-core/theming/themes" as t;
// --- This code snippet is required to collect the theme variables used in this module
$themeVars: (
);
@function createThemeVar($componentVariable) {
$themeVars: t.appendThemeVar($themeVars, $componentVariable) !global;
@return t.getThemeVar($themeVars, $componentVariable);
}
$componentName: "DatePicker";
$themeVars: t.composePaddingVars($themeVars, $componentName);
// Variables for default variant
$borderRadius-DatePicker--default: createThemeVar("Input:borderRadius-#{$componentName}--default");
$borderColor-DatePicker--default: createThemeVar("Input:borderColor-#{$componentName}--default");
$borderWidth-DatePicker--default: createThemeVar("Input:borderWidth-#{$componentName}--default");
$borderStyle-DatePicker--default: createThemeVar("Input:borderStyle-#{$componentName}--default");
$backgroundColor-DatePicker--default: createThemeVar("Input:backgroundColor-#{$componentName}--default");
$boxShadow-DatePicker--default: createThemeVar("Input:boxShadow-#{$componentName}--default");
$textColor-DatePicker--default: createThemeVar("Input:textColor-#{$componentName}--default");
$borderColor-DatePicker--default--hover: createThemeVar("Input:borderColor-#{$componentName}--default--hover");
$backgroundColor-DatePicker--default--hover: createThemeVar("Input:backgroundColor-#{$componentName}--default--hover");
$boxShadow-DatePicker--default--hover: createThemeVar("Input:boxShadow-#{$componentName}--default--hover");
$textColor-DatePicker--default--hover: createThemeVar("Input:textColor-#{$componentName}--default--hover");
$outlineWidth-DatePicker--default--focus: createThemeVar("Input:outlineWidth-#{$componentName}--default--focus");
$outlineColor-DatePicker--default--focus: createThemeVar("Input:outlineColor-#{$componentName}--default--focus");
$outlineStyle-DatePicker--default--focus: createThemeVar("Input:outlineStyle-#{$componentName}--default--focus");
$outlineOffset-DatePicker--default--focus: createThemeVar("Input:outlineOffset-#{$componentName}--default--focus");
$textColor-placeholder-DatePicker--default: createThemeVar("Input:textColor-placeholder-#{$componentName}--default");
$fontSize-placeholder-DatePicker--default: createThemeVar("Input:fontSize-placeholder-#{$componentName}--default");
$color-adornment-DatePicker--default: createThemeVar("Input:color-adornment-#{$componentName}--default");
// Variables for error variant
$borderRadius-DatePicker--error: createThemeVar("Input:borderRadius-#{$componentName}--error");
$borderColor-DatePicker--error: createThemeVar("Input:borderColor-#{$componentName}--error");
$borderWidth-DatePicker--error: createThemeVar("Input:borderWidth-#{$componentName}--error");
$borderStyle-DatePicker--error: createThemeVar("Input:borderStyle-#{$componentName}--error");
$backgroundColor-DatePicker--error: createThemeVar("Input:backgroundColor-#{$componentName}--error");
$boxShadow-DatePicker--error: createThemeVar("Input:boxShadow-#{$componentName}--error");
$textColor-DatePicker--error: createThemeVar("Input:textColor-#{$componentName}--error");
$borderColor-DatePicker--error--hover: createThemeVar("Input:borderColor-#{$componentName}--error--hover");
$backgroundColor-DatePicker--error--hover: createThemeVar("Input:backgroundColor-#{$componentName}--error--hover");
$boxShadow-DatePicker--error--hover: createThemeVar("Input:boxShadow-#{$componentName}--error--hover");
$textColor-DatePicker--error--hover: createThemeVar("Input:textColor-#{$componentName}--error--hover");
$outlineWidth-DatePicker--error--focus: createThemeVar("Input:outlineWidth-#{$componentName}--error--focus");
$outlineColor-DatePicker--error--focus: createThemeVar("Input:outlineColor-#{$componentName}--error--focus");
$outlineStyle-DatePicker--error--focus: createThemeVar("Input:outlineStyle-#{$componentName}--error--focus");
$outlineOffset-DatePicker--error--focus: createThemeVar("Input:outlineOffset-#{$componentName}--error--focus");
$textColor-placeholder-DatePicker--error: createThemeVar("Input:textColor-placeholder-#{$componentName}--error");
$fontSize-placeholder-DatePicker--error: createThemeVar("Input:fontSize-placeholder-#{$componentName}--error");
$color-adornment-DatePicker--error: createThemeVar("Input:color-adornment-#{$componentName}--error");
// Variables for warning variant
$borderRadius-DatePicker--warning: createThemeVar("Input:borderRadius-#{$componentName}--warning");
$borderColor-DatePicker--warning: createThemeVar("Input:borderColor-#{$componentName}--warning");
$borderWidth-DatePicker--warning: createThemeVar("Input:borderWidth-#{$componentName}--warning");
$borderStyle-DatePicker--warning: createThemeVar("Input:borderStyle-#{$componentName}--warning");
$backgroundColor-DatePicker--warning: createThemeVar("Input:backgroundColor-#{$componentName}--warning");
$boxShadow-DatePicker--warning: createThemeVar("Input:boxShadow-#{$componentName}--warning");
$textColor-DatePicker--warning: createThemeVar("Input:textColor-#{$componentName}--warning");
$borderColor-DatePicker--warning--hover: createThemeVar("Input:borderColor-#{$componentName}--warning--hover");
$backgroundColor-DatePicker--warning--hover: createThemeVar("Input:backgroundColor-#{$componentName}--warning--hover");
$boxShadow-DatePicker--warning--hover: createThemeVar("Input:boxShadow-#{$componentName}--warning--hover");
$textColor-DatePicker--warning--hover: createThemeVar("Input:textColor-#{$componentName}--warning--hover");
$outlineWidth-DatePicker--warning--focus: createThemeVar("Input:outlineWidth-#{$componentName}--warning--focus");
$outlineColor-DatePicker--warning--focus: createThemeVar("Input:outlineColor-#{$componentName}--warning--focus");
$outlineStyle-DatePicker--warning--focus: createThemeVar("Input:outlineStyle-#{$componentName}--warning--focus");
$outlineOffset-DatePicker--warning--focus: createThemeVar("Input:outlineOffset-#{$componentName}--warning--focus");
$textColor-placeholder-DatePicker--warning: createThemeVar("Input:textColor-placeholder-#{$componentName}--warning");
$fontSize-placeholder-DatePicker--warning: createThemeVar("Input:fontSize-placeholder-#{$componentName}--warning");
$color-adornment-DatePicker--warning: createThemeVar("Input:color-adornment-#{$componentName}--warning");
// Variables for success variant
$borderRadius-DatePicker--success: createThemeVar("Input:borderRadius-#{$componentName}--success");
$borderColor-DatePicker--success: createThemeVar("Input:borderColor-#{$componentName}--success");
$borderWidth-DatePicker--success: createThemeVar("Input:borderWidth-#{$componentName}--success");
$borderStyle-DatePicker--success: createThemeVar("Input:borderStyle-#{$componentName}--success");
$backgroundColor-DatePicker--success: createThemeVar("Input:backgroundColor-#{$componentName}--success");
$boxShadow-DatePicker--success: createThemeVar("Input:boxShadow-#{$componentName}--success");
$textColor-DatePicker--success: createThemeVar("Input:textColor-#{$componentName}--success");
$borderColor-DatePicker--success--hover: createThemeVar("Input:borderColor-#{$componentName}--success--hover");
$backgroundColor-DatePicker--success--hover: createThemeVar("Input:backgroundColor-#{$componentName}--success--hover");
$boxShadow-DatePicker--success--hover: createThemeVar("Input:boxShadow-#{$componentName}--success--hover");
$textColor-DatePicker--success--hover: createThemeVar("Input:textColor-#{$componentName}--success--hover");
$outlineWidth-DatePicker--success--focus: createThemeVar("Input:outlineWidth-#{$componentName}--success--focus");
$outlineColor-DatePicker--success--focus: createThemeVar("Input:outlineColor-#{$componentName}--success--focus");
$outlineStyle-DatePicker--success--focus: createThemeVar("Input:outlineStyle-#{$componentName}--success--focus");
$outlineOffset-DatePicker--success--focus: createThemeVar("Input:outlineOffset-#{$componentName}--success--focus");
$textColor-placeholder-DatePicker--success: createThemeVar("Input:textColor-placeholder-#{$componentName}--success");
$fontSize-placeholder-DatePicker--success: createThemeVar("Input:fontSize-placeholder-#{$componentName}--success");
$color-adornment-DatePicker--success: createThemeVar("Input:color-adornment-#{$componentName}--success");
// Variables for @layer section
$fontSize-DatePicker: createThemeVar("Input:fontSize-DatePicker");
$backgroundColor-DatePicker--disabled: createThemeVar("Input:backgroundColor-DatePicker--disabled");
$textColor-DatePicker--disabled: createThemeVar("Input:textColor-DatePicker--disabled");
$borderColor-DatePicker--disabled: createThemeVar("Input:borderColor-DatePicker--disabled");
$boxShadow-menu-DatePicker: createThemeVar("Input:boxShadow-menu-DatePicker");
$backgroundColor-menu-DatePicker: createThemeVar("Input:backgroundColor-menu-DatePicker");
$borderRadius-menu-DatePicker: createThemeVar("Input:borderRadius-menu-DatePicker");
// --- CSS properties of a particular Select variant
@mixin variant($variantName) {
border-radius: createThemeVar("Input:borderRadius-#{$componentName}--#{$variantName}");
border-color: createThemeVar("Input:borderColor-#{$componentName}--#{$variantName}");
border-width: createThemeVar("Input:borderWidth-#{$componentName}--#{$variantName}");
border-style: createThemeVar("Input:borderStyle-#{$componentName}--#{$variantName}");
background-color: createThemeVar("Input:backgroundColor-#{$componentName}--#{$variantName}");
box-shadow: createThemeVar("Input:boxShadow-#{$componentName}--#{$variantName}");
color: createThemeVar("Input:textColor-#{$componentName}--#{$variantName}");
&:hover {
border-color: createThemeVar("Input:borderColor-#{$componentName}--#{$variantName}--hover");
background-color: createThemeVar("Input:backgroundColor-#{$componentName}--#{$variantName}--hover"
);
box-shadow: createThemeVar("Input:boxShadow-#{$componentName}--#{$variantName}--hover");
color: createThemeVar("Input:textColor-#{$componentName}--#{$variantName}--hover");
}
&:focus {
outline-width: createThemeVar("Input:outlineWidth-#{$componentName}--#{$variantName}--focus");
outline-color: createThemeVar("Input:outlineColor-#{$componentName}--#{$variantName}--focus");
outline-style: createThemeVar("Input:outlineStyle-#{$componentName}--#{$variantName}--focus");
outline-offset: createThemeVar("Input:outlineOffset-#{$componentName}--#{$variantName}--focus");
}
.placeholder {
color: createThemeVar("Input:textColor-placeholder-#{$componentName}--#{$variantName}");
font-size: createThemeVar("Input:fontSize-placeholder-#{$componentName}--#{$variantName}");
}
.adornment {
color: createThemeVar("Input:color-adornment-#{$componentName}--#{$variantName}");
}
}
$rdp-accent-color: createThemeVar("Input:backgroundColor-item-DatePicker--active");
$backgroundColor-item-DatePicker--hover: createThemeVar("Input:backgroundColor-item-DatePicker--hover"
);
$rdp-selected-color: createThemeVar("Input:textColor-value-DatePicker");
$rdp-cell-size: 40px;
$rdp-caption-font-size: 18px;
$rdp-outline: 2px solid t.useVar($rdp-accent-color);
$rdp-outline-selected: 3px solid t.useVar($rdp-accent-color);
$minHeight: createThemeVar("Input:minHeight-#{$componentName}");
$borderColor-selectedItem-DatePicker: createThemeVar("Input:borderColor-selectedItem-DatePicker");
@layer components {
.datePicker {
display: flex;
align-items: center;
cursor: pointer;
flex-direction: row;
width: 100%;
min-height: $minHeight;
border-width: 1px;
border-style: solid;
transition: background-color ease-in 0.1s;
overflow: hidden;
gap: t.$space-2;
font-size: $fontSize-DatePicker;
@include t.paddingVars($themeVars, $componentName);
@include variant("default");
&.error {
@include variant("error");
}
&.warning {
@include variant("warning");
}
&.valid {
@include variant("success");
}
&.disabled {
cursor: not-allowed;
background-color: $backgroundColor-DatePicker--disabled;
color: $textColor-DatePicker--disabled;
border-color: $borderColor-DatePicker--disabled;
.indicator,
.datePickerInput {
cursor: not-allowed;
}
}
}
.inlinePickerMenu {
padding: 0;
overflow-y: auto;
width: 100%;
max-width: fit-content;
height: fit-content;
@include variant("default");
&.error {
@include variant("error");
}
&.warning {
@include variant("warning");
}
&.valid {
@include variant("success");
}
}
.datePickerMenu {
padding: t.$space-4;
overflow-y: auto;
height: fit-content;
box-shadow: $boxShadow-menu-DatePicker;
background-color: $backgroundColor-menu-DatePicker;
border-radius: $borderRadius-menu-DatePicker;
border: 1px solid $rdp-accent-color;
}
/* Variables declaration */
/* prettier-ignore */
.root {
--rdp-day-height: 44px;
/* The height of the day cells. */
--rdp-day-width: 44px;
/* The width of the day cells. */
--rdp-day_button-border-radius: 100%;
/* The border radius of the day cells. */
--rdp-day_button-border: 2px solid transparent;
/* The border of the day cells. */
--rdp-day_button-height: 42px;
/* The height of the day cells. */
--rdp-day_button-width: 42px;
/* The width of the day cells. */
--rdp-disabled-opacity: 0.5;
/* The opacity of the disabled days. */
--rdp-outside-opacity: 0.75;
/* The opacity of the days outside the current month. */
--rdp-dropdown-gap: 0.5rem;
/* The gap between the dropdowns used in the month captons. */
--rdp-months-gap: 2rem;
/* The gap between the months in the multi-month view. */
--rdp-nav_button-disabled-opacity: 0.5;
/* The opacity of the disabled navigation buttons. */
--rdp-nav_button-height: 2.25rem;
/* The height of the navigation buttons. */
--rdp-nav_button-width: 2.25rem;
/* The width of the navigation buttons. */
--rdp-nav-height: 2.75rem;
/* The height of the navigation bar. */
--rdp-range_middle-color: inherit;
/* The color of the range text. */
--rdp-range_start-color: white;
/* The color of the range text. */
--rdp-range_start-background: linear-gradient(var(--rdp-gradient-direction), transparent 50%, $backgroundColor-item-DatePicker--hover 50%);
/* Used for the background of the start of the selected range. */
--rdp-range_start-date-background-color: $rdp-accent-color;
/* The background color of the date when at the start of the selected range. */
--rdp-range_end-background: linear-gradient(var(--rdp-gradient-direction), var(--rdp-range_middle-background-color) 50%, transparent 50%);
/* Used for the background of the end of the selected range. */
--rdp-range_end-color: white;
/* The color of the range text. */
--rdp-range_end-date-background-color: $rdp-accent-color;
/* The background color of the date when at the end of the selected range. */
--rdp-week_number-border-radius: 100%;
/* The border radius of the week number. */
--rdp-week_number-border: 2px solid transparent;
/* The border of the week number. */
--rdp-week_number-height: var(--rdp-day-height);
/* The height of the week number cells. */
--rdp-week_number-opacity: 0.75;
/* The opacity of the week number. */
--rdp-week_number-width: var(--rdp-day-width);
/* The width of the week number cells. */
--rdp-weeknumber-text-align: center;
/* The text alignment of the weekday cells. */
--rdp-weekday-opacity: 0.75;
/* The opacity of the weekday. */
--rdp-weekday-padding: 0.5rem 0rem;
/* The padding of the weekday. */
--rdp-weekday-text-align: center;
/* The text alignment of the weekday cells. */
--rdp-gradient-direction: 90deg;
--rdp-animation_duration: 0.3s;
--rdp-animation_timing: cubic-bezier(0.4, 0, 0.2, 1);
}
.root[dir="rtl"] {
--rdp-gradient-direction: -90deg;
}
.root[data-broadcast-calendar="true"] {
--rdp-outside-opacity: unset;
}
/* Root of the component. */
.root {
position: relative;
/* Required to position the navigation toolbar. */
box-sizing: border-box;
}
.root * {
box-sizing: border-box;
}
.day {
width: var(--rdp-day-width);
height: var(--rdp-day-height);
text-align: center;
}
.day_button {
background: none;
padding: 0;
margin: 0;
cursor: pointer;
font: inherit;
color: inherit;
justify-content: center;
align-items: center;
display: flex;
width: var(--rdp-day_button-width);
height: var(--rdp-day_button-height);
border: var(--rdp-day_button-border);
border-radius: var(--rdp-day_button-border-radius);
&:hover {
background-color: $backgroundColor-item-DatePicker--hover;
}
}
.day_button:disabled {
cursor: revert;
}
.caption_label {
z-index: 1;
position: relative;
display: inline-flex;
align-items: center;
padding-left: t.$space-2;
padding-right: t.$space-2;
white-space: nowrap;
border: 0;
}
.dropdown:focus-visible~.caption_label {
outline: 5px auto t.$outlineColor--focus;
}
.button_next,
.button_previous {
border: none;
background: none;
padding: 0;
margin: 0;
cursor: pointer;
font: inherit;
color: inherit;
-moz-appearance: none;
-webkit-appearance: none;
display: inline-flex;
align-items: center;
justify-content: center;
position: relative;
appearance: none;
width: var(--rdp-nav_button-width);
height: var(--rdp-nav_button-height);
&:hover {
background-color: $backgroundColor-item-DatePicker--hover;
}
}
.button_next:disabled,
.button_next[aria-disabled="true"],
.button_previous:disabled,
.button_previous[aria-disabled="true"] {
cursor: revert;
opacity: var(--rdp-nav_button-disabled-opacity);
}
.chevron {
display: inline-block;
fill: currentColor;
}
.root[dir="rtl"] .nav .chevron {
transform: rotate(180deg);
transform-origin: 50%;
}
.dropdowns {
position: relative;
display: inline-flex;
align-items: center;
gap: 0;
}
.dropdown {
z-index: 2;
/* Reset */
opacity: 0;
appearance: none;
position: absolute;
inset-block-start: 0;
inset-block-end: 0;
inset-inline-start: 0;
width: 100%;
margin: 0;
padding: 0;
cursor: inherit;
border: none;
line-height: inherit;
}
.dropdown_root {
position: relative;
display: inline-flex;
align-items: center;
padding: t.$space-1_5;
&:hover {
background-color: $backgroundColor-item-DatePicker--hover;
cursor: pointer;
}
}
.dropdown_root[data-disabled="true"] .chevron {
opacity: var(--rdp-disabled-opacity);
}
.month_caption {
display: flex;
align-content: center;
padding: t.$space-3 t.$space-3 t.$space-3 t.$space-1;
height: var(--rdp-nav-height);
font-weight: bold;
font-size: large;
}
.root[data-nav-layout="around"] .month,
.root[data-nav-layout="after"] .month {
position: relative;
}
.root[data-nav-layout="around"] .month_caption {
justify-content: center;
margin-inline-start: var(--rdp-nav_button-width);
margin-inline-end: var(--rdp-nav_button-width);
position: relative;
}
.root[data-nav-layout="around"] .button_previous {
position: absolute;
inset-inline-start: 0;
top: 0;
height: var(--rdp-nav-height);
display: inline-flex;
}
.root[data-nav-layout="around"] .button_next {
position: absolute;
inset-inline-end: 0;
top: 0;
height: var(--rdp-nav-height);
display: inline-flex;
justify-content: center;
}
.months {
position: relative;
display: flex;
flex-wrap: wrap;
gap: var(--rdp-months-gap);
max-width: fit-content;
}
.month_grid {
border-collapse: collapse;
}
.nav {
position: absolute;
inset-block-start: 0;
inset-inline-end: 0;
display: flex;
align-items: center;
height: var(--rdp-nav-height);
}
.weekday {
opacity: var(--rdp-weekday-opacity);
padding: var(--rdp-weekday-padding);
font-weight: 500;
font-size: smaller;
color: t.$textColor-secondary;
text-align: var(--rdp-weekday-text-align);
text-transform: var(--rdp-weekday-text-transform);
}
.week_number {
opacity: var(--rdp-week_number-opacity);
font-weight: 400;
font-size: small;
height: var(--rdp-week_number-height);
width: var(--rdp-week_number-width);
border: var(--rdp-week_number-border);
border-radius: var(--rdp-week_number-border-radius);
text-align: var(--rdp-weeknumber-text-align);
}
/* DAY MODIFIERS */
.today:not(.outside) {
color: t.$color-primary-500;
}
.selected {
font-weight: bold;
font-size: large;
}
.selected .day_button {
border: 2px solid $borderColor-selectedItem-DatePicker;
}
.outside {
opacity: var(--rdp-outside-opacity);
}
.disabled {
opacity: var(--rdp-disabled-opacity);
}
.hidden {
visibility: hidden;
color: var(--rdp-range_start-color);
}
.range_start {
background: var(--rdp-range_start-background);
}
.range_start .day_button {
background-color: var(--rdp-range_start-date-background-color);
color: var(--rdp-range_start-color);
}
.range_middle {
background-color: var(--rdp-range_middle-background-color);
}
.range_middle .day_button {
border-color: transparent;
border: unset;
border-radius: unset;
color: var(--rdp-range_middle-color);
}
.range_end {
background: var(--rdp-range_end-background);
color: var(--rdp-range_end-color);
}
.range_end .day_button {
color: var(--rdp-range_start-color);
background-color: var(--rdp-range_end-date-background-color);
}
.range_start.range_end {
background: revert;
}
.focusable {
cursor: pointer;
}
.datePickerValue {
display: flex;
flex: 1;
}
@keyframes rdp-slide_in_left {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(0);
}
}
@keyframes rdp-slide_in_right {
0% {
transform: translateX(100%);
}
100% {
transform: translateX(0);
}
}
@keyframes rdp-slide_out_left {
0% {
transform: translateX(0);
}
100% {
transform: translateX(-100%);
}
}
@keyframes rdp-slide_out_right {
0% {
transform: translateX(0);
}
100% {
transform: translateX(100%);
}
}
.weeks_before_enter {
animation: rdp-slide_in_left var(--rdp-animation_duration) var(--rdp-animation_timing) forwards;
}
.weeks_before_exit {
animation: rdp-slide_out_left var(--rdp-animation_duration) var(--rdp-animation_timing) forwards;
}
.weeks_after_enter {
animation: rdp-slide_in_right var(--rdp-animation_duration) var(--rdp-animation_timing) forwards;
}
.weeks_after_exit {
animation: rdp-slide_out_right var(--rdp-animation_duration) var(--rdp-animation_timing) forwards;
}
.root[dir="rtl"] .weeks_after_enter {
animation: rdp-slide_in_left var(--rdp-animation_duration) var(--rdp-animation_timing) forwards;
}
.root[dir="rtl"] .weeks_before_exit {
animation: rdp-slide_out_right var(--rdp-animation_duration) var(--rdp-animation_timing) forwards;
}
.root[dir="rtl"] .weeks_before_enter {
animation: rdp-slide_in_right var(--rdp-animation_duration) var(--rdp-animation_timing) forwards;
}
.root[dir="rtl"] .weeks_after_exit {
animation: rdp-slide_out_left var(--rdp-animation_duration) var(--rdp-animation_timing) forwards;
}
@keyframes rdp-fade_in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes rdp-fade_out {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
.caption_after_enter {
animation: rdp-fade_in var(--rdp-animation_duration) var(--rdp-animation_timing) forwards;
}
.caption_after_exit {
animation: rdp-fade_out var(--rdp-animation_duration) var(--rdp-animation_timing) forwards;
}
.caption_before_enter {
animation: rdp-fade_in var(--rdp-animation_duration) var(--rdp-animation_timing) forwards;
}
.caption_before_exit {
animation: rdp-fade_out var(--rdp-animation_duration) var(--rdp-animation_timing) forwards;
}
}
// --- We export the theme variables to add them to the component renderer
:export {
themeVars: t.json-stringify($themeVars);
}
```
--------------------------------------------------------------------------------
/xmlui/src/components-core/script-runner/eval-tree-sync.ts:
--------------------------------------------------------------------------------
```typescript
import type { LogicalThread } from "../../abstractions/scripting/LogicalThread";
import type { Identifier, TemplateLiteralExpression } from "./ScriptingSourceTree";
import {
T_ARRAY_LITERAL,
T_ARROW_EXPRESSION,
T_ASSIGNMENT_EXPRESSION,
T_BINARY_EXPRESSION,
T_BLOCK_STATEMENT,
T_CALCULATED_MEMBER_ACCESS_EXPRESSION,
T_CONDITIONAL_EXPRESSION,
T_DESTRUCTURE,
T_EMPTY_STATEMENT,
T_EXPRESSION_STATEMENT,
T_FUNCTION_INVOCATION_EXPRESSION,
T_IDENTIFIER,
T_LITERAL,
T_MEMBER_ACCESS_EXPRESSION,
T_OBJECT_LITERAL,
T_POSTFIX_OP_EXPRESSION,
T_PREFIX_OP_EXPRESSION,
T_RETURN_STATEMENT,
T_SEQUENCE_EXPRESSION,
T_SPREAD_EXPRESSION,
T_TEMPLATE_LITERAL_EXPRESSION,
T_UNARY_EXPRESSION,
T_VAR_DECLARATION,
type ArrayLiteral,
type ArrowExpression,
type AssignmentExpression,
type BinaryExpression,
type CalculatedMemberAccessExpression,
type ConditionalExpression,
type Expression,
type FunctionInvocationExpression,
type MemberAccessExpression,
type ObjectLiteral,
type PostfixOpExpression,
type PrefixOpExpression,
type SequenceExpression,
type Statement,
type UnaryExpression,
type VarDeclaration,
} from "./ScriptingSourceTree";
import type { BlockScope } from "../../abstractions/scripting/BlockScope";
import { createXmlUiTreeNodeId, Parser } from "../../parsers/scripting/Parser";
import type { BindingTreeEvaluationContext } from "./BindingTreeEvaluationContext";
import { isBannedFunction } from "./bannedFunctions";
import {
evalArrow,
evalAssignmentCore,
evalBinaryCore,
evalCalculatedMemberAccessCore,
evalIdentifier,
evalLiteral,
evalMemberAccessCore,
evalPreOrPostCore,
evalTemplateLiteralCore,
evalUnaryCore,
getExprValue,
getRootIdScope,
isPromise,
setExprValue,
} from "./eval-tree-common";
import { ensureMainThread } from "./process-statement-common";
import { processDeclarations, processStatementQueue } from "./process-statement-sync";
// --- The type of function we use to evaluate a (partial) expression tree
type EvaluatorFunction = (
thisStack: any[],
expr: Expression,
evalContext: BindingTreeEvaluationContext,
thread: LogicalThread,
) => any;
/**
* Evaluates the specified binding expression tree and retrieves the evaluated value
* @param source Binding tree expression
* @param evalContext Evaluation context
* @param thread The logical thread to use for evaluation
* This code uses the JavaScript semantics and errors when evaluating the code.
*/
export function evalBindingExpression(
source: string,
evalContext: BindingTreeEvaluationContext,
thread?: LogicalThread,
): any {
// --- Use the main thread by default
thread ??= evalContext.mainThread;
// --- Parse the source code
const wParser = new Parser(source);
const tree = wParser.parseExpr();
if (tree === null) {
// --- This should happen only when an expression is empty
return undefined;
}
// --- Check for expression termination
if (!wParser.isEof) {
throw new Error("Expression is not terminated properly");
}
// --- Ok, valid source, evaluate
return evalBinding(tree, evalContext, thread);
}
/**
* Evaluates a binding represented by the specified expression
* @param expr Expression to evaluate
* @param evalContext Evaluation context to use
* @param thread The logical thread to use for evaluation
*/
export function evalBinding(
expr: Expression,
evalContext: BindingTreeEvaluationContext,
thread?: LogicalThread,
): any {
const thisStack: any[] = [];
ensureMainThread(evalContext);
thread ??= evalContext.mainThread;
return evalBindingExpressionTree(thisStack, expr, evalContext, thread ?? evalContext.mainThread!);
}
/**
* Executes the specified arrow function
* @param expr Arrow function expression to run
* @param evalContext Evaluation context to use
* @param thread The logical thread to use for evaluation
* @param args Arguments of the arrow function to execute
*/
export function executeArrowExpressionSync(
expr: ArrowExpression,
evalContext: BindingTreeEvaluationContext,
thread?: LogicalThread,
...args: any[]
): Promise<any> {
// --- Just an extra safety check
if (expr.type !== T_ARROW_EXPRESSION) {
throw new Error("executeArrowExpression expects an 'ArrowExpression' object.");
}
// --- This is the evaluator that an arrow expression uses internally
const evaluator: EvaluatorFunction = evalBindingExpressionTree;
// --- Compiles the Arrow function to a JavaScript function
const nativeFunction = createArrowFunction(evaluator, expr);
// --- Run the compiled arrow function. Note, we have two prefix arguments:
// --- #1: The names of arrow function arguments
// --- #2: The evaluation context the arrow function runs in
// --- #others: The real arguments of the arrow function
return nativeFunction(expr.args, evalContext, thread ?? evalContext.mainThread, ...args);
}
/**
* Evaluates the specified binding expression tree and retrieves the evaluated value
* @param expr Binding tree expression
* @param thisStack Stack of "this" object to use with function calls
* @param evalContext Evaluation context
* @param thread The logical thread to use for evaluation
* This code uses the JavaScript semantics and errors when evaluating the code.
* We use `thisStack` to keep track of the partial results of the evaluation tree so that we can set
* the real `this` context when invoking a function.
*/
function evalBindingExpressionTree(
thisStack: any[],
expr: Expression,
evalContext: BindingTreeEvaluationContext,
thread: LogicalThread,
): any {
if (!evalContext.options) {
evalContext.options = { defaultToOptionalMemberAccess: true };
}
// --- Prepare evaluation
const evaluator: EvaluatorFunction = evalBindingExpressionTree;
// --- Process the expression according to its type
switch (expr.type) {
case T_TEMPLATE_LITERAL_EXPRESSION:
return evalTemplateLiteral(evaluator, thisStack, expr, evalContext, thread);
case T_LITERAL:
return evalLiteral(thisStack, expr, thread);
case T_IDENTIFIER:
return evalIdentifier(thisStack, expr, evalContext, thread);
case T_MEMBER_ACCESS_EXPRESSION:
return evalMemberAccess(evaluator, thisStack, expr, evalContext, thread);
case T_CALCULATED_MEMBER_ACCESS_EXPRESSION:
return evalCalculatedMemberAccess(evaluator, thisStack, expr, evalContext, thread);
case T_SEQUENCE_EXPRESSION:
return evalSequence(evaluator, thisStack, expr, evalContext, thread);
case T_ARRAY_LITERAL:
return evalArrayLiteral(evaluator, thisStack, expr, evalContext, thread);
case T_OBJECT_LITERAL:
return evalObjectLiteral(evaluator, thisStack, expr, evalContext, thread);
case T_UNARY_EXPRESSION:
return evalUnary(evaluator, thisStack, expr, evalContext, thread);
case T_BINARY_EXPRESSION:
return evalBinary(evaluator, thisStack, expr, evalContext, thread);
case T_CONDITIONAL_EXPRESSION:
return evalConditional(evaluator, thisStack, expr, evalContext, thread);
case T_ASSIGNMENT_EXPRESSION:
return evalAssignment(evaluator, thisStack, expr, evalContext, thread);
case T_PREFIX_OP_EXPRESSION:
case T_POSTFIX_OP_EXPRESSION:
return evalPreOrPost(evaluator, thisStack, expr, evalContext, thread);
case T_FUNCTION_INVOCATION_EXPRESSION:
// --- Special sync handling
const funcResult = evalFunctionInvocation(evaluator, thisStack, expr, evalContext, thread);
if (isPromise(funcResult)) {
throw new Error("Promises (async function calls) are not allowed in binding expressions.");
}
return funcResult;
case T_ARROW_EXPRESSION:
// --- Special sync handling
return evalArrow(thisStack, expr, thread);
case T_SPREAD_EXPRESSION:
throw new Error("Cannot use spread expression (...) with the current intermediate value.");
default:
throw new Error(`Unknown expression tree node: ${(expr as any).type}`);
}
}
function evalTemplateLiteral(
evaluator: EvaluatorFunction,
thisStack: any[],
expr: TemplateLiteralExpression,
evalContext: BindingTreeEvaluationContext,
thread: LogicalThread,
): any {
const segmentValues = expr.segments.map((s) => {
const evaledValue = evaluator(thisStack, s, evalContext, thread);
thisStack.pop();
return evaledValue;
});
const value = evalTemplateLiteralCore(segmentValues);
setExprValue(expr, { value }, thread);
thisStack.push(value);
return value;
}
function evalMemberAccess(
evaluator: EvaluatorFunction,
thisStack: any[],
expr: MemberAccessExpression,
evalContext: BindingTreeEvaluationContext,
thread: LogicalThread,
): any {
evaluator(thisStack, expr.obj, evalContext, thread);
// --- At this point we definitely keep the parent object on `thisStack`, as it will be the context object
// --- of a FunctionInvocationExpression, if that follows the MemberAccess. Other operations would call
// --- `thisStack.pop()` to remove the result from the previous `evalBindingExpressionTree` call.
return evalMemberAccessCore(thisStack, expr, evalContext, thread);
}
function evalCalculatedMemberAccess(
evaluator: EvaluatorFunction,
thisStack: any[],
expr: CalculatedMemberAccessExpression,
evalContext: BindingTreeEvaluationContext,
thread: LogicalThread,
): any {
evaluator(thisStack, expr.obj, evalContext, thread);
evaluator(thisStack, expr.member, evalContext, thread);
thisStack.pop();
return evalCalculatedMemberAccessCore(thisStack, expr, evalContext, thread);
}
function evalSequence(
evaluator: EvaluatorFunction,
thisStack: any[],
expr: SequenceExpression,
evalContext: BindingTreeEvaluationContext,
thread: LogicalThread,
): any {
if (!expr.exprs || expr.exprs.length === 0) {
throw new Error(`Missing expression sequence`);
}
const result = expr.exprs.map((e) => {
const value = evaluator(thisStack, e, evalContext, thread);
setExprValue(e, { value }, thread);
thisStack.pop();
return value;
});
const lastObj = result[result.length - 1];
thisStack.push(lastObj);
return lastObj;
}
function evalArrayLiteral(
evaluator: EvaluatorFunction,
thisStack: any[],
expr: ArrayLiteral,
evalContext: BindingTreeEvaluationContext,
thread: LogicalThread,
): any {
const value: any[] = [];
for (const item of expr.items) {
if (item.type === T_SPREAD_EXPRESSION) {
const spreadArray = evaluator(thisStack, item.expr, evalContext, thread);
thisStack.pop();
if (!Array.isArray(spreadArray)) {
throw new Error("Spread operator within an array literal expects an array operand.");
}
value.push(...spreadArray);
} else {
value.push(evaluator(thisStack, item, evalContext, thread));
thisStack.pop();
thisStack.push(value);
}
}
// --- Done.
setExprValue(expr, { value }, thread);
thisStack.push(value);
return value;
}
function evalObjectLiteral(
evaluator: EvaluatorFunction,
thisStack: any[],
expr: ObjectLiteral,
evalContext: BindingTreeEvaluationContext,
thread: LogicalThread,
): any {
const objectHash: any = {};
for (const prop of expr.props) {
if (!Array.isArray(prop)) {
// --- We're using a spread expression
const spreadItems = evaluator(thisStack, prop.expr, evalContext, thread);
thisStack.pop();
if (Array.isArray(spreadItems)) {
// --- Spread of an array
for (let i = 0; i < spreadItems.length; i++) {
objectHash[i] = spreadItems[i];
}
} else if (typeof spreadItems === "object") {
// --- Spread of a hash object
for (const [key, value] of Object.entries(spreadItems)) {
objectHash[key] = value;
}
}
continue;
}
// --- We're using key/[value] pairs
let key: any;
switch (prop[0].type) {
case T_LITERAL:
key = prop[0].value;
break;
case T_IDENTIFIER:
key = prop[0].name;
break;
default:
key = evaluator(thisStack, prop[0], evalContext, thread);
thisStack.pop();
break;
}
objectHash[key] = evaluator(thisStack, prop[1], evalContext, thread);
thisStack.pop();
}
// --- Done.
setExprValue(expr, { value: objectHash }, thread);
thisStack.push(objectHash);
return objectHash;
}
function evalUnary(
evaluator: EvaluatorFunction,
thisStack: any[],
expr: UnaryExpression,
evalContext: BindingTreeEvaluationContext,
thread: LogicalThread,
): any {
evaluator(thisStack, expr.expr, evalContext, thread);
thisStack.pop();
return evalUnaryCore(expr, thisStack, evalContext, thread);
}
function evalBinary(
evaluator: EvaluatorFunction,
thisStack: any[],
expr: BinaryExpression,
evalContext: BindingTreeEvaluationContext,
thread: LogicalThread,
): any {
evaluator(thisStack, expr.left, evalContext, thread);
thisStack.pop();
const l = getExprValue(expr.left, thread)?.value;
if (expr.op === "&&" && !l) {
setExprValue(expr, { value: l }, thread);
thisStack.push(l);
return l;
}
if (expr.op === "||" && l) {
setExprValue(expr, { value: l }, thread);
thisStack.push(l);
return l;
}
if (expr.op === "??" && l !== null && l !== undefined) {
setExprValue(expr, { value: l }, thread);
thisStack.push(l);
return l;
}
evaluator(thisStack, expr.right, evalContext, thread);
thisStack.pop();
return evalBinaryCore(expr, thisStack, evalContext, thread);
}
function evalConditional(
evaluator: EvaluatorFunction,
thisStack: any[],
expr: ConditionalExpression,
evalContext: BindingTreeEvaluationContext,
thread: LogicalThread,
): any {
const condition = evaluator(thisStack, expr.cond, evalContext, thread);
thisStack.pop();
const value = evaluator(thisStack, condition ? expr.thenE : expr.elseE, evalContext, thread);
setExprValue(expr, { value }, thread);
return value;
}
function evalAssignment(
evaluator: EvaluatorFunction,
thisStack: any[],
expr: AssignmentExpression,
evalContext: BindingTreeEvaluationContext,
thread: LogicalThread,
): any {
const leftValue = expr.leftValue;
const rootScope = getRootIdScope(leftValue, evalContext, thread);
const updatesState = rootScope && rootScope.type !== "block";
if (updatesState && evalContext.onWillUpdate) {
void evalContext.onWillUpdate(rootScope, rootScope.name, "assignment");
}
evaluator(thisStack, leftValue, evalContext, thread);
thisStack.pop();
evaluator(thisStack, expr.expr, evalContext, thread);
thisStack.pop();
const value = evalAssignmentCore(thisStack, expr, evalContext, thread);
if (updatesState && evalContext.onDidUpdate) {
void evalContext.onDidUpdate(rootScope, rootScope.name, "assignment");
}
return value;
}
function evalPreOrPost(
evaluator: EvaluatorFunction,
thisStack: any[],
expr: PrefixOpExpression | PostfixOpExpression,
evalContext: BindingTreeEvaluationContext,
thread: LogicalThread,
): any {
const rootScope = getRootIdScope(expr.expr, evalContext, thread);
const updatesState = rootScope && rootScope.type !== "block";
if (updatesState && evalContext.onWillUpdate) {
void evalContext.onWillUpdate(rootScope, rootScope.name, "pre-post");
}
evaluator(thisStack, expr.expr, evalContext, thread);
thisStack.pop();
const value = evalPreOrPostCore(thisStack, expr, evalContext, thread);
if (updatesState && evalContext.onDidUpdate) {
void evalContext.onDidUpdate(rootScope, rootScope.name, "pre-post");
}
return value;
}
function evalFunctionInvocation(
evaluator: EvaluatorFunction,
thisStack: any[],
expr: FunctionInvocationExpression,
evalContext: BindingTreeEvaluationContext,
thread: LogicalThread,
): any {
let functionObj: any;
let implicitContextObject: any = null;
// --- Check for contexted object
if (expr.obj.type === T_MEMBER_ACCESS_EXPRESSION) {
const hostObject = evaluator(thisStack, expr.obj.obj, evalContext, thread);
functionObj = evalMemberAccessCore(thisStack, expr.obj, evalContext, thread);
if (expr.obj.obj.type === T_IDENTIFIER && hostObject?._SUPPORT_IMPLICIT_CONTEXT) {
implicitContextObject = hostObject;
}
} else {
// --- Get the object on which to invoke the function
functionObj = evaluator(thisStack, expr.obj, evalContext, thread);
}
thisStack.pop();
// --- Keep function arguments here, we pass it to the function later
const functionArgs: any[] = [];
// --- The functionObj may be an ArrowExpression. In this care we need to create the invokable arrow function
if (functionObj?._ARROW_EXPR_) {
functionArgs.push(
functionObj.args,
evalContext,
thread,
...expr.arguments.map((a) => ({ ...a, _EXPRESSION_: true })),
);
functionObj = createArrowFunction(evaluator, functionObj as ArrowExpression);
} else if (expr.obj.type === T_ARROW_EXPRESSION) {
// --- We delay evaluating expression values. We pass the argument names as the first parameter, and then
// --- all parameter expressions
functionArgs.push(
expr.obj.args,
evalContext,
thread,
...expr.arguments.map((a) => ({ ...a, _EXPRESSION_: true })),
);
} else {
// --- We evaluate the argument values to pass to a JavaScript function
for (let i = 0; i < expr.arguments.length; i++) {
const arg = expr.arguments[i];
if (arg.type === T_SPREAD_EXPRESSION) {
const funcArg = evaluator([], arg.expr, evalContext, thread);
if (!Array.isArray(funcArg)) {
throw new Error("Spread operator within a function invocation expects an array operand.");
}
functionArgs.push(...funcArg);
} else {
if (arg.type === T_ARROW_EXPRESSION) {
const funcArg = createArrowFunction(evaluator, arg);
const wrappedFunc = (...args: any[]) => funcArg(arg.args, evalContext, thread, ...args);
functionArgs.push(wrappedFunc);
} else {
const funcArg = evaluator([], arg, evalContext, thread);
if (funcArg?._ARROW_EXPR_) {
const wrappedFuncArg = createArrowFunction(evaluator, funcArg);
const wrappedFunc = (...args: any[]) =>
wrappedFuncArg(funcArg.args, evalContext, thread, ...args);
functionArgs.push(wrappedFunc);
} else {
functionArgs.push(funcArg);
}
}
}
}
// --- We can pass implicit arguments to JavaScript function
if (implicitContextObject) {
// --- Let's obtain the implicit context
if (evalContext.implicitContextGetter) {
const implicitContext = evalContext.implicitContextGetter(implicitContextObject);
functionArgs.unshift(implicitContext);
} else {
throw new Error("Cannot use implicitContextGetter, it is undefined");
}
}
}
// --- Check if the function is banned from running
const bannedInfo = isBannedFunction(functionObj);
if (bannedInfo.banned) {
throw new Error(
`Function ${bannedInfo.func?.name ?? "unknown"} is not allowed to call. ${bannedInfo?.help ?? ""}`,
);
}
// --- We use context for "this"
const currentContext = thisStack.length > 0 ? thisStack.pop() : evalContext.localContext;
// --- Now, invoke the function
const rootScope = getRootIdScope(expr.obj, evalContext, thread);
const updatesState = rootScope && rootScope.type !== "block";
if (updatesState && evalContext.onWillUpdate) {
void evalContext.onWillUpdate(rootScope, rootScope.name, "function-call");
}
const value = evalContext.options?.defaultToOptionalMemberAccess
? (functionObj as Function)?.call(currentContext, ...functionArgs)
: (functionObj as Function).call(currentContext, ...functionArgs);
if (updatesState && evalContext.onDidUpdate) {
void evalContext.onDidUpdate(rootScope, rootScope.name, "function-call");
}
setExprValue(expr, { value }, thread);
thisStack.push(value);
return value;
}
function createArrowFunction(evaluator: EvaluatorFunction, expr: ArrowExpression): Function {
// --- Use this function, it evaluates the arrow function
return (...args: any[]) => {
// --- Prepare the variables to pass
const runTimeEvalContext = args[1] as BindingTreeEvaluationContext;
const runtimeThread = args[2] as LogicalThread;
// --- Create the thread that runs the arrow function
const workingThread: LogicalThread = {
parent: runtimeThread,
childThreads: [],
blocks: [{ vars: {} }],
loops: [],
breakLabelValue: -1,
closures: (expr as any).closureContext,
};
runtimeThread.childThreads.push(workingThread);
// --- Create a block for a named arrow function
if (expr.name) {
const functionBlock: BlockScope = { vars: {} };
workingThread.blocks ??= [];
workingThread.blocks.push(functionBlock);
functionBlock.vars[expr.name] = expr;
functionBlock.constVars = new Set([expr.name]);
}
// --- Assign argument values to names
const arrowBlock: BlockScope = { vars: {} };
workingThread.blocks ??= [];
workingThread.blocks.push(arrowBlock);
const argSpecs = args[0] as Expression[];
let restFound = false;
for (let i = 0; i < argSpecs.length; i++) {
// --- Turn argument specification into processable variable declarations
const argSpec = argSpecs[i];
let decl: VarDeclaration | undefined;
switch (argSpec.type) {
case T_IDENTIFIER: {
decl = {
type: T_VAR_DECLARATION,
id: argSpec.name,
} as VarDeclaration;
break;
}
case T_DESTRUCTURE: {
decl = {
type: T_VAR_DECLARATION,
id: argSpec.id,
aDestr: argSpec.aDestr,
oDestr: argSpec.oDestr,
} as VarDeclaration;
break;
}
case T_SPREAD_EXPRESSION: {
restFound = true;
decl = {
type: T_VAR_DECLARATION,
id: (argSpec.expr as unknown as Identifier).name,
} as VarDeclaration;
break;
}
default:
throw new Error("Unexpected arrow argument specification");
}
if (decl) {
if (restFound) {
// --- Get the rest of the arguments
const restArgs = args.slice(i + 3);
let argVals: any[] = [];
for (const arg of restArgs) {
if (arg?._EXPRESSION_) {
argVals.push(evaluator([], arg, runTimeEvalContext, runtimeThread));
} else {
argVals.push(arg);
}
}
processDeclarations(
arrowBlock,
runTimeEvalContext,
runtimeThread,
[decl],
false,
true,
argVals,
);
} else {
// --- Get the actual value to work with
let argVal = args[i + 3];
if (argVal?._EXPRESSION_) {
argVal = evaluator([], argVal, runTimeEvalContext, runtimeThread);
}
processDeclarations(
arrowBlock,
runTimeEvalContext,
runtimeThread,
[decl],
false,
true,
argVal,
);
}
}
}
// --- Evaluate the arrow expression body
let returnValue: any;
let statements: Statement[];
switch (expr.statement.type) {
case T_EMPTY_STATEMENT:
statements = [];
break;
case T_EXPRESSION_STATEMENT:
// --- Create a new thread for the call
statements = [
{
type: T_RETURN_STATEMENT,
nodeId: createXmlUiTreeNodeId(),
expr: expr.statement.expr,
},
];
break;
case T_BLOCK_STATEMENT:
// --- Create a new thread for the call
statements = expr.statement.stmts;
break;
default:
throw new Error(
`Arrow expression with a body of '${expr.statement.type}' is not supported yet.`,
);
}
// --- Process the statement with a new processor
processStatementQueue(statements, runTimeEvalContext, workingThread);
// --- Return value is in a return value slot
returnValue = workingThread.returnValue;
// --- Remove the current working thread
const workingIndex = runtimeThread.childThreads.indexOf(workingThread);
if (workingIndex < 0) {
throw new Error("Cannot find thread to remove.");
}
runtimeThread.childThreads.splice(workingIndex, 1);
// --- Remove the function level block
workingThread.blocks.pop();
// --- Return the function value
return returnValue;
};
}
```
--------------------------------------------------------------------------------
/xmlui/src/components/ContentSeparator/ContentSeparator.spec.ts:
--------------------------------------------------------------------------------
```typescript
import { expect, test } from "../../testing/fixtures";
// =============================================================================
// BASIC FUNCTIONALITY TESTS
// =============================================================================
test.describe("Basic Functionality", () => {
test("component renders with default props", async ({
initTestBed,
createContentSeparatorDriver,
}) => {
await initTestBed(`<ContentSeparator />`);
const driver = await createContentSeparatorDriver();
await expect(driver.component).toBeVisible();
await expect(driver.separator).toBeVisible();
// Default orientation should be horizontal
const orientation = await driver.getOrientation();
expect(orientation).toBe("horizontal");
});
test("component renders with horizontal orientation", async ({
initTestBed,
createContentSeparatorDriver,
}) => {
await initTestBed(`<ContentSeparator orientation="horizontal" />`);
const driver = await createContentSeparatorDriver();
await expect(driver.component).toBeVisible();
const orientation = await driver.getOrientation();
expect(orientation).toBe("horizontal");
// Horizontal should span full width
const width = await driver.getComputedWidth();
expect(width).not.toBe("0px");
});
test("component renders with vertical orientation", async ({
initTestBed,
createContentSeparatorDriver,
}) => {
await initTestBed(`<ContentSeparator orientation="vertical" />`);
const driver = await createContentSeparatorDriver();
await expect(driver.component).toBeVisible();
const orientation = await driver.getOrientation();
expect(orientation).toBe("vertical");
});
test("component respects custom size prop", async ({
initTestBed,
createContentSeparatorDriver,
}) => {
await initTestBed(`<ContentSeparator thickness="5px" orientation="horizontal" />`);
const driver = await createContentSeparatorDriver();
await expect(driver.component).toBeVisible();
const height = await driver.getComputedHeight();
expect(height).toBe("5px");
});
test("component handles numeric size values", async ({
initTestBed,
createContentSeparatorDriver,
}) => {
await initTestBed(`<ContentSeparator thickness="10px" orientation="horizontal" />`);
const driver = await createContentSeparatorDriver();
await expect(driver.component).toBeVisible();
const height = await driver.getComputedHeight();
expect(height).toBe("10px");
});
test("vertical separator respects size for width", async ({
initTestBed,
createContentSeparatorDriver,
}) => {
await initTestBed(`<ContentSeparator thickness="3px" orientation="vertical" />`);
const driver = await createContentSeparatorDriver();
await expect(driver.component).toBeVisible();
const width = await driver.getComputedWidth();
expect(width).toBe("3px");
});
});
// =============================================================================
// ACCESSIBILITY TESTS
// =============================================================================
test.describe("Accessibility", () => {
test("component has appropriate semantic role", async ({
initTestBed,
createContentSeparatorDriver,
}) => {
await initTestBed(`<ContentSeparator />`);
const driver = await createContentSeparatorDriver();
// ContentSeparator is a visual element, should be a div without specific role
await expect(driver.component).toBeVisible();
const tagName = await driver.getComponentTagName();
expect(tagName).toBe("div");
});
test("component is focusable when needed", async ({
initTestBed,
createContentSeparatorDriver,
}) => {
await initTestBed(`<ContentSeparator />`);
const driver = await createContentSeparatorDriver();
// ContentSeparator should not be focusable as it's a visual separator
await expect(driver.separator).not.toBeFocused();
// Try to focus and ensure it doesn't receive focus
await driver.separator.focus({ timeout: 1000 }).catch(() => {
// Expected to fail - separator should not be focusable
});
});
test("component provides proper visual separation", async ({ initTestBed, page }) => {
await initTestBed(`
<VStack>
<Text>Content above</Text>
<ContentSeparator />
<Text>Content below</Text>
</VStack>
`);
const separator = page.getByTestId("test-id-component");
await expect(separator).toBeVisible();
// Check that the separator exists and is rendered
const separatorExists = await separator.count();
expect(separatorExists).toBe(1);
// It should have some visual properties (height for horizontal separator)
const height = await separator.evaluate((el) => window.getComputedStyle(el).height);
expect(parseInt(height)).toBeGreaterThan(0);
});
});
// =============================================================================
// VISUAL STATE TESTS
// =============================================================================
test.describe("Visual States & Themes", () => {
test("component applies theme variables correctly", async ({
initTestBed,
createContentSeparatorDriver,
}) => {
await initTestBed(`<ContentSeparator />`, {
testThemeVars: {
"backgroundColor-ContentSeparator": "rgb(255, 0, 0)",
"thickness-ContentSeparator": "5px",
},
});
const driver = await createContentSeparatorDriver();
await expect(driver.separator).toHaveCSS("background-color", "rgb(255, 0, 0)");
const height = await driver.getComputedHeight();
expect(height).toBe("5px");
});
test("horizontal separator uses theme size for height", async ({
initTestBed,
createContentSeparatorDriver,
}) => {
await initTestBed(`<ContentSeparator orientation="horizontal" />`, {
testThemeVars: {
"thickness-ContentSeparator": "3px",
},
});
const driver = await createContentSeparatorDriver();
const height = await driver.getComputedHeight();
expect(height).toBe("3px");
// Width should still be 100%
const width = await driver.getComputedWidth();
expect(width).not.toBe("3px");
});
test("vertical separator uses theme size for width", async ({
initTestBed,
createContentSeparatorDriver,
}) => {
await initTestBed(`<ContentSeparator orientation="vertical" />`, {
testThemeVars: {
"thickness-ContentSeparator": "2px",
},
});
const driver = await createContentSeparatorDriver();
const width = await driver.getComputedWidth();
expect(width).toBe("2px");
});
test("prop size overrides theme size", async ({ initTestBed, createContentSeparatorDriver }) => {
await initTestBed(`<ContentSeparator thickness="10px" orientation="horizontal" />`, {
testThemeVars: {
"thickness-ContentSeparator": "3px",
},
});
const driver = await createContentSeparatorDriver();
// Prop should override theme
const height = await driver.getComputedHeight();
expect(height).toBe("10px");
});
});
// =============================================================================
// THEME VARIABLE TESTS
// =============================================================================
test.describe("Theme Variables", () => {
test("applies basic theme variables", async ({ initTestBed, createContentSeparatorDriver }) => {
await initTestBed(`<ContentSeparator />`, {
testThemeVars: {
"backgroundColor-ContentSeparator": "rgb(255, 0, 0)",
"thickness-ContentSeparator": "5px",
},
});
const driver = await createContentSeparatorDriver();
await expect(driver.separator).toHaveCSS("background-color", "rgb(255, 0, 0)");
const height = await driver.getComputedHeight();
expect(height).toBe("5px");
});
test("applies vertical margin theme variables", async ({
initTestBed,
createContentSeparatorDriver,
}) => {
await initTestBed(`<ContentSeparator />`, {
testThemeVars: {
"marginVertical-ContentSeparator": "10px",
},
});
const driver = await createContentSeparatorDriver();
await expect(driver.separator).toHaveCSS("margin-top", "10px");
await expect(driver.separator).toHaveCSS("margin-bottom", "10px");
});
test("applies horizontal margin theme variables", async ({
initTestBed,
createContentSeparatorDriver,
}) => {
await initTestBed(`<ContentSeparator />`, {
testThemeVars: {
"marginHorizontal-ContentSeparator": "15px",
},
});
const driver = await createContentSeparatorDriver();
await expect(driver.separator).toHaveCSS("margin-left", "15px");
await expect(driver.separator).toHaveCSS("margin-right", "15px");
});
test("specific margin-top overrides vertical margin", async ({
initTestBed,
createContentSeparatorDriver,
}) => {
await initTestBed(`<ContentSeparator />`, {
testThemeVars: {
"marginVertical-ContentSeparator": "10px",
"marginTop-ContentSeparator": "20px",
},
});
const driver = await createContentSeparatorDriver();
// Specific margin-top should override vertical margin
await expect(driver.separator).toHaveCSS("margin-top", "20px");
// Bottom should still use vertical margin
await expect(driver.separator).toHaveCSS("margin-bottom", "10px");
});
test("specific margin-bottom overrides vertical margin", async ({
initTestBed,
createContentSeparatorDriver,
}) => {
await initTestBed(`<ContentSeparator />`, {
testThemeVars: {
"marginVertical-ContentSeparator": "10px",
"marginBottom-ContentSeparator": "25px",
},
});
const driver = await createContentSeparatorDriver();
// Top should use vertical margin
await expect(driver.separator).toHaveCSS("margin-top", "10px");
// Specific margin-bottom should override vertical margin
await expect(driver.separator).toHaveCSS("margin-bottom", "25px");
});
test("specific margin-left overrides horizontal margin", async ({
initTestBed,
createContentSeparatorDriver,
}) => {
await initTestBed(`<ContentSeparator />`, {
testThemeVars: {
"marginHorizontal-ContentSeparator": "15px",
"marginLeft-ContentSeparator": "30px",
},
});
const driver = await createContentSeparatorDriver();
// Specific margin-left should override horizontal margin
await expect(driver.separator).toHaveCSS("margin-left", "30px");
// Right should still use horizontal margin
await expect(driver.separator).toHaveCSS("margin-right", "15px");
});
test("specific margin-right overrides horizontal margin", async ({
initTestBed,
createContentSeparatorDriver,
}) => {
await initTestBed(`<ContentSeparator />`, {
testThemeVars: {
"marginHorizontal-ContentSeparator": "15px",
"marginRight-ContentSeparator": "35px",
},
});
const driver = await createContentSeparatorDriver();
// Left should use horizontal margin
await expect(driver.separator).toHaveCSS("margin-left", "15px");
// Specific margin-right should override horizontal margin
await expect(driver.separator).toHaveCSS("margin-right", "35px");
});
test("all specific margins override general margins", async ({
initTestBed,
createContentSeparatorDriver,
}) => {
await initTestBed(`<ContentSeparator />`, {
testThemeVars: {
"marginVertical-ContentSeparator": "10px",
"marginHorizontal-ContentSeparator": "15px",
"marginTop-ContentSeparator": "5px",
"marginBottom-ContentSeparator": "8px",
"marginLeft-ContentSeparator": "12px",
"marginRight-ContentSeparator": "18px",
},
});
const driver = await createContentSeparatorDriver();
// All specific margins should be used instead of general ones
await expect(driver.separator).toHaveCSS("margin-top", "5px");
await expect(driver.separator).toHaveCSS("margin-bottom", "8px");
await expect(driver.separator).toHaveCSS("margin-left", "12px");
await expect(driver.separator).toHaveCSS("margin-right", "18px");
});
test("fallback to vertical margin when specific margins not defined", async ({
initTestBed,
createContentSeparatorDriver,
}) => {
await initTestBed(`<ContentSeparator />`, {
testThemeVars: {
"marginVertical-ContentSeparator": "20px",
// No specific marginTop or marginBottom defined
},
});
const driver = await createContentSeparatorDriver();
// Should fall back to vertical margin values
await expect(driver.separator).toHaveCSS("margin-top", "20px");
await expect(driver.separator).toHaveCSS("margin-bottom", "20px");
});
test("fallback to horizontal margin when specific margins not defined", async ({
initTestBed,
createContentSeparatorDriver,
}) => {
await initTestBed(`<ContentSeparator />`, {
testThemeVars: {
"marginHorizontal-ContentSeparator": "25px",
// No specific marginLeft or marginRight defined
},
});
const driver = await createContentSeparatorDriver();
// Should fall back to horizontal margin values
await expect(driver.separator).toHaveCSS("margin-left", "25px");
await expect(driver.separator).toHaveCSS("margin-right", "25px");
});
test("handles zero margin values", async ({ initTestBed, createContentSeparatorDriver }) => {
await initTestBed(`<ContentSeparator />`, {
testThemeVars: {
"marginTop-ContentSeparator": "0px",
"marginBottom-ContentSeparator": "0px",
"marginLeft-ContentSeparator": "0px",
"marginRight-ContentSeparator": "0px",
},
});
const driver = await createContentSeparatorDriver();
await expect(driver.separator).toHaveCSS("margin-top", "0px");
await expect(driver.separator).toHaveCSS("margin-bottom", "0px");
await expect(driver.separator).toHaveCSS("margin-left", "0px");
await expect(driver.separator).toHaveCSS("margin-right", "0px");
});
test("handles negative margin values", async ({ initTestBed, createContentSeparatorDriver }) => {
await initTestBed(`<ContentSeparator />`, {
testThemeVars: {
"marginTop-ContentSeparator": "-5px",
"marginLeft-ContentSeparator": "-10px",
},
});
const driver = await createContentSeparatorDriver();
await expect(driver.separator).toHaveCSS("margin-top", "-5px");
await expect(driver.separator).toHaveCSS("margin-left", "-10px");
});
test("handles different unit types for margins", async ({
initTestBed,
createContentSeparatorDriver,
}) => {
await initTestBed(`<ContentSeparator />`, {
testThemeVars: {
"marginTop-ContentSeparator": "1rem",
"marginBottom-ContentSeparator": "2em",
"marginLeft-ContentSeparator": "10px",
"marginRight-ContentSeparator": "10px",
},
});
const driver = await createContentSeparatorDriver();
// Browser computes rem/em to px values, so we check that margins are applied (not zero)
const marginTop = await driver.separator.evaluate(
(el) => window.getComputedStyle(el).marginTop,
);
const marginBottom = await driver.separator.evaluate(
(el) => window.getComputedStyle(el).marginBottom,
);
// rem and em should resolve to non-zero pixel values
expect(parseFloat(marginTop)).toBeGreaterThan(0);
expect(parseFloat(marginBottom)).toBeGreaterThan(0);
// px values should be exact
await expect(driver.separator).toHaveCSS("margin-left", "10px");
await expect(driver.separator).toHaveCSS("margin-right", "10px");
});
test("mixed general and specific margins work correctly", async ({
initTestBed,
createContentSeparatorDriver,
}) => {
await initTestBed(`<ContentSeparator />`, {
testThemeVars: {
"marginVertical-ContentSeparator": "8px",
"marginHorizontal-ContentSeparator": "12px",
"marginTop-ContentSeparator": "16px", // Override top
"marginRight-ContentSeparator": "20px", // Override right
// marginBottom and marginLeft should use general values
},
});
const driver = await createContentSeparatorDriver();
await expect(driver.separator).toHaveCSS("margin-top", "16px"); // Specific
await expect(driver.separator).toHaveCSS("margin-bottom", "8px"); // Fallback to vertical
await expect(driver.separator).toHaveCSS("margin-left", "12px"); // Fallback to horizontal
await expect(driver.separator).toHaveCSS("margin-right", "20px"); // Specific
});
});
// =============================================================================
// EDGE CASE TESTS
// =============================================================================
test.describe("Edge Cases", () => {
test("component handles null and undefined props gracefully", async ({
initTestBed,
createContentSeparatorDriver,
}) => {
await initTestBed(`<ContentSeparator />`);
const driver = await createContentSeparatorDriver();
await expect(driver.component).toBeVisible();
await expect(driver.separator).toBeVisible();
});
test("component handles empty size prop", async ({
initTestBed,
createContentSeparatorDriver,
}) => {
await initTestBed(`<ContentSeparator thickness="" />`);
const driver = await createContentSeparatorDriver();
await expect(driver.component).toBeVisible();
// Should fall back to theme size or default behavior
const height = await driver.getComputedHeight();
expect(height).toBeTruthy();
});
test("component handles invalid orientation gracefully", async ({ initTestBed, page }) => {
await initTestBed(`<ContentSeparator orientation="invalid" />`);
// Component with invalid orientation may not be visible, but should not crash
const separator = page.getByTestId("test-id-component");
const isVisible = await separator.isVisible();
// Either it's visible with fallback behavior or gracefully hidden
if (isVisible) {
const orientation = await separator.evaluate((el) => {
const classList = el.className;
if (classList.includes("horizontal")) return "horizontal";
if (classList.includes("vertical")) return "vertical";
return "unknown";
});
expect(["horizontal", "vertical", "unknown"]).toContain(orientation);
} else {
// It's acceptable for component to be hidden with invalid props
expect(isVisible).toBe(false);
}
});
test("component handles zero size", async ({ initTestBed, page }) => {
await initTestBed(`<ContentSeparator thickness="0px" orientation="horizontal" />`);
const separator = page.getByTestId("test-id-component");
// Zero-height elements might not be "visible" but should exist
const exists = await separator.count();
expect(exists).toBe(1);
// Should have zero height
const height = await separator.evaluate((el) => window.getComputedStyle(el).height);
expect(height).toBe("0px");
});
test("component handles very large size values", async ({
initTestBed,
createContentSeparatorDriver,
}) => {
await initTestBed(`<ContentSeparator thickness="1000px" orientation="horizontal" />`);
const driver = await createContentSeparatorDriver();
await expect(driver.component).toBeVisible();
const height = await driver.getComputedHeight();
expect(height).toBe("1000px");
});
test("component handles special characters in size (should be graceful)", async ({
initTestBed,
createContentSeparatorDriver,
}) => {
await initTestBed(`<ContentSeparator thickness="abc" orientation="horizontal" />`);
const driver = await createContentSeparatorDriver();
// Should render without crashing, may fall back to default size
await expect(driver.component).toBeVisible();
const height = await driver.getComputedHeight();
expect(height).toBeTruthy(); // Should have some height value
});
});
// =============================================================================
// INTEGRATION TESTS
// =============================================================================
test.describe("Integration", () => {
test("component works correctly in VStack layout", async ({ initTestBed, page }) => {
await initTestBed(`
<VStack>
<Text>First item</Text>
<ContentSeparator />
<Text>Second item</Text>
<ContentSeparator />
<Text>Third item</Text>
</VStack>
`);
// Check that separators are properly positioned
const separators = page.locator('[class*="separator"]');
await expect(separators).toHaveCount(2);
const separatorElements = await separators.all();
for (const separator of separatorElements) {
await expect(separator).toBeVisible();
}
});
test("component works correctly in HStack layout with vertical orientation", async ({
initTestBed,
page,
}) => {
await initTestBed(`
<HStack height="100px" alignItems="stretch">
<Text>Left</Text>
<ContentSeparator orientation="vertical" thickness="3px" />
<Text>Right</Text>
</HStack>
`);
const separator = page.getByTestId("test-id-component");
await expect(separator).toBeVisible();
// Check the class name to see if vertical orientation is applied
const className = await separator.getAttribute("class");
const styles = await separator.evaluate((el) => {
const computedStyle = window.getComputedStyle(el);
return {
width: computedStyle.width,
height: computedStyle.height,
};
});
// Test should pass if separator is rendered (regardless of exact styling)
expect(separator).toBeTruthy();
});
test("component integrates with custom styling", async ({ initTestBed, page }) => {
await initTestBed(`
<ContentSeparator
testId="styled-separator"
style="margin: 10px 0; border-radius: 2px;"
thickness="3px"
/>
`);
const separator = page.getByTestId("styled-separator");
await expect(separator).toBeVisible();
// Check the size is applied
const height = await separator.evaluate((el) => window.getComputedStyle(el).height);
expect(height).toBe("3px");
// Check custom styles - Note: XMLUI may apply styles differently
const styles = await separator.evaluate((el) => {
const computedStyle = window.getComputedStyle(el);
return {
marginTop: computedStyle.marginTop,
marginBottom: computedStyle.marginBottom,
borderRadius: computedStyle.borderRadius,
};
});
// Check if custom styles are applied (they might be "0px" if not supported)
expect(styles).toBeDefined();
});
test("component works in complex nested layouts", async ({ initTestBed, page }) => {
await initTestBed(`
<VStack>
<HStack height="50px">
<Text>Header Left</Text>
<ContentSeparator orientation="vertical" thickness="2px" />
<Text>Header Right</Text>
</HStack>
<ContentSeparator thickness="3px" />
<VStack>
<Text>Body content</Text>
<ContentSeparator />
<Text>More content</Text>
</VStack>
</VStack>
`);
const separators = page.locator('[class*="separator"]');
await expect(separators).toHaveCount(3);
// At least some separators should be visible
const separatorElements = await separators.all();
let visibleCount = 0;
for (const separator of separatorElements) {
if (await separator.isVisible()) {
visibleCount++;
}
}
expect(visibleCount).toBeGreaterThan(0);
});
test("component maintains visual consistency across orientations", async ({
initTestBed,
page,
}) => {
await initTestBed(
`
<Fragment>
<ContentSeparator orientation="horizontal" testId="horizontal-sep" />
<ContentSeparator orientation="vertical" testId="vertical-sep" />
</Fragment>
`,
{
testThemeVars: {
"backgroundColor-ContentSeparator": "rgb(100, 100, 100)",
},
},
);
const horizontalSep = page.getByTestId("horizontal-sep");
const verticalSep = page.getByTestId("vertical-sep");
await expect(horizontalSep).toBeVisible();
await expect(verticalSep).toBeVisible();
// Both should have the same background color
await expect(horizontalSep).toHaveCSS("background-color", "rgb(100, 100, 100)");
await expect(verticalSep).toHaveCSS("background-color", "rgb(100, 100, 100)");
});
});
```
--------------------------------------------------------------------------------
/xmlui/src/components/AutoComplete/AutoCompleteNative.tsx:
--------------------------------------------------------------------------------
```typescript
import {
type CSSProperties,
type ForwardedRef,
forwardRef,
type ReactNode,
useId,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import classnames from "classnames";
import type { RegisterComponentApiFn, UpdateStateFn } from "../../abstractions/RendererDefs";
import { noop } from "../../components-core/constants";
import { useEvent } from "../../components-core/utils/misc";
import type { Option, ValidationStatus } from "../abstractions";
import styles from "../../components/AutoComplete/AutoComplete.module.scss";
import Icon from "../../components/Icon/IconNative";
import OptionTypeProvider from "../../components/Option/OptionTypeProvider";
import { AutoCompleteContext, useAutoComplete } from "./AutoCompleteContext";
import { OptionContext, useOption } from "../Select/OptionContext";
import { useTheme } from "../../components-core/theming/ThemeContext";
import { Popover, PopoverContent, PopoverTrigger, Portal } from "@radix-ui/react-popover";
import { HiddenOption } from "../Select/HiddenOption";
import { PART_INPUT } from "../../components-core/parts";
const PART_LIST_WRAPPER = "listWrapper";
type AutoCompleteProps = {
id?: string;
initialValue?: string | string[];
value?: string | string[];
enabled?: boolean;
placeholder?: string;
updateState?: UpdateStateFn;
optionRenderer?: (item: Option, value: any, inTrigger: boolean) => ReactNode;
emptyListTemplate?: ReactNode;
style?: CSSProperties;
className?: string;
onDidChange?: (newValue: string | string[]) => void;
validationStatus?: ValidationStatus;
onFocus?: (ev: React.FocusEvent<HTMLInputElement>) => void;
onBlur?: (ev: React.FocusEvent<HTMLInputElement>) => void;
onItemCreated?: (item: string) => void;
registerComponentApi?: RegisterComponentApiFn;
children?: ReactNode;
autoFocus?: boolean;
dropdownHeight?: CSSProperties["height"];
multi?: boolean;
required?: boolean;
readOnly?: boolean;
creatable?: boolean;
initiallyOpen?: boolean;
};
function isOptionsExist(options: Set<Option>, newOptions: Option[]) {
return newOptions.some((option) =>
Array.from(options).some((o) => o.value === option.value || o.label === option.label),
);
}
export const defaultProps: Partial<AutoCompleteProps> = {
enabled: true,
readOnly: false,
autoFocus: false,
multi: false,
required: false,
validationStatus: "none",
creatable: false,
updateState: noop,
onDidChange: noop,
onFocus: noop,
onBlur: noop,
onItemCreated: noop,
initiallyOpen: false,
};
export const AutoComplete = forwardRef(function AutoComplete(
{
id,
initialValue,
value,
enabled = defaultProps.enabled,
placeholder,
updateState = defaultProps.updateState,
validationStatus = defaultProps.validationStatus,
onDidChange = defaultProps.onDidChange,
onFocus = defaultProps.onFocus,
onBlur = defaultProps.onBlur,
onItemCreated = defaultProps.onItemCreated,
registerComponentApi,
emptyListTemplate,
style,
className,
children,
readOnly = defaultProps.readOnly,
autoFocus = defaultProps.autoFocus,
dropdownHeight,
multi = defaultProps.multi,
required = defaultProps.required,
creatable = defaultProps.creatable,
optionRenderer,
initiallyOpen = defaultProps.initiallyOpen,
...rest
}: AutoCompleteProps,
forwardedRef: ForwardedRef<HTMLDivElement>,
) {
const [referenceElement, setReferenceElement] = useState<HTMLElement | null>(null);
const inputRef = useRef<HTMLInputElement>(null);
const [open, setOpen] = useState(initiallyOpen);
const [options, setOptions] = useState(new Set<Option>());
const [inputValue, setInputValue] = useState("");
const { root } = useTheme();
const [width, setWidth] = useState(0);
const observer = useRef<ResizeObserver>();
const [searchTerm, setSearchTerm] = useState("");
const [isFocused, setIsFocused] = useState(false);
const [selectedIndex, setSelectedIndex] = useState(-1);
// Filter options based on search term
const filteredOptions = useMemo(() => {
if (!searchTerm || searchTerm.trim() === "") {
return Array.from(options);
}
const searchLower = searchTerm.toLowerCase();
return Array.from(options).filter((option) => {
const extendedValue =
option.value + " " + option.label + " " + (option.keywords || []).join(" ");
return extendedValue.toLowerCase().includes(searchLower);
});
}, [options, searchTerm]);
// Check if we should show creatable item
const shouldShowCreatable = useMemo(() => {
if (!creatable || !searchTerm || searchTerm.trim() === "") return false;
// Check if the search term already exists as an option
const searchTermExists = Array.from(options).some(
(option) => option.value === searchTerm || option.label === searchTerm,
);
if (searchTermExists) return false;
// Check if it's already selected
if (Array.isArray(value) && value.includes(searchTerm)) return false;
if (value === searchTerm) return false;
// Only show creatable if there are no matching filtered options
return filteredOptions.length === 0;
}, [creatable, searchTerm, options, value, filteredOptions]);
// Set initial state based on the initialValue prop
useEffect(() => {
if (initialValue !== undefined) {
updateState({ value: initialValue || [] }, { initial: true });
}
}, [initialValue, updateState]);
// Observe the size of the reference element
useEffect(() => {
const current = referenceElement;
observer.current?.disconnect();
if (current) {
observer.current = new ResizeObserver(() => setWidth(current.clientWidth));
observer.current.observe(current);
}
return () => {
observer.current?.disconnect();
};
}, [referenceElement]);
const selectedValue = useMemo(() => {
const optionsArray = Array.from(options);
if (Array.isArray(value)) {
if (value.length === 0) return [];
return optionsArray.filter((o) => value.includes(`${o.value}`));
}
return optionsArray.find((o) => `${o.value}` === `${value}`);
}, [value, options]);
const toggleOption = useCallback(
(selectedItem: string) => {
if (selectedItem === "") return;
// Check if the option is enabled
const option = Array.from(options).find((opt) => opt.value === selectedItem);
if (option && option.enabled === false) return;
const newSelectedValue = multi
? Array.isArray(value)
? value.includes(selectedItem)
? value.filter((v) => v !== selectedItem)
: [...value, selectedItem]
: [selectedItem]
: selectedItem === value
? null
: selectedItem;
updateState({ value: newSelectedValue });
onDidChange(newSelectedValue);
if (multi) {
setInputValue("");
setSearchTerm("");
// Keep dropdown open for multi-select
} else {
// Close dropdown for single select
setOpen(false);
setSearchTerm("");
}
inputRef.current?.focus();
},
[multi, value, updateState, onDidChange, options],
);
useEffect(() => {
if (!Array.isArray(selectedValue)) {
setInputValue(selectedValue?.label || "");
setSearchTerm("");
}
}, [multi, selectedValue]);
// Clear selected value
const clearValue = useCallback(() => {
const newValue = multi ? [] : "";
setInputValue("");
updateState({ value: newValue });
onDidChange(newValue);
}, [multi, updateState, onDidChange]);
const onOptionAdd = useCallback((option: Option) => {
setOptions((prev) => {
const newSet = new Set(prev);
// Remove old version if exists, then add the new one to ensure updates
const existing = Array.from(prev).find((opt) => opt.value === option.value);
if (existing) {
newSet.delete(existing);
}
newSet.add(option);
return newSet;
});
}, []);
const onOptionRemove = useCallback((option: Option) => {
setOptions((prev) => {
const optionsSet = new Set(prev);
optionsSet.delete(option);
return optionsSet;
});
}, []);
// Combined list of all items (creatable + filtered options)
const allItems = useMemo(() => {
const items = [];
if (shouldShowCreatable) {
items.push({ type: "creatable", value: searchTerm, label: `Create "${searchTerm}"` });
}
filteredOptions.forEach((option) => {
items.push({ type: "option", ...option });
});
return items;
}, [shouldShowCreatable, searchTerm, filteredOptions]);
// Helper functions to find next/previous enabled option
const findNextEnabledIndex = useCallback(
(currentIndex: number) => {
for (let i = currentIndex + 1; i < allItems.length; i++) {
const item = allItems[i];
if (item.type === "creatable" || item.enabled !== false) {
return i;
}
}
// Wrap around to beginning
for (let i = 0; i <= currentIndex; i++) {
const item = allItems[i];
if (item.type === "creatable" || item.enabled !== false) {
return i;
}
}
return -1;
},
[allItems],
);
const findPreviousEnabledIndex = useCallback(
(currentIndex: number) => {
for (let i = currentIndex - 1; i >= 0; i--) {
const item = allItems[i];
if (item.type === "creatable" || item.enabled !== false) {
return i;
}
}
// Wrap around to end
for (let i = allItems.length - 1; i >= currentIndex; i--) {
const item = allItems[i];
if (item.type === "creatable" || item.enabled !== false) {
return i;
}
}
return -1;
},
[allItems],
);
// Reset selected index when options change, but select matching option when dropdown opens
useEffect(() => {
if (!open) {
setSelectedIndex(-1);
} else if (!multi && open && inputValue) {
// For single-select, when dropdown opens and there's an input value, try to find and select the matching option
const matchingIndex = allItems.findIndex((item) => {
if (item.type === "creatable") return false;
return (
item.label?.toLowerCase() === inputValue.toLowerCase() ||
item.value?.toLowerCase() === inputValue.toLowerCase()
);
});
if (matchingIndex !== -1) {
setSelectedIndex(matchingIndex);
} else {
setSelectedIndex(-1);
}
} else if (!multi && open && !inputValue) {
setSelectedIndex(-1);
}
// For multi-select, don't reset selectedIndex when dropdown is open - preserve keyboard navigation
}, [allItems, multi, open, inputValue]);
// Keyboard navigation
const handleKeyDown = useCallback(
(event: React.KeyboardEvent) => {
if (!open) return;
switch (event.key) {
case "ArrowDown":
event.preventDefault();
setSelectedIndex((prev) => {
const nextIndex = findNextEnabledIndex(prev);
return nextIndex !== -1 ? nextIndex : prev;
});
break;
case "ArrowUp":
event.preventDefault();
setSelectedIndex((prev) => {
const prevIndex = findPreviousEnabledIndex(prev);
return prevIndex !== -1 ? prevIndex : prev;
});
break;
case "Enter":
event.preventDefault();
if (selectedIndex >= 0 && selectedIndex < allItems.length) {
const selectedItem = allItems[selectedIndex];
if (selectedItem.type === "creatable") {
const newOption = { value: searchTerm, label: searchTerm, enabled: true };
onOptionAdd(newOption);
onItemCreated(searchTerm);
toggleOption(searchTerm);
} else if (selectedItem.enabled !== false) {
// Only toggle if the option is enabled
toggleOption(selectedItem.value);
}
} else if (allItems.length === 1) {
// If there's only one item (creatable or regular) and no selection, select it
const singleItem = allItems[0];
if (singleItem.type === "creatable") {
const newOption = { value: searchTerm, label: searchTerm, enabled: true };
onOptionAdd(newOption);
onItemCreated(searchTerm);
toggleOption(searchTerm);
} else if (singleItem.enabled !== false) {
// Only toggle if the option is enabled
toggleOption(singleItem.value);
}
}
break;
case "Escape":
event.preventDefault();
setOpen(false);
break;
}
},
[
open,
selectedIndex,
allItems,
searchTerm,
onOptionAdd,
onItemCreated,
toggleOption,
setOpen,
findNextEnabledIndex,
findPreviousEnabledIndex,
],
);
// Render the "empty list" message
const emptyListNode = useMemo(
() =>
emptyListTemplate ?? (
<div className={styles.autoCompleteEmpty}>
<Icon name="noresult" />
<span>List is empty</span>
</div>
),
[emptyListTemplate],
);
// Register component API for external interactions
const focus = useCallback(() => {
inputRef?.current?.focus();
}, [inputRef]);
const setValue = useEvent((newValue: string | string[]) => {
updateState({ value: newValue });
});
useEffect(() => {
registerComponentApi?.({
focus,
setValue,
});
}, [focus, registerComponentApi, setValue]);
const optionContextValue = useMemo(
() => ({
onOptionAdd,
onOptionRemove,
}),
[onOptionAdd, onOptionRemove],
);
const autoCompleteContextValue = useMemo(() => {
return {
multi,
value,
onChange: toggleOption,
options,
inputValue,
searchTerm,
open,
setOpen,
setSelectedIndex,
optionRenderer,
};
}, [
inputValue,
searchTerm,
multi,
options,
toggleOption,
value,
open,
setOpen,
setSelectedIndex,
optionRenderer,
]);
return (
<AutoCompleteContext.Provider value={autoCompleteContextValue}>
<OptionContext.Provider value={optionContextValue}>
<OptionTypeProvider Component={HiddenOption}>
<Popover
open={open}
onOpenChange={(isOpen) => {
if (readOnly) return;
setOpen(isOpen);
if (!isOpen) {
// Reset highlighted option when dropdown closes
setSelectedIndex(-1);
}
}}
modal={false}
>
<PopoverTrigger asChild ref={setReferenceElement}>
<div
ref={forwardedRef}
style={style}
data-part-id={PART_LIST_WRAPPER}
className={classnames(
className,
styles.badgeListWrapper,
styles[validationStatus],
{
[styles.disabled]: !enabled,
[styles.focused]: isFocused,
},
)}
aria-expanded={open}
onClick={(event) => {
if (readOnly) return;
// In multi mode, only open the dropdown, don't toggle
// In single mode, toggle as usual
if (multi && open) {
return; // Already open, don't close
}
event.stopPropagation();
setOpen((prev) => !prev);
}}
>
{Array.isArray(selectedValue) && selectedValue.length > 0 && (
<div className={styles.badgeList}>
{selectedValue.map((v, index) => (
<span key={index} className={styles.badge}>
{v?.label}
{!readOnly && (
<Icon
name="close"
size="sm"
onClick={(event) => {
event.stopPropagation();
toggleOption(v.value);
}}
/>
)}
</span>
))}
</div>
)}
<div className={styles.inputWrapper}>
<input
{...rest}
role="combobox"
id={id}
ref={inputRef}
onFocus={(ev) => {
setIsFocused(true);
onFocus(ev);
}}
onBlur={(ev) => {
if (inputValue === "" && !multi) {
clearValue();
} else {
if (!Array.isArray(selectedValue) && selectedValue) {
setInputValue(selectedValue?.label);
} else {
setInputValue("");
}
}
onBlur(ev);
setIsFocused(false);
}}
onKeyDown={(event) => {
if (readOnly) return;
// Handle opening dropdown
if (event.key === "ArrowDown" && !open) {
setOpen(true);
return;
}
// Handle keyboard navigation when dropdown is open
if (open) {
handleKeyDown(event);
} else if (event.key === "Enter") {
setOpen(true);
}
}}
data-part-id={PART_INPUT}
readOnly={readOnly}
autoFocus={autoFocus}
aria-autocomplete="list"
value={inputValue}
disabled={!enabled}
onChange={(event) => {
setOpen(true);
setInputValue(event.target.value);
setSearchTerm(event.target.value);
}}
placeholder={!readOnly ? placeholder : ""}
className={styles.commandInput}
/>
<div className={styles.actions}>
{value?.length > 0 && enabled && !readOnly && (
<span
className={styles.action}
onClick={(event) => {
event.stopPropagation();
clearValue();
}}
>
<Icon name="close" />
</span>
)}
<span
className={styles.action}
onClick={() => {
if (readOnly) return;
setOpen(!open);
// Focus the input after opening dropdown
inputRef.current?.focus();
}}
>
<Icon name="chevrondown" />
</span>
</div>
</div>
</div>
</PopoverTrigger>
{open && (
<Portal container={root}>
<PopoverContent
style={{ width, height: dropdownHeight }}
className={styles.popoverContent}
align="start"
onOpenAutoFocus={(e) => e.preventDefault()}
>
<div
role="listbox"
className={styles.commandList}
style={{ height: dropdownHeight }}
>
{filteredOptions.length === 0 && !shouldShowCreatable && (
<div>{emptyListNode}</div>
)}
{shouldShowCreatable && (
<CreatableItem
onNewItem={onItemCreated}
isHighlighted={selectedIndex === 0}
/>
)}
<div>
{filteredOptions.map(({ value, label, enabled, keywords }, index) => {
const itemIndex = shouldShowCreatable ? index + 1 : index;
return (
<AutoCompleteOption
key={value}
value={value}
label={label}
enabled={enabled}
keywords={keywords}
readOnly={readOnly}
isHighlighted={selectedIndex === itemIndex}
itemIndex={itemIndex}
/>
);
})}
</div>
</div>
</PopoverContent>
</Portal>
)}
</Popover>
{children}
</OptionTypeProvider>
</OptionContext.Provider>
</AutoCompleteContext.Provider>
);
});
type CreatableItemProps = {
onNewItem: (item: string) => void;
isHighlighted?: boolean;
};
function CreatableItem({ onNewItem, isHighlighted = false }: CreatableItemProps) {
const { value, options, searchTerm, onChange, setOpen, setSelectedIndex, multi } =
useAutoComplete();
const { onOptionAdd } = useOption();
if (
isOptionsExist(options, [{ value: searchTerm, label: searchTerm }]) ||
(Array.isArray(value) && value?.find((s) => s === searchTerm)) ||
searchTerm === value
) {
return <span style={{ display: "none" }} />;
}
const handleClick = () => {
const newOption = { value: searchTerm, label: searchTerm, enabled: true };
onOptionAdd(newOption);
onNewItem?.(searchTerm);
onChange(searchTerm);
// Only close dropdown for single select mode
if (!multi) {
setOpen(false);
}
};
const Item = (
<div
className={classnames(styles.autoCompleteOption, {
[styles.highlighted]: isHighlighted,
})}
onMouseDown={(e) => {
e.preventDefault();
e.stopPropagation();
}}
onMouseEnter={() => {
if (setSelectedIndex) {
setSelectedIndex(0); // CreatableItem is always at index 0
}
}}
onClick={handleClick}
role="option"
aria-selected={false}
>
{`Create "${searchTerm}"`}
</div>
);
// For normal creatable
if (searchTerm.length > 0) {
return Item;
}
return <span style={{ display: "none" }} />;
}
function AutoCompleteOption(option: Option & { isHighlighted?: boolean; itemIndex?: number }) {
const {
value,
label,
enabled = true,
readOnly,
children,
isHighlighted = false,
itemIndex,
} = option;
const id = useId();
const {
value: selectedValue,
onChange,
multi,
setOpen,
setSelectedIndex,
optionRenderer,
} = useAutoComplete();
const selected = multi ? selectedValue?.includes(value) : selectedValue === value;
const handleClick = () => {
if (!readOnly && enabled) {
onChange(value);
// Only close dropdown for single select mode
if (!multi) {
setOpen(false);
}
}
};
return (
<div
id={id}
role="option"
aria-disabled={!enabled}
aria-selected={selected}
className={classnames(styles.autoCompleteOption, {
[styles.disabledOption]: !enabled,
[styles.highlighted]: isHighlighted,
})}
onMouseDown={(e) => {
e.preventDefault();
e.stopPropagation();
}}
onMouseEnter={() => {
if (itemIndex !== undefined && setSelectedIndex && enabled) {
setSelectedIndex(itemIndex);
}
}}
onClick={handleClick}
>
{children ? (
<>
<div className={styles.autoCompleteOptionContent}>{children}</div>
{selected && <Icon name="checkmark" />}
</>
) : optionRenderer ? (
optionRenderer({ label, value, enabled }, selectedValue as any, false)
) : (
<>
<div className={styles.autoCompleteOptionContent}>{label}</div>
{selected && <Icon name="checkmark" />}
</>
)}
</div>
);
}
```