This is page 96 of 186. Use http://codebase.md/xmlui-org/xmlui/tools/vscode/resources/assets/img/bg-iphone-14-pro.jpg?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .changeset
│ └── config.json
├── .eslintrc.cjs
├── .github
│ ├── build-checklist.png
│ ├── ISSUE_TEMPLATE
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ └── workflows
│ ├── deploy-blog-optimized.yml
│ ├── deploy-blog-swa.yml
│ ├── deploy-blog.yml
│ ├── deploy-docs-optimized.yml
│ ├── deploy-docs.yml
│ ├── prepare-versions.yml
│ ├── release-packages.yml
│ ├── run-all-tests.yml
│ └── run-smoke-tests.yml
├── .gitignore
├── .prettierrc.js
├── .vscode
│ ├── launch.json
│ └── settings.json
├── blog
│ ├── .gitignore
│ ├── .gitkeep
│ ├── CHANGELOG.md
│ ├── extensions.ts
│ ├── index.html
│ ├── index.ts
│ ├── package.json
│ ├── public
│ │ ├── blog
│ │ │ ├── images
│ │ │ │ ├── an-advanced-codefence.gif
│ │ │ │ ├── an-advanced-codefence.mp4
│ │ │ │ ├── blog-page-component.png
│ │ │ │ ├── blog-scrabble.png
│ │ │ │ ├── codefence-runner.png
│ │ │ │ ├── integrated-blog-search.png
│ │ │ │ ├── lorem-ipsum.png
│ │ │ │ ├── playground-checkbox-source.png
│ │ │ │ ├── playground.png
│ │ │ │ ├── use-xmlui-mcp-to-find-a-howto.png
│ │ │ │ └── xmlui-demo-gallery.png
│ │ │ ├── introducing-xmlui.md
│ │ │ ├── lorem-ipsum.md
│ │ │ ├── newest-post.md
│ │ │ ├── older-post.md
│ │ │ ├── xmlui-playground.md
│ │ │ └── xmlui-powered-blog.md
│ │ ├── mockServiceWorker.js
│ │ ├── resources
│ │ │ ├── favicon.ico
│ │ │ ├── files
│ │ │ │ └── for-download
│ │ │ │ └── xmlui
│ │ │ │ └── xmlui-standalone.umd.js
│ │ │ ├── github.svg
│ │ │ ├── llms.txt
│ │ │ ├── logo-dark.svg
│ │ │ ├── logo.svg
│ │ │ ├── pg-popout.svg
│ │ │ ├── rss.svg
│ │ │ └── xmlui-logo.svg
│ │ ├── serve.json
│ │ ├── staticwebapp.config.json
│ │ └── web.config
│ ├── scripts
│ │ ├── download-latest-xmlui.js
│ │ ├── generate-rss.js
│ │ ├── get-releases.js
│ │ └── utils.js
│ ├── src
│ │ ├── components
│ │ │ ├── BlogOverview.xmlui
│ │ │ ├── BlogPage.xmlui
│ │ │ └── PageNotFound.xmlui
│ │ ├── config.ts
│ │ ├── Main.xmlui
│ │ └── themes
│ │ └── blog-theme.ts
│ └── tsconfig.json
├── CONTRIBUTING.md
├── docs
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── ComponentRefLinks.txt
│ ├── content
│ │ ├── _meta.json
│ │ ├── components
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── APICall.md
│ │ │ ├── App.md
│ │ │ ├── AppHeader.md
│ │ │ ├── AppState.md
│ │ │ ├── AutoComplete.md
│ │ │ ├── Avatar.md
│ │ │ ├── Backdrop.md
│ │ │ ├── Badge.md
│ │ │ ├── BarChart.md
│ │ │ ├── Bookmark.md
│ │ │ ├── Breakout.md
│ │ │ ├── Button.md
│ │ │ ├── Card.md
│ │ │ ├── Carousel.md
│ │ │ ├── ChangeListener.md
│ │ │ ├── Checkbox.md
│ │ │ ├── CHStack.md
│ │ │ ├── ColorPicker.md
│ │ │ ├── Column.md
│ │ │ ├── ContentSeparator.md
│ │ │ ├── CVStack.md
│ │ │ ├── DataSource.md
│ │ │ ├── DateInput.md
│ │ │ ├── DatePicker.md
│ │ │ ├── DonutChart.md
│ │ │ ├── DropdownMenu.md
│ │ │ ├── EmojiSelector.md
│ │ │ ├── ExpandableItem.md
│ │ │ ├── FileInput.md
│ │ │ ├── FileUploadDropZone.md
│ │ │ ├── FlowLayout.md
│ │ │ ├── Footer.md
│ │ │ ├── Form.md
│ │ │ ├── FormItem.md
│ │ │ ├── FormSection.md
│ │ │ ├── Fragment.md
│ │ │ ├── H1.md
│ │ │ ├── H2.md
│ │ │ ├── H3.md
│ │ │ ├── H4.md
│ │ │ ├── H5.md
│ │ │ ├── H6.md
│ │ │ ├── Heading.md
│ │ │ ├── HSplitter.md
│ │ │ ├── HStack.md
│ │ │ ├── Icon.md
│ │ │ ├── IFrame.md
│ │ │ ├── Image.md
│ │ │ ├── Items.md
│ │ │ ├── LabelList.md
│ │ │ ├── Legend.md
│ │ │ ├── LineChart.md
│ │ │ ├── Link.md
│ │ │ ├── List.md
│ │ │ ├── Logo.md
│ │ │ ├── Markdown.md
│ │ │ ├── MenuItem.md
│ │ │ ├── MenuSeparator.md
│ │ │ ├── ModalDialog.md
│ │ │ ├── NavGroup.md
│ │ │ ├── NavLink.md
│ │ │ ├── NavPanel.md
│ │ │ ├── NoResult.md
│ │ │ ├── NumberBox.md
│ │ │ ├── Option.md
│ │ │ ├── Page.md
│ │ │ ├── PageMetaTitle.md
│ │ │ ├── Pages.md
│ │ │ ├── Pagination.md
│ │ │ ├── PasswordInput.md
│ │ │ ├── PieChart.md
│ │ │ ├── ProgressBar.md
│ │ │ ├── Queue.md
│ │ │ ├── RadioGroup.md
│ │ │ ├── RealTimeAdapter.md
│ │ │ ├── Redirect.md
│ │ │ ├── Select.md
│ │ │ ├── Slider.md
│ │ │ ├── Slot.md
│ │ │ ├── SpaceFiller.md
│ │ │ ├── Spinner.md
│ │ │ ├── Splitter.md
│ │ │ ├── Stack.md
│ │ │ ├── StickyBox.md
│ │ │ ├── SubMenuItem.md
│ │ │ ├── Switch.md
│ │ │ ├── TabItem.md
│ │ │ ├── Table.md
│ │ │ ├── TableOfContents.md
│ │ │ ├── Tabs.md
│ │ │ ├── Text.md
│ │ │ ├── TextArea.md
│ │ │ ├── TextBox.md
│ │ │ ├── Theme.md
│ │ │ ├── TimeInput.md
│ │ │ ├── Timer.md
│ │ │ ├── ToneChangerButton.md
│ │ │ ├── ToneSwitch.md
│ │ │ ├── Tooltip.md
│ │ │ ├── Tree.md
│ │ │ ├── VSplitter.md
│ │ │ ├── VStack.md
│ │ │ ├── xmlui-animations
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── Animation.md
│ │ │ │ ├── FadeAnimation.md
│ │ │ │ ├── FadeInAnimation.md
│ │ │ │ ├── FadeOutAnimation.md
│ │ │ │ ├── ScaleAnimation.md
│ │ │ │ └── SlideInAnimation.md
│ │ │ ├── xmlui-pdf
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Pdf.md
│ │ │ ├── xmlui-spreadsheet
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Spreadsheet.md
│ │ │ └── xmlui-website-blocks
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── Carousel.md
│ │ │ ├── HelloMd.md
│ │ │ ├── HeroSection.md
│ │ │ └── ScrollToTop.md
│ │ └── extensions
│ │ ├── _meta.json
│ │ ├── xmlui-animations
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── Animation.md
│ │ │ ├── FadeAnimation.md
│ │ │ ├── FadeInAnimation.md
│ │ │ ├── FadeOutAnimation.md
│ │ │ ├── ScaleAnimation.md
│ │ │ └── SlideInAnimation.md
│ │ └── xmlui-website-blocks
│ │ ├── _meta.json
│ │ ├── _overview.md
│ │ ├── Carousel.md
│ │ ├── FancyButton.md
│ │ ├── HeroSection.md
│ │ └── ScrollToTop.md
│ ├── extensions.ts
│ ├── index.html
│ ├── index.ts
│ ├── package.json
│ ├── public
│ │ ├── feed.rss
│ │ ├── mockServiceWorker.js
│ │ ├── pages
│ │ │ ├── _meta.json
│ │ │ ├── app-structure.md
│ │ │ ├── build-editor-component.md
│ │ │ ├── build-hello-world-component.md
│ │ │ ├── components-intro.md
│ │ │ ├── context-variables.md
│ │ │ ├── forms.md
│ │ │ ├── globals.md
│ │ │ ├── glossary.md
│ │ │ ├── helper-tags.md
│ │ │ ├── hosted-deployment.md
│ │ │ ├── howto
│ │ │ │ ├── assign-a-complex-json-literal-to-a-component-variable.md
│ │ │ │ ├── chain-a-refetch.md
│ │ │ │ ├── control-cache-invalidation.md
│ │ │ │ ├── debounce-user-input-for-api-calls.md
│ │ │ │ ├── debounce-with-changelistener.md
│ │ │ │ ├── debug-a-component.md
│ │ │ │ ├── delay-a-datasource-until-another-datasource-is-ready.md
│ │ │ │ ├── delegate-a-method.md
│ │ │ │ ├── do-custom-form-validation.md
│ │ │ │ ├── expose-a-method-from-a-component.md
│ │ │ │ ├── filter-and-transform-data-from-an-api.md
│ │ │ │ ├── group-items-in-list-by-a-property.md
│ │ │ │ ├── handle-background-operations.md
│ │ │ │ ├── hide-an-element-until-its-datasource-is-ready.md
│ │ │ │ ├── make-a-set-of-equal-width-cards.md
│ │ │ │ ├── make-a-table-responsive.md
│ │ │ │ ├── make-navpanel-width-responsive.md
│ │ │ │ ├── modify-a-value-reported-in-a-column.md
│ │ │ │ ├── paginate-a-list.md
│ │ │ │ ├── pass-data-to-a-modal-dialog.md
│ │ │ │ ├── react-to-button-click-not-keystrokes.md
│ │ │ │ ├── set-the-initial-value-of-a-select-from-fetched-data.md
│ │ │ │ ├── share-a-modaldialog-across-components.md
│ │ │ │ ├── sync-selections-between-table-and-list-views.md
│ │ │ │ ├── update-ui-optimistically.md
│ │ │ │ ├── use-built-in-form-validation.md
│ │ │ │ └── use-the-same-modaldialog-to-add-or-edit.md
│ │ │ ├── howto.md
│ │ │ ├── intro.md
│ │ │ ├── layout.md
│ │ │ ├── markup.md
│ │ │ ├── mcp.md
│ │ │ ├── modal-dialogs.md
│ │ │ ├── news-and-reviews.md
│ │ │ ├── reactive-intro.md
│ │ │ ├── refactoring.md
│ │ │ ├── routing-and-links.md
│ │ │ ├── samples
│ │ │ │ ├── color-palette.xmlui
│ │ │ │ ├── color-values.xmlui
│ │ │ │ ├── shadow-sizes.xmlui
│ │ │ │ ├── spacing-sizes.xmlui
│ │ │ │ ├── swatch.xmlui
│ │ │ │ ├── theme-gallery-brief.xmlui
│ │ │ │ └── theme-gallery.xmlui
│ │ │ ├── scoping.md
│ │ │ ├── scripting.md
│ │ │ ├── styles-and-themes
│ │ │ │ ├── common-units.md
│ │ │ │ ├── layout-props.md
│ │ │ │ ├── theme-variable-defaults.md
│ │ │ │ ├── theme-variables.md
│ │ │ │ └── themes.md
│ │ │ ├── template-properties.md
│ │ │ ├── test.md
│ │ │ ├── tutorial-01.md
│ │ │ ├── tutorial-02.md
│ │ │ ├── tutorial-03.md
│ │ │ ├── tutorial-04.md
│ │ │ ├── tutorial-05.md
│ │ │ ├── tutorial-06.md
│ │ │ ├── tutorial-07.md
│ │ │ ├── tutorial-08.md
│ │ │ ├── tutorial-09.md
│ │ │ ├── tutorial-10.md
│ │ │ ├── tutorial-11.md
│ │ │ ├── tutorial-12.md
│ │ │ ├── universal-properties.md
│ │ │ ├── user-defined-components.md
│ │ │ ├── vscode.md
│ │ │ ├── working-with-markdown.md
│ │ │ ├── working-with-text.md
│ │ │ ├── xmlui-animations
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── Animation.md
│ │ │ │ ├── FadeAnimation.md
│ │ │ │ ├── FadeInAnimation.md
│ │ │ │ ├── FadeOutAnimation.md
│ │ │ │ ├── ScaleAnimation.md
│ │ │ │ └── SlideInAnimation.md
│ │ │ ├── xmlui-charts
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── BarChart.md
│ │ │ │ ├── DonutChart.md
│ │ │ │ ├── LabelList.md
│ │ │ │ ├── Legend.md
│ │ │ │ ├── LineChart.md
│ │ │ │ └── PieChart.md
│ │ │ ├── xmlui-pdf
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Pdf.md
│ │ │ └── xmlui-spreadsheet
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ └── Spreadsheet.md
│ │ ├── resources
│ │ │ ├── devdocs
│ │ │ │ ├── debug-proxy-object-2.png
│ │ │ │ ├── debug-proxy-object.png
│ │ │ │ ├── table_editor_01.png
│ │ │ │ ├── table_editor_02.png
│ │ │ │ ├── table_editor_03.png
│ │ │ │ ├── table_editor_04.png
│ │ │ │ ├── table_editor_05.png
│ │ │ │ ├── table_editor_06.png
│ │ │ │ ├── table_editor_07.png
│ │ │ │ ├── table_editor_08.png
│ │ │ │ ├── table_editor_09.png
│ │ │ │ ├── table_editor_10.png
│ │ │ │ ├── table_editor_11.png
│ │ │ │ ├── table-editor-01.png
│ │ │ │ ├── table-editor-02.png
│ │ │ │ ├── table-editor-03.png
│ │ │ │ ├── table-editor-04.png
│ │ │ │ ├── table-editor-06.png
│ │ │ │ ├── table-editor-07.png
│ │ │ │ ├── table-editor-08.png
│ │ │ │ ├── table-editor-09.png
│ │ │ │ └── xmlui-rendering-of-tiptap-markdown.png
│ │ │ ├── favicon.ico
│ │ │ ├── files
│ │ │ │ ├── clients.json
│ │ │ │ ├── daily-revenue.json
│ │ │ │ ├── dashboard-stats.json
│ │ │ │ ├── demo.xmlui
│ │ │ │ ├── demo.xmlui.xs
│ │ │ │ ├── downloads
│ │ │ │ │ └── downloads.json
│ │ │ │ ├── for-download
│ │ │ │ │ ├── index-with-api.html
│ │ │ │ │ ├── index.html
│ │ │ │ │ ├── mockApi.js
│ │ │ │ │ ├── start-darwin.sh
│ │ │ │ │ ├── start-linux.sh
│ │ │ │ │ ├── start.bat
│ │ │ │ │ └── xmlui
│ │ │ │ │ └── xmlui-standalone.umd.js
│ │ │ │ ├── getting-started
│ │ │ │ │ ├── cl-tutorial-final.zip
│ │ │ │ │ ├── cl-tutorial.zip
│ │ │ │ │ ├── cl-tutorial2.zip
│ │ │ │ │ ├── cl-tutorial3.zip
│ │ │ │ │ ├── cl-tutorial4.zip
│ │ │ │ │ ├── cl-tutorial5.zip
│ │ │ │ │ ├── cl-tutorial6.zip
│ │ │ │ │ ├── getting-started.zip
│ │ │ │ │ ├── hello-xmlui.zip
│ │ │ │ │ ├── xmlui-empty.zip
│ │ │ │ │ └── xmlui-starter.zip
│ │ │ │ ├── howto
│ │ │ │ │ └── component-icons
│ │ │ │ │ └── up-arrow.svg
│ │ │ │ ├── invoices.json
│ │ │ │ ├── monthly-status.json
│ │ │ │ ├── news-and-reviews.json
│ │ │ │ ├── products.json
│ │ │ │ ├── releases.json
│ │ │ │ ├── tutorials
│ │ │ │ │ ├── datasource
│ │ │ │ │ │ └── api.ts
│ │ │ │ │ └── p2do
│ │ │ │ │ ├── api.ts
│ │ │ │ │ └── todo-logo.svg
│ │ │ │ └── xmlui.json
│ │ │ ├── github.svg
│ │ │ ├── images
│ │ │ │ ├── apiaction-tutorial
│ │ │ │ │ ├── add-success.png
│ │ │ │ │ ├── apiaction-param.png
│ │ │ │ │ ├── change-completed.png
│ │ │ │ │ ├── change-in-progress.png
│ │ │ │ │ ├── confirm-delete.png
│ │ │ │ │ ├── data-error.png
│ │ │ │ │ ├── data-progress.png
│ │ │ │ │ ├── data-success.png
│ │ │ │ │ ├── display-1.png
│ │ │ │ │ ├── item-deleted.png
│ │ │ │ │ ├── item-updated.png
│ │ │ │ │ ├── missing-api-key.png
│ │ │ │ │ ├── new-item-added.png
│ │ │ │ │ └── test-message.png
│ │ │ │ ├── chat-api
│ │ │ │ │ └── domain-model.svg
│ │ │ │ ├── components
│ │ │ │ │ ├── image
│ │ │ │ │ │ └── breakfast.jpg
│ │ │ │ │ ├── markdown
│ │ │ │ │ │ └── colors.png
│ │ │ │ │ └── modal
│ │ │ │ │ ├── deep_link_dialog_1.jpg
│ │ │ │ │ └── deep_link_dialog_2.jpg
│ │ │ │ ├── create-apps
│ │ │ │ │ ├── collapsed-vertical.png
│ │ │ │ │ ├── using-forms-warning-dialog.png
│ │ │ │ │ └── using-forms.png
│ │ │ │ ├── datasource-tutorial
│ │ │ │ │ ├── data-with-header.png
│ │ │ │ │ ├── filtered-data.png
│ │ │ │ │ ├── filtered-items.png
│ │ │ │ │ ├── initial-page-items.png
│ │ │ │ │ ├── list-items.png
│ │ │ │ │ ├── next-page-items.png
│ │ │ │ │ ├── no-data.png
│ │ │ │ │ ├── pagination-1.jpg
│ │ │ │ │ ├── pagination-1.png
│ │ │ │ │ ├── polling-1.png
│ │ │ │ │ ├── refetch-data.png
│ │ │ │ │ ├── slow-loading.png
│ │ │ │ │ ├── test-message.png
│ │ │ │ │ ├── Thumbs.db
│ │ │ │ │ ├── unconventional-data.png
│ │ │ │ │ └── unfiltered-items.png
│ │ │ │ ├── flower.jpg
│ │ │ │ ├── get-started
│ │ │ │ │ ├── add-new-contact.png
│ │ │ │ │ ├── app-modified.png
│ │ │ │ │ ├── app-start.png
│ │ │ │ │ ├── app-with-boxes.png
│ │ │ │ │ ├── app-with-toast.png
│ │ │ │ │ ├── boilerplate-structure.png
│ │ │ │ │ ├── cl-initial.png
│ │ │ │ │ ├── cl-start.png
│ │ │ │ │ ├── contact-counts.png
│ │ │ │ │ ├── contact-dialog-title.png
│ │ │ │ │ ├── contact-dialog.png
│ │ │ │ │ ├── contact-menus.png
│ │ │ │ │ ├── contact-predicates.png
│ │ │ │ │ ├── context-menu.png
│ │ │ │ │ ├── dashboard-numbers.png
│ │ │ │ │ ├── default-contact-list.png
│ │ │ │ │ ├── delete-contact.png
│ │ │ │ │ ├── delete-task.png
│ │ │ │ │ ├── detailed-template.png
│ │ │ │ │ ├── edit-contact-details.png
│ │ │ │ │ ├── edited-contact-saved.png
│ │ │ │ │ ├── empty-sections.png
│ │ │ │ │ ├── filter-completed.png
│ │ │ │ │ ├── fullwidth-desktop.png
│ │ │ │ │ ├── fullwidth-mobile.png
│ │ │ │ │ ├── initial-table.png
│ │ │ │ │ ├── items-and-badges.png
│ │ │ │ │ ├── loading-message.png
│ │ │ │ │ ├── new-contact-button.png
│ │ │ │ │ ├── new-contact-saved.png
│ │ │ │ │ ├── no-empty-sections.png
│ │ │ │ │ ├── personal-todo-initial.png
│ │ │ │ │ ├── piechart.png
│ │ │ │ │ ├── review-today.png
│ │ │ │ │ ├── rudimentary-dashboard.png
│ │ │ │ │ ├── section-collapsed.png
│ │ │ │ │ ├── sectioned-items.png
│ │ │ │ │ ├── sections-ordered.png
│ │ │ │ │ ├── spacex-list-with-links.png
│ │ │ │ │ ├── spacex-list.png
│ │ │ │ │ ├── start-personal-todo-1.png
│ │ │ │ │ ├── submit-new-contact.png
│ │ │ │ │ ├── submit-new-task.png
│ │ │ │ │ ├── syntax-highlighting.png
│ │ │ │ │ ├── table-with-badge.png
│ │ │ │ │ ├── template-with-card.png
│ │ │ │ │ ├── test-emulated-api.png
│ │ │ │ │ ├── Thumbs.db
│ │ │ │ │ ├── todo-logo.png
│ │ │ │ │ └── xmlui-tools.png
│ │ │ │ ├── HelloApp.png
│ │ │ │ ├── HelloApp2.png
│ │ │ │ ├── logos
│ │ │ │ │ ├── xmlui1.svg
│ │ │ │ │ ├── xmlui2.svg
│ │ │ │ │ ├── xmlui3.svg
│ │ │ │ │ ├── xmlui4.svg
│ │ │ │ │ ├── xmlui5.svg
│ │ │ │ │ ├── xmlui6.svg
│ │ │ │ │ └── xmlui7.svg
│ │ │ │ ├── pdf
│ │ │ │ │ └── dummy-pdf.jpg
│ │ │ │ ├── rendering-engine
│ │ │ │ │ ├── AppEngine-flow.svg
│ │ │ │ │ ├── Component.svg
│ │ │ │ │ ├── CompoundComponent.svg
│ │ │ │ │ ├── RootComponent.svg
│ │ │ │ │ └── tree-with-containers.svg
│ │ │ │ ├── reviewers-guide
│ │ │ │ │ ├── AppEngine-flow.svg
│ │ │ │ │ └── incbutton-in-action.png
│ │ │ │ ├── tools
│ │ │ │ │ └── boilerplate-structure.png
│ │ │ │ ├── try.svg
│ │ │ │ ├── tutorial
│ │ │ │ │ ├── app-chat-history.png
│ │ │ │ │ ├── app-content-placeholder.png
│ │ │ │ │ ├── app-header-and-content.png
│ │ │ │ │ ├── app-links-channel-selected.png
│ │ │ │ │ ├── app-links-click.png
│ │ │ │ │ ├── app-navigation.png
│ │ │ │ │ ├── finished-ex01.png
│ │ │ │ │ ├── finished-ex02.png
│ │ │ │ │ ├── hello.png
│ │ │ │ │ ├── splash-screen-advanced.png
│ │ │ │ │ ├── splash-screen-after-click.png
│ │ │ │ │ ├── splash-screen-centered.png
│ │ │ │ │ ├── splash-screen-events.png
│ │ │ │ │ ├── splash-screen-expression.png
│ │ │ │ │ ├── splash-screen-reuse-after.png
│ │ │ │ │ ├── splash-screen-reuse-before.png
│ │ │ │ │ └── splash-screen.png
│ │ │ │ └── tutorial-01.png
│ │ │ ├── llms.txt
│ │ │ ├── logo-dark.svg
│ │ │ ├── logo.svg
│ │ │ ├── pg-popout.svg
│ │ │ └── xmlui-logo.svg
│ │ ├── serve.json
│ │ └── web.config
│ ├── scripts
│ │ ├── download-latest-xmlui.js
│ │ ├── generate-rss.js
│ │ ├── get-releases.js
│ │ └── utils.js
│ ├── src
│ │ ├── components
│ │ │ ├── BlogOverview.xmlui
│ │ │ ├── BlogPage.xmlui
│ │ │ ├── Boxes.xmlui
│ │ │ ├── Breadcrumb.xmlui
│ │ │ ├── ChangeLog.xmlui
│ │ │ ├── ColorPalette.xmlui
│ │ │ ├── DocumentLinks.xmlui
│ │ │ ├── DocumentPage.xmlui
│ │ │ ├── DocumentPageNoTOC.xmlui
│ │ │ ├── Icons.xmlui
│ │ │ ├── IncButton.xmlui
│ │ │ ├── IncButton2.xmlui
│ │ │ ├── NameValue.xmlui
│ │ │ ├── PageNotFound.xmlui
│ │ │ ├── PaletteItem.xmlui
│ │ │ ├── Palettes.xmlui
│ │ │ ├── SectionHeader.xmlui
│ │ │ ├── TBD.xmlui
│ │ │ ├── Test.xmlui
│ │ │ ├── ThemesIntro.xmlui
│ │ │ ├── ThousandThemes.xmlui
│ │ │ ├── TubeStops.xmlui
│ │ │ ├── TubeStops.xmlui.xs
│ │ │ └── TwoColumnCode.xmlui
│ │ ├── config.ts
│ │ ├── Main.xmlui
│ │ └── themes
│ │ ├── docs-theme.ts
│ │ ├── earthtone.ts
│ │ ├── xmlui-gray-on-default.ts
│ │ ├── xmlui-green-on-default.ts
│ │ └── xmlui-orange-on-default.ts
│ └── tsconfig.json
├── LICENSE
├── package-lock.json
├── package.json
├── packages
│ ├── tsconfig.json
│ ├── xmlui-animations
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── Animation.tsx
│ │ ├── AnimationNative.tsx
│ │ ├── FadeAnimation.tsx
│ │ ├── FadeInAnimation.tsx
│ │ ├── FadeOutAnimation.tsx
│ │ ├── index.tsx
│ │ ├── ScaleAnimation.tsx
│ │ └── SlideInAnimation.tsx
│ ├── xmlui-devtools
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ ├── src
│ │ │ ├── devtools
│ │ │ │ ├── DevTools.tsx
│ │ │ │ ├── DevToolsNative.module.scss
│ │ │ │ ├── DevToolsNative.tsx
│ │ │ │ ├── ModalDialog.module.scss
│ │ │ │ ├── ModalDialog.tsx
│ │ │ │ ├── ModalVisibilityContext.tsx
│ │ │ │ ├── Tooltip.module.scss
│ │ │ │ ├── Tooltip.tsx
│ │ │ │ └── utils.ts
│ │ │ ├── editor
│ │ │ │ └── Editor.tsx
│ │ │ └── index.tsx
│ │ └── vite.config-overrides.ts
│ ├── xmlui-hello-world
│ │ ├── .gitignore
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── HelloWorld.module.scss
│ │ ├── HelloWorld.tsx
│ │ ├── HelloWorldNative.tsx
│ │ └── index.tsx
│ ├── xmlui-os-frames
│ │ ├── .gitignore
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── index.tsx
│ │ ├── IPhoneFrame.module.scss
│ │ ├── IPhoneFrame.tsx
│ │ ├── MacOSAppFrame.module.scss
│ │ ├── MacOSAppFrame.tsx
│ │ ├── WindowsAppFrame.module.scss
│ │ └── WindowsAppFrame.tsx
│ ├── xmlui-pdf
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ ├── components
│ │ │ │ └── Pdf.xmlui
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── index.tsx
│ │ ├── LazyPdfNative.tsx
│ │ ├── Pdf.module.scss
│ │ └── Pdf.tsx
│ ├── xmlui-playground
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── hooks
│ │ │ ├── usePlayground.ts
│ │ │ └── useToast.ts
│ │ ├── index.tsx
│ │ ├── playground
│ │ │ ├── Box.module.scss
│ │ │ ├── Box.tsx
│ │ │ ├── CodeSelector.tsx
│ │ │ ├── ConfirmationDialog.module.scss
│ │ │ ├── ConfirmationDialog.tsx
│ │ │ ├── Editor.tsx
│ │ │ ├── Header.module.scss
│ │ │ ├── Header.tsx
│ │ │ ├── Playground.tsx
│ │ │ ├── PlaygroundContent.module.scss
│ │ │ ├── PlaygroundContent.tsx
│ │ │ ├── PlaygroundNative.module.scss
│ │ │ ├── PlaygroundNative.tsx
│ │ │ ├── Preview.module.scss
│ │ │ ├── Preview.tsx
│ │ │ ├── Select.module.scss
│ │ │ ├── StandalonePlayground.tsx
│ │ │ ├── StandalonePlaygroundNative.module.scss
│ │ │ ├── StandalonePlaygroundNative.tsx
│ │ │ ├── ThemeSwitcher.module.scss
│ │ │ ├── ThemeSwitcher.tsx
│ │ │ ├── ToneSwitcher.tsx
│ │ │ ├── Tooltip.module.scss
│ │ │ ├── Tooltip.tsx
│ │ │ └── utils.ts
│ │ ├── providers
│ │ │ ├── Toast.module.scss
│ │ │ └── ToastProvider.tsx
│ │ ├── state
│ │ │ └── store.ts
│ │ ├── themes
│ │ │ └── theme.ts
│ │ └── utils
│ │ └── helpers.ts
│ ├── xmlui-search
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── index.tsx
│ │ ├── Search.module.scss
│ │ └── Search.tsx
│ ├── xmlui-spreadsheet
│ │ ├── .gitignore
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── index.tsx
│ │ ├── Spreadsheet.tsx
│ │ └── SpreadsheetNative.tsx
│ └── xmlui-website-blocks
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── demo
│ │ ├── components
│ │ │ ├── HeroBackgroundBreakoutPage.xmlui
│ │ │ ├── HeroBackgroundsPage.xmlui
│ │ │ ├── HeroContentsPage.xmlui
│ │ │ ├── HeroTextAlignPage.xmlui
│ │ │ ├── HeroTextPage.xmlui
│ │ │ └── HeroTonesPage.xmlui
│ │ ├── Main.xmlui
│ │ └── themes
│ │ └── default.ts
│ ├── index.html
│ ├── index.ts
│ ├── meta
│ │ └── componentsMetadata.ts
│ ├── package.json
│ ├── public
│ │ └── resources
│ │ ├── building.jpg
│ │ └── xmlui-logo.svg
│ └── src
│ ├── Carousel
│ │ ├── Carousel.module.scss
│ │ ├── Carousel.tsx
│ │ ├── CarouselContext.tsx
│ │ └── CarouselNative.tsx
│ ├── FancyButton
│ │ ├── FancyButton.module.scss
│ │ ├── FancyButton.tsx
│ │ └── FancyButton.xmlui
│ ├── Hello
│ │ ├── Hello.tsx
│ │ ├── Hello.xmlui
│ │ └── Hello.xmlui.xs
│ ├── HeroSection
│ │ ├── HeroSection.module.scss
│ │ ├── HeroSection.spec.ts
│ │ ├── HeroSection.tsx
│ │ └── HeroSectionNative.tsx
│ ├── index.tsx
│ ├── ScrollToTop
│ │ ├── ScrollToTop.module.scss
│ │ ├── ScrollToTop.tsx
│ │ └── ScrollToTopNative.tsx
│ └── vite-env.d.ts
├── playwright.config.ts
├── README.md
├── tools
│ ├── codefence
│ │ └── xmlui-code-fence-docs.md
│ ├── create-app
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── create-app.ts
│ │ ├── helpers
│ │ │ ├── copy.ts
│ │ │ ├── get-pkg-manager.ts
│ │ │ ├── git.ts
│ │ │ ├── install.ts
│ │ │ ├── is-folder-empty.ts
│ │ │ ├── is-writeable.ts
│ │ │ ├── make-dir.ts
│ │ │ └── validate-pkg.ts
│ │ ├── index.ts
│ │ ├── package.json
│ │ ├── templates
│ │ │ ├── default
│ │ │ │ └── ts
│ │ │ │ ├── gitignore
│ │ │ │ ├── index.html
│ │ │ │ ├── index.ts
│ │ │ │ ├── public
│ │ │ │ │ ├── mockServiceWorker.js
│ │ │ │ │ ├── resources
│ │ │ │ │ │ ├── favicon.ico
│ │ │ │ │ │ └── xmlui-logo.svg
│ │ │ │ │ └── serve.json
│ │ │ │ └── src
│ │ │ │ ├── components
│ │ │ │ │ ├── ApiAware.xmlui
│ │ │ │ │ ├── Home.xmlui
│ │ │ │ │ ├── IncButton.xmlui
│ │ │ │ │ └── PagePanel.xmlui
│ │ │ │ ├── config.ts
│ │ │ │ └── Main.xmlui
│ │ │ ├── index.ts
│ │ │ └── types.ts
│ │ └── tsconfig.json
│ ├── create-xmlui-hello-world
│ │ ├── index.js
│ │ └── package.json
│ └── vscode
│ ├── .gitignore
│ ├── .vscode
│ │ ├── launch.json
│ │ └── tasks.json
│ ├── .vscodeignore
│ ├── build.sh
│ ├── CHANGELOG.md
│ ├── esbuild.js
│ ├── eslint.config.mjs
│ ├── formatter-docs.md
│ ├── generate-test-sample.sh
│ ├── LICENSE.md
│ ├── package-lock.json
│ ├── package.json
│ ├── README.md
│ ├── resources
│ │ ├── xmlui-logo.png
│ │ └── xmlui-markup-syntax-highlighting.png
│ ├── src
│ │ ├── extension.ts
│ │ └── server.ts
│ ├── syntaxes
│ │ └── xmlui.tmLanguage.json
│ ├── test-samples
│ │ └── sample.xmlui
│ ├── tsconfig.json
│ └── tsconfig.tsbuildinfo
├── turbo.json
└── xmlui
├── .gitignore
├── bin
│ ├── bootstrap.cjs
│ ├── bootstrap.js
│ ├── build-lib.ts
│ ├── build.ts
│ ├── index.ts
│ ├── preview.ts
│ ├── start.ts
│ ├── vite-xmlui-plugin.ts
│ └── viteConfig.ts
├── CHANGELOG.md
├── conventions
│ ├── component-qa-checklist.md
│ ├── copilot-conventions.md
│ ├── create-xmlui-components.md
│ ├── mermaid.md
│ ├── testing-conventions.md
│ └── xmlui-in-a-nutshell.md
├── dev-docs
│ ├── accessibility.md
│ ├── build-system.md
│ ├── build-xmlui.md
│ ├── component-behaviors.md
│ ├── 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
│ │ ├── DataSource
│ │ │ ├── DataSource.md
│ │ │ └── DataSource.tsx
│ │ ├── DateInput
│ │ │ ├── DateInput.md
│ │ │ ├── DateInput.module.scss
│ │ │ ├── DateInput.spec.ts
│ │ │ ├── DateInput.tsx
│ │ │ └── DateInputNative.tsx
│ │ ├── DatePicker
│ │ │ ├── DatePicker.md
│ │ │ ├── DatePicker.module.scss
│ │ │ ├── DatePicker.spec.ts
│ │ │ ├── DatePicker.tsx
│ │ │ └── DatePickerNative.tsx
│ │ ├── DropdownMenu
│ │ │ ├── DropdownMenu.md
│ │ │ ├── DropdownMenu.module.scss
│ │ │ ├── DropdownMenu.spec.ts
│ │ │ ├── DropdownMenu.tsx
│ │ │ ├── DropdownMenuNative.tsx
│ │ │ ├── MenuItem.md
│ │ │ └── SubMenuItem.md
│ │ ├── EmojiSelector
│ │ │ ├── EmojiSelector.md
│ │ │ ├── EmojiSelector.spec.ts
│ │ │ ├── EmojiSelector.tsx
│ │ │ └── EmojiSelectorNative.tsx
│ │ ├── ExpandableItem
│ │ │ ├── ExpandableItem.module.scss
│ │ │ ├── ExpandableItem.spec.ts
│ │ │ ├── ExpandableItem.tsx
│ │ │ └── ExpandableItemNative.tsx
│ │ ├── FileInput
│ │ │ ├── FileInput.md
│ │ │ ├── FileInput.module.scss
│ │ │ ├── FileInput.spec.ts
│ │ │ ├── FileInput.tsx
│ │ │ └── FileInputNative.tsx
│ │ ├── FileUploadDropZone
│ │ │ ├── FileUploadDropZone.md
│ │ │ ├── FileUploadDropZone.module.scss
│ │ │ ├── FileUploadDropZone.spec.ts
│ │ │ ├── FileUploadDropZone.tsx
│ │ │ └── FileUploadDropZoneNative.tsx
│ │ ├── FlowLayout
│ │ │ ├── FlowLayout.md
│ │ │ ├── FlowLayout.module.scss
│ │ │ ├── FlowLayout.spec.ts
│ │ │ ├── FlowLayout.spec.ts-snapshots
│ │ │ │ └── Edge-cases-boxShadow-is-not-clipped-1-non-smoke-darwin.png
│ │ │ ├── FlowLayout.tsx
│ │ │ └── FlowLayoutNative.tsx
│ │ ├── Footer
│ │ │ ├── Footer.md
│ │ │ ├── Footer.module.scss
│ │ │ ├── Footer.spec.ts
│ │ │ ├── Footer.tsx
│ │ │ └── FooterNative.tsx
│ │ ├── Form
│ │ │ ├── Form.md
│ │ │ ├── Form.module.scss
│ │ │ ├── Form.spec.ts
│ │ │ ├── Form.tsx
│ │ │ ├── formActions.ts
│ │ │ ├── FormContext.ts
│ │ │ └── FormNative.tsx
│ │ ├── FormItem
│ │ │ ├── FormItem.md
│ │ │ ├── FormItem.module.scss
│ │ │ ├── FormItem.spec.ts
│ │ │ ├── FormItem.tsx
│ │ │ ├── FormItemNative.tsx
│ │ │ ├── HelperText.module.scss
│ │ │ ├── HelperText.tsx
│ │ │ ├── ItemWithLabel.tsx
│ │ │ └── Validations.ts
│ │ ├── FormSection
│ │ │ ├── FormSection.md
│ │ │ ├── FormSection.ts
│ │ │ └── FormSection.xmlui
│ │ ├── Fragment
│ │ │ ├── Fragment.spec.ts
│ │ │ └── Fragment.tsx
│ │ ├── Heading
│ │ │ ├── abstractions.ts
│ │ │ ├── H1.md
│ │ │ ├── H1.spec.ts
│ │ │ ├── H2.md
│ │ │ ├── H2.spec.ts
│ │ │ ├── H3.md
│ │ │ ├── H3.spec.ts
│ │ │ ├── H4.md
│ │ │ ├── H4.spec.ts
│ │ │ ├── H5.md
│ │ │ ├── H5.spec.ts
│ │ │ ├── H6.md
│ │ │ ├── H6.spec.ts
│ │ │ ├── Heading.md
│ │ │ ├── Heading.module.scss
│ │ │ ├── Heading.spec.ts
│ │ │ ├── Heading.tsx
│ │ │ └── HeadingNative.tsx
│ │ ├── HoverCard
│ │ │ ├── HoverCard.tsx
│ │ │ └── HovercardNative.tsx
│ │ ├── HtmlTags
│ │ │ ├── HtmlTags.module.scss
│ │ │ ├── HtmlTags.spec.ts
│ │ │ └── HtmlTags.tsx
│ │ ├── Icon
│ │ │ ├── AdmonitionDanger.tsx
│ │ │ ├── AdmonitionInfo.tsx
│ │ │ ├── AdmonitionNote.tsx
│ │ │ ├── AdmonitionTip.tsx
│ │ │ ├── AdmonitionWarning.tsx
│ │ │ ├── ApiIcon.tsx
│ │ │ ├── ArrowDropDown.module.scss
│ │ │ ├── ArrowDropDown.tsx
│ │ │ ├── ArrowDropUp.module.scss
│ │ │ ├── ArrowDropUp.tsx
│ │ │ ├── ArrowLeft.module.scss
│ │ │ ├── ArrowLeft.tsx
│ │ │ ├── ArrowRight.module.scss
│ │ │ ├── ArrowRight.tsx
│ │ │ ├── Attach.tsx
│ │ │ ├── Binding.module.scss
│ │ │ ├── Binding.tsx
│ │ │ ├── BoardIcon.tsx
│ │ │ ├── BoxIcon.tsx
│ │ │ ├── CheckIcon.tsx
│ │ │ ├── ChevronDownIcon.tsx
│ │ │ ├── ChevronLeft.tsx
│ │ │ ├── ChevronRight.tsx
│ │ │ ├── ChevronUpIcon.tsx
│ │ │ ├── CodeFileIcon.tsx
│ │ │ ├── CodeSandbox.tsx
│ │ │ ├── CompactListIcon.tsx
│ │ │ ├── ContentCopyIcon.tsx
│ │ │ ├── DarkToLightIcon.tsx
│ │ │ ├── DatabaseIcon.module.scss
│ │ │ ├── DatabaseIcon.tsx
│ │ │ ├── DocFileIcon.tsx
│ │ │ ├── DocIcon.tsx
│ │ │ ├── DotMenuHorizontalIcon.tsx
│ │ │ ├── DotMenuIcon.tsx
│ │ │ ├── EmailIcon.tsx
│ │ │ ├── EmptyFolderIcon.tsx
│ │ │ ├── ErrorIcon.tsx
│ │ │ ├── ExpressionIcon.tsx
│ │ │ ├── FillPlusCricleIcon.tsx
│ │ │ ├── FilterIcon.tsx
│ │ │ ├── FolderIcon.tsx
│ │ │ ├── GlobeIcon.tsx
│ │ │ ├── HomeIcon.tsx
│ │ │ ├── HyperLinkIcon.tsx
│ │ │ ├── Icon.md
│ │ │ ├── Icon.module.scss
│ │ │ ├── Icon.spec.ts
│ │ │ ├── Icon.tsx
│ │ │ ├── IconNative.tsx
│ │ │ ├── ImageFileIcon.tsx
│ │ │ ├── Inspect.tsx
│ │ │ ├── LightToDark.tsx
│ │ │ ├── LinkIcon.tsx
│ │ │ ├── ListIcon.tsx
│ │ │ ├── LooseListIcon.tsx
│ │ │ ├── MoonIcon.tsx
│ │ │ ├── MoreOptionsIcon.tsx
│ │ │ ├── NoSortIcon.tsx
│ │ │ ├── PDFIcon.tsx
│ │ │ ├── PenIcon.tsx
│ │ │ ├── PhoneIcon.tsx
│ │ │ ├── PhotoIcon.tsx
│ │ │ ├── PlusIcon.tsx
│ │ │ ├── SearchIcon.tsx
│ │ │ ├── ShareIcon.tsx
│ │ │ ├── SortAscendingIcon.tsx
│ │ │ ├── SortDescendingIcon.tsx
│ │ │ ├── StarsIcon.tsx
│ │ │ ├── SunIcon.tsx
│ │ │ ├── svg
│ │ │ │ ├── admonition_danger.svg
│ │ │ │ ├── admonition_info.svg
│ │ │ │ ├── admonition_note.svg
│ │ │ │ ├── admonition_tip.svg
│ │ │ │ ├── admonition_warning.svg
│ │ │ │ ├── api.svg
│ │ │ │ ├── arrow-dropdown.svg
│ │ │ │ ├── arrow-left.svg
│ │ │ │ ├── arrow-right.svg
│ │ │ │ ├── arrow-up.svg
│ │ │ │ ├── attach.svg
│ │ │ │ ├── binding.svg
│ │ │ │ ├── box.svg
│ │ │ │ ├── bulb.svg
│ │ │ │ ├── code-file.svg
│ │ │ │ ├── code-sandbox.svg
│ │ │ │ ├── dark_to_light.svg
│ │ │ │ ├── database.svg
│ │ │ │ ├── doc.svg
│ │ │ │ ├── empty-folder.svg
│ │ │ │ ├── expression.svg
│ │ │ │ ├── eye-closed.svg
│ │ │ │ ├── eye-dark.svg
│ │ │ │ ├── eye.svg
│ │ │ │ ├── file-text.svg
│ │ │ │ ├── filter.svg
│ │ │ │ ├── folder.svg
│ │ │ │ ├── img.svg
│ │ │ │ ├── inspect.svg
│ │ │ │ ├── light_to_dark.svg
│ │ │ │ ├── moon.svg
│ │ │ │ ├── pdf.svg
│ │ │ │ ├── photo.svg
│ │ │ │ ├── share.svg
│ │ │ │ ├── stars.svg
│ │ │ │ ├── sun.svg
│ │ │ │ ├── trending-down.svg
│ │ │ │ ├── trending-level.svg
│ │ │ │ ├── trending-up.svg
│ │ │ │ ├── txt.svg
│ │ │ │ ├── unknown-file.svg
│ │ │ │ ├── unlink.svg
│ │ │ │ └── xls.svg
│ │ │ ├── TableDeleteColumnIcon.tsx
│ │ │ ├── TableDeleteRowIcon.tsx
│ │ │ ├── TableInsertColumnIcon.tsx
│ │ │ ├── TableInsertRowIcon.tsx
│ │ │ ├── TrashIcon.tsx
│ │ │ ├── TrendingDownIcon.tsx
│ │ │ ├── TrendingLevelIcon.tsx
│ │ │ ├── TrendingUpIcon.tsx
│ │ │ ├── TxtIcon.tsx
│ │ │ ├── UnknownFileIcon.tsx
│ │ │ ├── UnlinkIcon.tsx
│ │ │ ├── UserIcon.tsx
│ │ │ ├── WarningIcon.tsx
│ │ │ └── XlsIcon.tsx
│ │ ├── IconProvider.tsx
│ │ ├── IconRegistryContext.tsx
│ │ ├── IFrame
│ │ │ ├── IFrame.md
│ │ │ ├── IFrame.module.scss
│ │ │ ├── IFrame.spec.ts
│ │ │ ├── IFrame.tsx
│ │ │ └── IFrameNative.tsx
│ │ ├── Image
│ │ │ ├── Image.md
│ │ │ ├── Image.module.scss
│ │ │ ├── Image.spec.ts
│ │ │ ├── Image.tsx
│ │ │ └── ImageNative.tsx
│ │ ├── Input
│ │ │ ├── index.ts
│ │ │ ├── InputAdornment.module.scss
│ │ │ ├── InputAdornment.tsx
│ │ │ ├── InputDivider.module.scss
│ │ │ ├── InputDivider.tsx
│ │ │ ├── InputLabel.module.scss
│ │ │ ├── InputLabel.tsx
│ │ │ ├── PartialInput.module.scss
│ │ │ └── PartialInput.tsx
│ │ ├── InspectButton
│ │ │ ├── InspectButton.module.scss
│ │ │ └── InspectButton.tsx
│ │ ├── Items
│ │ │ ├── Items.md
│ │ │ ├── Items.spec.ts
│ │ │ ├── Items.tsx
│ │ │ └── ItemsNative.tsx
│ │ ├── Link
│ │ │ ├── Link.md
│ │ │ ├── Link.module.scss
│ │ │ ├── Link.spec.ts
│ │ │ ├── Link.tsx
│ │ │ └── LinkNative.tsx
│ │ ├── List
│ │ │ ├── doc-resources
│ │ │ │ └── list-component-data.js
│ │ │ ├── List.md
│ │ │ ├── List.module.scss
│ │ │ ├── List.spec.ts
│ │ │ ├── List.tsx
│ │ │ └── ListNative.tsx
│ │ ├── Logo
│ │ │ ├── doc-resources
│ │ │ │ └── xmlui-logo.svg
│ │ │ ├── Logo.md
│ │ │ ├── Logo.tsx
│ │ │ └── LogoNative.tsx
│ │ ├── Markdown
│ │ │ ├── CodeText.module.scss
│ │ │ ├── CodeText.tsx
│ │ │ ├── Markdown.md
│ │ │ ├── Markdown.module.scss
│ │ │ ├── Markdown.spec.ts
│ │ │ ├── Markdown.tsx
│ │ │ ├── MarkdownNative.tsx
│ │ │ ├── parse-binding-expr.ts
│ │ │ └── utils.ts
│ │ ├── metadata-helpers.ts
│ │ ├── ModalDialog
│ │ │ ├── ConfirmationModalContextProvider.tsx
│ │ │ ├── Dialog.module.scss
│ │ │ ├── Dialog.tsx
│ │ │ ├── ModalDialog.md
│ │ │ ├── ModalDialog.module.scss
│ │ │ ├── ModalDialog.spec.ts
│ │ │ ├── ModalDialog.tsx
│ │ │ ├── ModalDialogNative.tsx
│ │ │ └── ModalVisibilityContext.tsx
│ │ ├── NavGroup
│ │ │ ├── NavGroup.md
│ │ │ ├── NavGroup.module.scss
│ │ │ ├── NavGroup.spec.ts
│ │ │ ├── NavGroup.tsx
│ │ │ ├── NavGroupContext.ts
│ │ │ └── NavGroupNative.tsx
│ │ ├── NavLink
│ │ │ ├── NavLink.md
│ │ │ ├── NavLink.module.scss
│ │ │ ├── NavLink.spec.ts
│ │ │ ├── NavLink.tsx
│ │ │ └── NavLinkNative.tsx
│ │ ├── NavPanel
│ │ │ ├── NavPanel.md
│ │ │ ├── NavPanel.module.scss
│ │ │ ├── NavPanel.spec.ts
│ │ │ ├── NavPanel.tsx
│ │ │ └── NavPanelNative.tsx
│ │ ├── NestedApp
│ │ │ ├── AppWithCodeView.module.scss
│ │ │ ├── AppWithCodeView.tsx
│ │ │ ├── AppWithCodeViewNative.tsx
│ │ │ ├── defaultProps.tsx
│ │ │ ├── logo.svg
│ │ │ ├── NestedApp.module.scss
│ │ │ ├── NestedApp.tsx
│ │ │ ├── NestedAppNative.tsx
│ │ │ ├── Tooltip.module.scss
│ │ │ ├── Tooltip.tsx
│ │ │ └── utils.ts
│ │ ├── NoResult
│ │ │ ├── NoResult.md
│ │ │ ├── NoResult.module.scss
│ │ │ ├── NoResult.spec.ts
│ │ │ ├── NoResult.tsx
│ │ │ └── NoResultNative.tsx
│ │ ├── NumberBox
│ │ │ ├── numberbox-abstractions.ts
│ │ │ ├── NumberBox.md
│ │ │ ├── NumberBox.module.scss
│ │ │ ├── NumberBox.spec.ts
│ │ │ ├── NumberBox.tsx
│ │ │ └── NumberBoxNative.tsx
│ │ ├── Option
│ │ │ ├── Option.md
│ │ │ ├── Option.spec.ts
│ │ │ ├── Option.tsx
│ │ │ ├── OptionNative.tsx
│ │ │ └── OptionTypeProvider.tsx
│ │ ├── PageMetaTitle
│ │ │ ├── PageMetaTilteNative.tsx
│ │ │ ├── PageMetaTitle.md
│ │ │ ├── PageMetaTitle.spec.ts
│ │ │ └── PageMetaTitle.tsx
│ │ ├── Pages
│ │ │ ├── Page.md
│ │ │ ├── Pages.md
│ │ │ ├── Pages.module.scss
│ │ │ ├── Pages.tsx
│ │ │ └── PagesNative.tsx
│ │ ├── Pagination
│ │ │ ├── Pagination.md
│ │ │ ├── Pagination.module.scss
│ │ │ ├── Pagination.spec.ts
│ │ │ ├── Pagination.tsx
│ │ │ └── PaginationNative.tsx
│ │ ├── PositionedContainer
│ │ │ ├── PositionedContainer.module.scss
│ │ │ ├── PositionedContainer.tsx
│ │ │ └── PositionedContainerNative.tsx
│ │ ├── ProfileMenu
│ │ │ ├── ProfileMenu.module.scss
│ │ │ └── ProfileMenu.tsx
│ │ ├── ProgressBar
│ │ │ ├── ProgressBar.md
│ │ │ ├── ProgressBar.module.scss
│ │ │ ├── ProgressBar.spec.ts
│ │ │ ├── ProgressBar.tsx
│ │ │ └── ProgressBarNative.tsx
│ │ ├── Queue
│ │ │ ├── Queue.md
│ │ │ ├── Queue.spec.ts
│ │ │ ├── Queue.tsx
│ │ │ ├── queueActions.ts
│ │ │ └── QueueNative.tsx
│ │ ├── RadioGroup
│ │ │ ├── RadioGroup.md
│ │ │ ├── RadioGroup.module.scss
│ │ │ ├── RadioGroup.spec.ts
│ │ │ ├── RadioGroup.tsx
│ │ │ ├── RadioGroupNative.tsx
│ │ │ ├── RadioItem.tsx
│ │ │ └── RadioItemNative.tsx
│ │ ├── RealTimeAdapter
│ │ │ ├── RealTimeAdapter.tsx
│ │ │ └── RealTimeAdapterNative.tsx
│ │ ├── Redirect
│ │ │ ├── Redirect.md
│ │ │ ├── Redirect.spec.ts
│ │ │ └── Redirect.tsx
│ │ ├── ResponsiveBar
│ │ │ ├── README.md
│ │ │ ├── ResponsiveBar.md
│ │ │ ├── ResponsiveBar.module.scss
│ │ │ ├── ResponsiveBar.spec.ts
│ │ │ ├── ResponsiveBar.tsx
│ │ │ └── ResponsiveBarNative.tsx
│ │ ├── Select
│ │ │ ├── HiddenOption.tsx
│ │ │ ├── OptionContext.ts
│ │ │ ├── Select.md
│ │ │ ├── Select.module.scss
│ │ │ ├── Select.spec.ts
│ │ │ ├── Select.tsx
│ │ │ ├── SelectContext.tsx
│ │ │ └── SelectNative.tsx
│ │ ├── SelectionStore
│ │ │ ├── SelectionStore.md
│ │ │ ├── SelectionStore.tsx
│ │ │ └── SelectionStoreNative.tsx
│ │ ├── Slider
│ │ │ ├── Slider.md
│ │ │ ├── Slider.module.scss
│ │ │ ├── Slider.spec.ts
│ │ │ ├── Slider.tsx
│ │ │ └── SliderNative.tsx
│ │ ├── Slot
│ │ │ ├── Slot.md
│ │ │ ├── Slot.spec.ts
│ │ │ └── Slot.ts
│ │ ├── SlotItem.tsx
│ │ ├── SpaceFiller
│ │ │ ├── SpaceFiller.md
│ │ │ ├── SpaceFiller.module.scss
│ │ │ ├── SpaceFiller.spec.ts
│ │ │ ├── SpaceFiller.tsx
│ │ │ └── SpaceFillerNative.tsx
│ │ ├── Spinner
│ │ │ ├── Spinner.md
│ │ │ ├── Spinner.module.scss
│ │ │ ├── Spinner.spec.ts
│ │ │ ├── Spinner.tsx
│ │ │ └── SpinnerNative.tsx
│ │ ├── Splitter
│ │ │ ├── HSplitter.md
│ │ │ ├── HSplitter.spec.ts
│ │ │ ├── Splitter.md
│ │ │ ├── Splitter.module.scss
│ │ │ ├── Splitter.spec.ts
│ │ │ ├── Splitter.tsx
│ │ │ ├── SplitterNative.tsx
│ │ │ ├── utils.ts
│ │ │ ├── VSplitter.md
│ │ │ └── VSplitter.spec.ts
│ │ ├── Stack
│ │ │ ├── CHStack.md
│ │ │ ├── CHStack.spec.ts
│ │ │ ├── CVStack.md
│ │ │ ├── CVStack.spec.ts
│ │ │ ├── HStack.md
│ │ │ ├── HStack.spec.ts
│ │ │ ├── Stack.md
│ │ │ ├── Stack.module.scss
│ │ │ ├── Stack.spec.ts
│ │ │ ├── Stack.tsx
│ │ │ ├── StackNative.tsx
│ │ │ ├── VStack.md
│ │ │ └── VStack.spec.ts
│ │ ├── StickyBox
│ │ │ ├── StickyBox.md
│ │ │ ├── StickyBox.module.scss
│ │ │ ├── StickyBox.tsx
│ │ │ └── StickyBoxNative.tsx
│ │ ├── Switch
│ │ │ ├── Switch.md
│ │ │ ├── Switch.spec.ts
│ │ │ └── Switch.tsx
│ │ ├── Table
│ │ │ ├── doc-resources
│ │ │ │ └── list-component-data.js
│ │ │ ├── react-table-config.d.ts
│ │ │ ├── Table.md
│ │ │ ├── Table.module.scss
│ │ │ ├── Table.spec.ts
│ │ │ ├── Table.tsx
│ │ │ ├── TableNative.tsx
│ │ │ └── useRowSelection.tsx
│ │ ├── TableOfContents
│ │ │ ├── TableOfContents.module.scss
│ │ │ ├── TableOfContents.spec.ts
│ │ │ ├── TableOfContents.tsx
│ │ │ └── TableOfContentsNative.tsx
│ │ ├── Tabs
│ │ │ ├── TabContext.tsx
│ │ │ ├── TabItem.md
│ │ │ ├── TabItem.tsx
│ │ │ ├── TabItemNative.tsx
│ │ │ ├── Tabs.md
│ │ │ ├── Tabs.module.scss
│ │ │ ├── Tabs.spec.ts
│ │ │ ├── Tabs.tsx
│ │ │ └── TabsNative.tsx
│ │ ├── Text
│ │ │ ├── Text.md
│ │ │ ├── Text.module.scss
│ │ │ ├── Text.spec.ts
│ │ │ ├── Text.tsx
│ │ │ └── TextNative.tsx
│ │ ├── TextArea
│ │ │ ├── TextArea.md
│ │ │ ├── TextArea.module.scss
│ │ │ ├── TextArea.spec.ts
│ │ │ ├── TextArea.tsx
│ │ │ ├── TextAreaNative.tsx
│ │ │ ├── TextAreaResizable.tsx
│ │ │ └── useComposedRef.ts
│ │ ├── TextBox
│ │ │ ├── TextBox.md
│ │ │ ├── TextBox.module.scss
│ │ │ ├── TextBox.spec.ts
│ │ │ ├── TextBox.tsx
│ │ │ └── TextBoxNative.tsx
│ │ ├── Theme
│ │ │ ├── NotificationToast.tsx
│ │ │ ├── Theme.md
│ │ │ ├── Theme.module.scss
│ │ │ ├── Theme.spec.ts
│ │ │ ├── Theme.tsx
│ │ │ └── ThemeNative.tsx
│ │ ├── TimeInput
│ │ │ ├── TimeInput.md
│ │ │ ├── TimeInput.module.scss
│ │ │ ├── TimeInput.spec.ts
│ │ │ ├── TimeInput.tsx
│ │ │ ├── TimeInputNative.tsx
│ │ │ └── utils.ts
│ │ ├── Timer
│ │ │ ├── Timer.md
│ │ │ ├── Timer.spec.ts
│ │ │ ├── Timer.tsx
│ │ │ └── TimerNative.tsx
│ │ ├── Toggle
│ │ │ ├── Toggle.module.scss
│ │ │ └── Toggle.tsx
│ │ ├── ToneChangerButton
│ │ │ ├── ToneChangerButton.md
│ │ │ ├── ToneChangerButton.spec.ts
│ │ │ └── ToneChangerButton.tsx
│ │ ├── ToneSwitch
│ │ │ ├── ToneSwitch.md
│ │ │ ├── ToneSwitch.module.scss
│ │ │ ├── ToneSwitch.spec.ts
│ │ │ ├── ToneSwitch.tsx
│ │ │ └── ToneSwitchNative.tsx
│ │ ├── Tooltip
│ │ │ ├── Tooltip.md
│ │ │ ├── Tooltip.module.scss
│ │ │ ├── Tooltip.spec.ts
│ │ │ ├── Tooltip.tsx
│ │ │ └── TooltipNative.tsx
│ │ ├── Tree
│ │ │ ├── testData.ts
│ │ │ ├── Tree-dynamic.spec.ts
│ │ │ ├── Tree-icons.spec.ts
│ │ │ ├── Tree.md
│ │ │ ├── Tree.spec.ts
│ │ │ ├── TreeComponent.module.scss
│ │ │ ├── TreeComponent.tsx
│ │ │ └── TreeNative.tsx
│ │ ├── TreeDisplay
│ │ │ ├── TreeDisplay.md
│ │ │ ├── TreeDisplay.module.scss
│ │ │ ├── TreeDisplay.tsx
│ │ │ └── TreeDisplayNative.tsx
│ │ ├── ValidationSummary
│ │ │ ├── ValidationSummary.module.scss
│ │ │ └── ValidationSummary.tsx
│ │ └── VisuallyHidden.tsx
│ ├── components-core
│ │ ├── abstractions
│ │ │ ├── ComponentRenderer.ts
│ │ │ ├── LoaderRenderer.ts
│ │ │ ├── standalone.ts
│ │ │ └── treeAbstractions.ts
│ │ ├── action
│ │ │ ├── actions.ts
│ │ │ ├── APICall.tsx
│ │ │ ├── FileDownloadAction.tsx
│ │ │ ├── FileUploadAction.tsx
│ │ │ ├── NavigateAction.tsx
│ │ │ └── TimedAction.tsx
│ │ ├── ApiBoundComponent.tsx
│ │ ├── appContext
│ │ │ ├── date-functions.ts
│ │ │ ├── math-function.ts
│ │ │ └── misc-utils.ts
│ │ ├── AppContext.tsx
│ │ ├── behaviors
│ │ │ ├── Behavior.tsx
│ │ │ └── CoreBehaviors.tsx
│ │ ├── component-hooks.ts
│ │ ├── ComponentDecorator.tsx
│ │ ├── ComponentViewer.tsx
│ │ ├── CompoundComponent.tsx
│ │ ├── constants.ts
│ │ ├── DebugViewProvider.tsx
│ │ ├── descriptorHelper.ts
│ │ ├── devtools
│ │ │ ├── InspectorDialog.module.scss
│ │ │ ├── InspectorDialog.tsx
│ │ │ └── InspectorDialogVisibilityContext.tsx
│ │ ├── EngineError.ts
│ │ ├── event-handlers.ts
│ │ ├── InspectorButton.module.scss
│ │ ├── InspectorContext.tsx
│ │ ├── interception
│ │ │ ├── abstractions.ts
│ │ │ ├── ApiInterceptor.ts
│ │ │ ├── ApiInterceptorProvider.tsx
│ │ │ ├── apiInterceptorWorker.ts
│ │ │ ├── Backend.ts
│ │ │ ├── Errors.ts
│ │ │ ├── IndexedDb.ts
│ │ │ ├── initMock.ts
│ │ │ ├── InMemoryDb.ts
│ │ │ ├── ReadonlyCollection.ts
│ │ │ └── useApiInterceptorContext.tsx
│ │ ├── loader
│ │ │ ├── ApiLoader.tsx
│ │ │ ├── DataLoader.tsx
│ │ │ ├── ExternalDataLoader.tsx
│ │ │ ├── Loader.tsx
│ │ │ ├── MockLoaderRenderer.tsx
│ │ │ └── PageableLoader.tsx
│ │ ├── LoaderComponent.tsx
│ │ ├── markup-check.ts
│ │ ├── parts.ts
│ │ ├── renderers.ts
│ │ ├── rendering
│ │ │ ├── AppContent.tsx
│ │ │ ├── AppRoot.tsx
│ │ │ ├── AppWrapper.tsx
│ │ │ ├── buildProxy.ts
│ │ │ ├── collectFnVarDeps.ts
│ │ │ ├── ComponentAdapter.tsx
│ │ │ ├── ComponentWrapper.tsx
│ │ │ ├── Container.tsx
│ │ │ ├── containers.ts
│ │ │ ├── ContainerWrapper.tsx
│ │ │ ├── ErrorBoundary.module.scss
│ │ │ ├── ErrorBoundary.tsx
│ │ │ ├── InvalidComponent.module.scss
│ │ │ ├── InvalidComponent.tsx
│ │ │ ├── nodeUtils.ts
│ │ │ ├── reducer.ts
│ │ │ ├── renderChild.tsx
│ │ │ ├── StandaloneComponent.tsx
│ │ │ ├── StateContainer.tsx
│ │ │ ├── UnknownComponent.module.scss
│ │ │ ├── UnknownComponent.tsx
│ │ │ └── valueExtractor.ts
│ │ ├── reportEngineError.ts
│ │ ├── RestApiProxy.ts
│ │ ├── script-runner
│ │ │ ├── asyncProxy.ts
│ │ │ ├── AttributeValueParser.ts
│ │ │ ├── bannedFunctions.ts
│ │ │ ├── BindingTreeEvaluationContext.ts
│ │ │ ├── eval-tree-async.ts
│ │ │ ├── eval-tree-common.ts
│ │ │ ├── eval-tree-sync.ts
│ │ │ ├── ParameterParser.ts
│ │ │ ├── process-statement-async.ts
│ │ │ ├── process-statement-common.ts
│ │ │ ├── process-statement-sync.ts
│ │ │ ├── ScriptingSourceTree.ts
│ │ │ ├── simplify-expression.ts
│ │ │ ├── statement-queue.ts
│ │ │ └── visitors.ts
│ │ ├── StandaloneApp.tsx
│ │ ├── StandaloneExtensionManager.ts
│ │ ├── TableOfContentsContext.tsx
│ │ ├── theming
│ │ │ ├── _themes.scss
│ │ │ ├── component-layout-resolver.ts
│ │ │ ├── extendThemeUtils.ts
│ │ │ ├── hvar.ts
│ │ │ ├── layout-resolver.ts
│ │ │ ├── parse-layout-props.ts
│ │ │ ├── StyleContext.tsx
│ │ │ ├── StyleRegistry.ts
│ │ │ ├── ThemeContext.tsx
│ │ │ ├── ThemeProvider.tsx
│ │ │ ├── themes
│ │ │ │ ├── base-utils.ts
│ │ │ │ ├── palette.ts
│ │ │ │ ├── root.ts
│ │ │ │ ├── solid.ts
│ │ │ │ ├── theme-colors.ts
│ │ │ │ └── xmlui.ts
│ │ │ ├── themeVars.module.scss
│ │ │ ├── themeVars.ts
│ │ │ ├── transformThemeVars.ts
│ │ │ └── utils.ts
│ │ ├── utils
│ │ │ ├── actionUtils.ts
│ │ │ ├── audio-utils.ts
│ │ │ ├── base64-utils.ts
│ │ │ ├── compound-utils.ts
│ │ │ ├── css-utils.ts
│ │ │ ├── DataLoaderQueryKeyGenerator.ts
│ │ │ ├── date-utils.ts
│ │ │ ├── extractParam.ts
│ │ │ ├── hooks.tsx
│ │ │ ├── LruCache.ts
│ │ │ ├── mergeProps.ts
│ │ │ ├── misc.ts
│ │ │ ├── request-params.ts
│ │ │ ├── statementUtils.ts
│ │ │ └── treeUtils.ts
│ │ └── xmlui-parser.ts
│ ├── index-standalone.ts
│ ├── index.scss
│ ├── index.ts
│ ├── language-server
│ │ ├── server-common.ts
│ │ ├── server-web-worker.ts
│ │ ├── server.ts
│ │ ├── services
│ │ │ ├── common
│ │ │ │ ├── docs-generation.ts
│ │ │ │ ├── lsp-utils.ts
│ │ │ │ ├── metadata-utils.ts
│ │ │ │ └── syntax-node-utilities.ts
│ │ │ ├── completion.ts
│ │ │ ├── diagnostic.ts
│ │ │ ├── format.ts
│ │ │ └── hover.ts
│ │ └── xmlui-metadata-generated.js
│ ├── logging
│ │ ├── LoggerContext.tsx
│ │ ├── LoggerInitializer.tsx
│ │ ├── LoggerService.ts
│ │ └── xmlui.ts
│ ├── logo.svg
│ ├── parsers
│ │ ├── common
│ │ │ ├── GenericToken.ts
│ │ │ ├── InputStream.ts
│ │ │ └── utils.ts
│ │ ├── scripting
│ │ │ ├── code-behind-collect.ts
│ │ │ ├── Lexer.ts
│ │ │ ├── modules.ts
│ │ │ ├── Parser.ts
│ │ │ ├── ParserError.ts
│ │ │ ├── ScriptingNodeTypes.ts
│ │ │ ├── TokenTrait.ts
│ │ │ ├── TokenType.ts
│ │ │ └── tree-visitor.ts
│ │ ├── style-parser
│ │ │ ├── errors.ts
│ │ │ ├── source-tree.ts
│ │ │ ├── StyleInputStream.ts
│ │ │ ├── StyleLexer.ts
│ │ │ ├── StyleParser.ts
│ │ │ └── tokens.ts
│ │ └── xmlui-parser
│ │ ├── CharacterCodes.ts
│ │ ├── diagnostics.ts
│ │ ├── fileExtensions.ts
│ │ ├── index.ts
│ │ ├── lint.ts
│ │ ├── parser.ts
│ │ ├── ParserError.ts
│ │ ├── scanner.ts
│ │ ├── syntax-kind.ts
│ │ ├── syntax-node.ts
│ │ ├── transform.ts
│ │ ├── utils.ts
│ │ ├── xmlui-serializer.ts
│ │ └── xmlui-tree.ts
│ ├── react-app-env.d.ts
│ ├── syntax
│ │ ├── monaco
│ │ │ ├── grammar.monacoLanguage.ts
│ │ │ ├── index.ts
│ │ │ ├── xmlui-dark.ts
│ │ │ ├── xmlui-light.ts
│ │ │ └── xmluiscript.monacoLanguage.ts
│ │ └── textMate
│ │ ├── index.ts
│ │ ├── xmlui-dark.json
│ │ ├── xmlui-light.json
│ │ ├── xmlui.json
│ │ └── xmlui.tmLanguage.json
│ ├── testing
│ │ ├── assertions.ts
│ │ ├── component-test-helpers.ts
│ │ ├── ComponentDrivers.ts
│ │ ├── drivers
│ │ │ ├── DateInputDriver.ts
│ │ │ ├── index.ts
│ │ │ ├── ModalDialogDriver.ts
│ │ │ ├── NumberBoxDriver.ts
│ │ │ ├── TextBoxDriver.ts
│ │ │ ├── TimeInputDriver.ts
│ │ │ ├── TimerDriver.ts
│ │ │ └── TreeDriver.ts
│ │ ├── fixtures.ts
│ │ ├── index.ts
│ │ ├── infrastructure
│ │ │ ├── index.html
│ │ │ ├── main.tsx
│ │ │ ├── public
│ │ │ │ ├── mockServiceWorker.js
│ │ │ │ ├── resources
│ │ │ │ │ ├── bell.svg
│ │ │ │ │ ├── box.svg
│ │ │ │ │ ├── doc.svg
│ │ │ │ │ ├── eye.svg
│ │ │ │ │ ├── flower-640x480.jpg
│ │ │ │ │ ├── sun.svg
│ │ │ │ │ ├── test-image-100x100.jpg
│ │ │ │ │ └── txt.svg
│ │ │ │ └── serve.json
│ │ │ └── TestBed.tsx
│ │ └── themed-app-test-helpers.ts
│ └── vite-env.d.ts
├── tests
│ ├── components
│ │ ├── CodeBlock
│ │ │ └── hightlight-code.test.ts
│ │ ├── playground-pattern.test.ts
│ │ └── Tree
│ │ └── Tree-states.test.ts
│ ├── components-core
│ │ ├── abstractions
│ │ │ └── treeAbstractions.test.ts
│ │ ├── container
│ │ │ └── buildProxy.test.ts
│ │ ├── interception
│ │ │ ├── orderBy.test.ts
│ │ │ ├── ReadOnlyCollection.test.ts
│ │ │ └── request-param-converter.test.ts
│ │ ├── scripts-runner
│ │ │ ├── AttributeValueParser.test.ts
│ │ │ ├── eval-tree-arrow-async.test.ts
│ │ │ ├── eval-tree-arrow.test.ts
│ │ │ ├── eval-tree-func-decl-async.test.ts
│ │ │ ├── eval-tree-func-decl.test.ts
│ │ │ ├── eval-tree-pre-post.test.ts
│ │ │ ├── eval-tree-regression.test.ts
│ │ │ ├── eval-tree.test.ts
│ │ │ ├── function-proxy.test.ts
│ │ │ ├── parser-regression.test.ts
│ │ │ ├── process-event.test.ts
│ │ │ ├── process-function.test.ts
│ │ │ ├── process-implicit-context.test.ts
│ │ │ ├── process-statement-asgn.test.ts
│ │ │ ├── process-statement-destruct.test.ts
│ │ │ ├── process-statement-regs.test.ts
│ │ │ ├── process-statement-sync.test.ts
│ │ │ ├── process-statement.test.ts
│ │ │ ├── process-switch-sync.test.ts
│ │ │ ├── process-switch.test.ts
│ │ │ ├── process-try-sync.test.ts
│ │ │ ├── process-try.test.ts
│ │ │ └── test-helpers.ts
│ │ ├── test-metadata-handler.ts
│ │ ├── theming
│ │ │ ├── border-segments.test.ts
│ │ │ ├── component-layout.resolver.test.ts
│ │ │ ├── layout-property-parser.test.ts
│ │ │ ├── layout-resolver.test.ts
│ │ │ ├── layout-resolver2.test.ts
│ │ │ ├── layout-vp-override.test.ts
│ │ │ └── padding-segments.test.ts
│ │ └── utils
│ │ ├── date-utils.test.ts
│ │ ├── format-human-elapsed-time.test.ts
│ │ └── LruCache.test.ts
│ ├── language-server
│ │ ├── completion.test.ts
│ │ ├── format.test.ts
│ │ ├── hover.test.ts
│ │ └── mockData.ts
│ └── parsers
│ ├── common
│ │ └── input-stream.test.ts
│ ├── markdown
│ │ └── parse-binding-expression.test.ts
│ ├── parameter-parser.test.ts
│ ├── paremeter-parser.test.ts
│ ├── scripting
│ │ ├── eval-tree-arrow.test.ts
│ │ ├── eval-tree-pre-post.test.ts
│ │ ├── eval-tree.test.ts
│ │ ├── function-proxy.test.ts
│ │ ├── lexer-literals.test.ts
│ │ ├── lexer-misc.test.ts
│ │ ├── module-parse.test.ts
│ │ ├── parser-arrow.test.ts
│ │ ├── parser-assignments.test.ts
│ │ ├── parser-binary.test.ts
│ │ ├── parser-destructuring.test.ts
│ │ ├── parser-errors.test.ts
│ │ ├── parser-expressions.test.ts
│ │ ├── parser-function.test.ts
│ │ ├── parser-literals.test.ts
│ │ ├── parser-primary.test.ts
│ │ ├── parser-regex.test.ts
│ │ ├── parser-statements.test.ts
│ │ ├── parser-unary.test.ts
│ │ ├── process-event.test.ts
│ │ ├── process-implicit-context.test.ts
│ │ ├── process-statement-asgn.test.ts
│ │ ├── process-statement-destruct.test.ts
│ │ ├── process-statement-regs.test.ts
│ │ ├── process-statement-sync.test.ts
│ │ ├── process-statement.test.ts
│ │ ├── process-switch-sync.test.ts
│ │ ├── process-switch.test.ts
│ │ ├── process-try-sync.test.ts
│ │ ├── process-try.test.ts
│ │ ├── simplify-expression.test.ts
│ │ ├── statement-hooks.test.ts
│ │ └── test-helpers.ts
│ ├── style-parser
│ │ ├── generateHvarChain.test.ts
│ │ ├── parseHVar.test.ts
│ │ ├── parser.test.ts
│ │ └── tokens.test.ts
│ └── xmlui
│ ├── lint.test.ts
│ ├── parser.test.ts
│ ├── scanner.test.ts
│ ├── transform.attr.test.ts
│ ├── transform.circular.test.ts
│ ├── transform.element.test.ts
│ ├── transform.errors.test.ts
│ ├── transform.escape.test.ts
│ ├── transform.regression.test.ts
│ ├── transform.script.test.ts
│ ├── transform.test.ts
│ └── xmlui.ts
├── tests-e2e
│ ├── api-bound-component-regression.spec.ts
│ ├── api-call-as-extracted-component.spec.ts
│ ├── assign-to-object-or-array-regression.spec.ts
│ ├── binding-regression.spec.ts
│ ├── children-as-template-context-vars.spec.ts
│ ├── compound-component.spec.ts
│ ├── context-vars-regression.spec.ts
│ ├── data-bindings.spec.ts
│ ├── datasource-and-api-usage-in-var.spec.ts
│ ├── datasource-direct-binding.spec.ts
│ ├── datasource-onLoaded-regression.spec.ts
│ ├── modify-array-item-regression.spec.ts
│ ├── namespaces.spec.ts
│ ├── push-to-array-regression.spec.ts
│ ├── screen-breakpoints.spec.ts
│ ├── scripting.spec.ts
│ ├── state-scope-in-pages.spec.ts
│ └── state-var-scopes.spec.ts
├── tsconfig.bin.json
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
└── vitest.config.ts
```
# Files
--------------------------------------------------------------------------------
/xmlui/src/components/ExpandableItem/ExpandableItem.spec.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { test, expect } from "../../testing/fixtures";
2 | import { ExpandableItemDriver } from "../../testing/ComponentDrivers";
3 |
4 | // =============================================================================
5 | // BASIC FUNCTIONALITY TESTS
6 | // =============================================================================
7 |
8 | test("component renders with basic props", async ({ initTestBed, createExpandableItemDriver }) => {
9 | await initTestBed(`<ExpandableItem summary="Test Summary">Content here</ExpandableItem>`, {});
10 | const driver = await createExpandableItemDriver();
11 |
12 | await expect(driver.component).toBeVisible();
13 | await expect(driver.getSummaryContent()).toContainText("Test Summary");
14 | await expect(driver.getContent()).not.toBeVisible(); // Initially collapsed
15 | });
16 |
17 | test("component displays summary content correctly", async ({ initTestBed, createExpandableItemDriver }) => {
18 | await initTestBed(`<ExpandableItem summary="My Summary">Content</ExpandableItem>`, {});
19 | const driver = await createExpandableItemDriver();
20 |
21 | await expect(driver.getSummaryContent()).toContainText("My Summary");
22 | await expect(driver.getSummary()).toBeVisible();
23 | });
24 |
25 | test("component handles initiallyExpanded prop", async ({ initTestBed, createExpandableItemDriver }) => {
26 | await initTestBed(`<ExpandableItem summary="Test" initiallyExpanded="true">Content here</ExpandableItem>`, {});
27 | const driver = await createExpandableItemDriver();
28 |
29 | await expect(driver.getContent()).toBeVisible();
30 | await expect(driver.getContent()).toContainText("Content here");
31 | });
32 |
33 | test("component toggles on summary click", async ({ initTestBed, createExpandableItemDriver }) => {
34 | await initTestBed(`<ExpandableItem summary="Click me">Hidden content</ExpandableItem>`, {});
35 | const driver = await createExpandableItemDriver();
36 |
37 | // Initially collapsed
38 | await expect(driver.getContent()).not.toBeVisible();
39 |
40 | // Click to expand
41 | await driver.getSummary().click();
42 | await expect(driver.getContent()).toBeVisible();
43 |
44 | // Click to collapse
45 | await driver.getSummary().click();
46 | await expect(driver.getContent()).not.toBeVisible();
47 | });
48 |
49 | test("component displays correct icons for collapsed and expanded states", async ({ initTestBed, createExpandableItemDriver }) => {
50 | await initTestBed(`<ExpandableItem summary="Test">Content</ExpandableItem>`, {});
51 | const driver = await createExpandableItemDriver();
52 |
53 | // Initially collapsed - should show chevronright icon
54 | await expect(driver.getIcon()).toBeVisible();
55 |
56 | // Expand and check icon changes
57 | await driver.getSummary().click();
58 | await expect(driver.getIcon()).toBeVisible();
59 | });
60 |
61 | test("component handles custom icons", async ({ initTestBed, createExpandableItemDriver }) => {
62 | await initTestBed(`
63 | <ExpandableItem
64 | summary="Test"
65 | iconCollapsed="plus"
66 | iconExpanded="minus">
67 | Content
68 | </ExpandableItem>
69 | `, {});
70 | const driver = await createExpandableItemDriver();
71 |
72 | await expect(driver.getIcon()).toBeVisible();
73 |
74 | // Expand to see expanded icon
75 | await driver.getSummary().click();
76 | await expect(driver.getIcon()).toBeVisible();
77 | });
78 |
79 | test("component supports iconPosition prop", async ({ initTestBed, createExpandableItemDriver }) => {
80 | await initTestBed(`<ExpandableItem summary="Test" iconPosition="start">Content</ExpandableItem>`, {});
81 | const driver = await createExpandableItemDriver();
82 |
83 | const className = await driver.getSummary().getAttribute('class');
84 | expect(className).toMatch(/iconStart/);
85 | await expect(driver.getIcon()).toBeVisible();
86 | });
87 |
88 | test("component handles withSwitch prop", async ({ initTestBed, createExpandableItemDriver }) => {
89 | await initTestBed(`<ExpandableItem summary="Test" withSwitch="true">Content</ExpandableItem>`, {});
90 | const driver = await createExpandableItemDriver();
91 |
92 | // Check that the switch container (aria-hidden div) is present
93 | const switchContainer = driver.getSummary().locator('[aria-hidden="true"]');
94 | await expect(switchContainer).toBeVisible();
95 | await expect(driver.getIcon()).not.toBeVisible();
96 | });
97 |
98 | // =============================================================================
99 | // ACCESSIBILITY TESTS
100 | // =============================================================================
101 |
102 | test("component has correct accessibility attributes", async ({ initTestBed, createExpandableItemDriver, page }) => {
103 | await initTestBed(`<ExpandableItem summary="Test Summary">Content here</ExpandableItem>`, {});
104 | const driver = await createExpandableItemDriver();
105 |
106 | const summary = driver.getSummary();
107 |
108 | // Summary should have proper button role and ARIA attributes
109 | await expect(summary).toHaveRole("button");
110 | await expect(summary).toHaveAttribute("aria-expanded", "false");
111 | await expect(summary).toHaveAttribute("aria-disabled", "false");
112 | await expect(summary).toHaveAttribute("tabindex", "0");
113 | await expect(summary).toHaveAttribute("aria-controls");
114 | await expect(summary).toHaveAttribute("id");
115 | });
116 |
117 | test("component ARIA attributes update with expansion state", async ({ initTestBed, createExpandableItemDriver, page }) => {
118 | await initTestBed(`<ExpandableItem summary="Test Summary">Content here</ExpandableItem>`, {});
119 | const driver = await createExpandableItemDriver();
120 |
121 | const summary = driver.getSummary();
122 |
123 | // Initially collapsed
124 | await expect(summary).toHaveAttribute("aria-expanded", "false");
125 |
126 | // Expand
127 | await summary.click();
128 | await expect(summary).toHaveAttribute("aria-expanded", "true");
129 |
130 | // Collapse
131 | await summary.click();
132 | await expect(summary).toHaveAttribute("aria-expanded", "false");
133 | });
134 |
135 | test("component content region has correct ARIA attributes", async ({ initTestBed, createExpandableItemDriver, page }) => {
136 | await initTestBed(`<ExpandableItem summary="Test Summary" initiallyExpanded="true">Content here</ExpandableItem>`, {});
137 | const driver = await createExpandableItemDriver();
138 |
139 | const summary = driver.getSummary();
140 | const content = driver.getContent();
141 |
142 | // Content should have region role and proper labeling
143 | await expect(content).toHaveRole("region");
144 | await expect(content).toHaveAttribute("aria-labelledby");
145 | await expect(content).toHaveAttribute("id");
146 |
147 | // Verify ARIA relationship between summary and content
148 | const summaryId = await summary.getAttribute("id");
149 | const contentAriaLabelledBy = await content.getAttribute("aria-labelledby");
150 | const summaryAriaControls = await summary.getAttribute("aria-controls");
151 | const contentId = await content.getAttribute("id");
152 |
153 | expect(contentAriaLabelledBy).toBe(summaryId);
154 | expect(summaryAriaControls).toBe(contentId);
155 | });
156 |
157 | test("component is keyboard accessible with Enter key", async ({ initTestBed, createExpandableItemDriver, page }) => {
158 | const { testStateDriver } = await initTestBed(`<ExpandableItem summary="Test" onExpandedChange="arg => testState = arg">Content</ExpandableItem>`, {});
159 | const driver = await createExpandableItemDriver();
160 |
161 | const summary = driver.getSummary();
162 |
163 | // Focus the summary button
164 | await summary.focus();
165 | await expect(summary).toBeFocused();
166 |
167 | // Press Enter to toggle
168 | await summary.press("Enter");
169 | await expect.poll(testStateDriver.testState).toEqual(true);
170 | await expect(driver.getContent()).toBeVisible();
171 |
172 | // Press Enter again to collapse
173 | await summary.press("Enter");
174 | await expect.poll(testStateDriver.testState).toEqual(false);
175 | await expect(driver.getContent()).not.toBeVisible();
176 | });
177 |
178 | test("component is keyboard accessible with Space key", async ({ initTestBed, createExpandableItemDriver, page }) => {
179 | const { testStateDriver } = await initTestBed(`<ExpandableItem summary="Test" onExpandedChange="arg => testState = arg">Content</ExpandableItem>`, {});
180 | const driver = await createExpandableItemDriver();
181 |
182 | const summary = driver.getSummary();
183 |
184 | // Focus the summary button
185 | await summary.focus();
186 | await expect(summary).toBeFocused();
187 |
188 | // Press Space to toggle
189 | await summary.press(" ");
190 | await expect.poll(testStateDriver.testState).toEqual(true);
191 | await expect(driver.getContent()).toBeVisible();
192 |
193 | // Press Space again to collapse
194 | await summary.press(" ");
195 | await expect.poll(testStateDriver.testState).toEqual(false);
196 | await expect(driver.getContent()).not.toBeVisible();
197 | });
198 |
199 | test("component maintains focus after keyboard toggle", async ({ initTestBed, createExpandableItemDriver, page }) => {
200 | await initTestBed(`<ExpandableItem summary="Test">Content</ExpandableItem>`, {});
201 | const driver = await createExpandableItemDriver();
202 |
203 | const summary = driver.getSummary();
204 |
205 | // Focus and expand with keyboard
206 | await summary.focus();
207 | await expect(summary).toBeFocused();
208 | await summary.press("Enter");
209 | await expect(driver.getContent()).toBeVisible();
210 | await expect(summary).toBeFocused(); // Focus should remain
211 |
212 | // Collapse with keyboard
213 | await summary.press("Space");
214 | await expect(driver.getContent()).not.toBeVisible();
215 | await expect(summary).toBeFocused(); // Focus should still remain
216 | });
217 |
218 | test("component is focusable only when enabled", async ({ initTestBed, createExpandableItemDriver, page }) => {
219 | await initTestBed(`<ExpandableItem summary="Test" enabled="false">Content</ExpandableItem>`, {});
220 | const driver = await createExpandableItemDriver();
221 |
222 | const summary = driver.getSummary();
223 |
224 | // Disabled component should not be focusable (no tabindex attribute)
225 | await expect(summary).not.toHaveAttribute("tabindex");
226 | await expect(summary).toHaveAttribute("aria-disabled", "true");
227 | });
228 |
229 | test("component keyboard interaction works with switch variant", async ({ initTestBed, createExpandableItemDriver, page }) => {
230 | const { testStateDriver } = await initTestBed(`<ExpandableItem summary="Test" withSwitch="true" onExpandedChange="arg => testState = arg">Content</ExpandableItem>`, {});
231 | const driver = await createExpandableItemDriver();
232 |
233 | const summary = driver.getSummary();
234 |
235 | // Focus and activate with Enter
236 | await summary.focus();
237 | await summary.press("Enter");
238 | await expect.poll(testStateDriver.testState).toEqual(true);
239 | await expect(driver.getContent()).toBeVisible();
240 |
241 | // Deactivate with Space
242 | await summary.press(" ");
243 | await expect.poll(testStateDriver.testState).toEqual(false);
244 | await expect(driver.getContent()).not.toBeVisible();
245 | });
246 |
247 | test("component icon/switch container is hidden from screen readers", async ({ initTestBed, createExpandableItemDriver, page }) => {
248 | await initTestBed(`<ExpandableItem summary="Test">Content</ExpandableItem>`, {});
249 | const driver = await createExpandableItemDriver();
250 |
251 | // Icon container should be aria-hidden
252 | const iconContainer = driver.getSummary().locator('[class*="icon"]');
253 | await expect(iconContainer).toHaveAttribute("aria-hidden", "true");
254 | });
255 |
256 | test("component supports screen reader navigation", async ({ initTestBed, createExpandableItemDriver }) => {
257 | await initTestBed(`<ExpandableItem summary="Accessible Summary">Screen reader content</ExpandableItem>`, {});
258 | const driver = await createExpandableItemDriver();
259 |
260 | // Summary should be properly announced as a button
261 | const summary = driver.getSummary();
262 | await expect(summary).toHaveRole("button");
263 | await summary.click();
264 | await expect(driver.getContent()).toBeVisible();
265 | });
266 |
267 | // =============================================================================
268 | // VISUAL STATE TESTS
269 | // =============================================================================
270 |
271 | test("component applies theme variables", async ({ initTestBed, createExpandableItemDriver }) => {
272 | await initTestBed(`<ExpandableItem summary="Test">Content</ExpandableItem>`, {
273 | testThemeVars: { "backgroundColor-ExpandableItem": "rgb(255, 0, 0)" }
274 | });
275 | const driver = await createExpandableItemDriver();
276 | await expect(driver.component).toHaveCSS("background-color", "rgb(255, 0, 0)");
277 | });
278 |
279 | test("component applies disabled visual state", async ({ initTestBed, createExpandableItemDriver }) => {
280 | await initTestBed(`<ExpandableItem summary="Test" enabled="false">Content</ExpandableItem>`, {});
281 | const driver = await createExpandableItemDriver();
282 |
283 | const className = await driver.component.getAttribute('class');
284 | expect(className).toMatch(/disabled/);
285 | });
286 |
287 | test("component applies expanded visual state", async ({ initTestBed, createExpandableItemDriver }) => {
288 | await initTestBed(`<ExpandableItem summary="Test" initiallyExpanded="true">Content</ExpandableItem>`, {});
289 | const driver = await createExpandableItemDriver();
290 |
291 | // Instead of checking for CSS class, check that content is visible when initially expanded
292 | await expect(driver.getContent()).toBeVisible();
293 | await expect(driver.getContent()).toContainText("Content");
294 | });
295 |
296 | test("component applies withSwitch visual state", async ({ initTestBed, createExpandableItemDriver }) => {
297 | await initTestBed(`<ExpandableItem summary="Test" withSwitch="true">Content</ExpandableItem>`, {});
298 | const driver = await createExpandableItemDriver();
299 |
300 | const className = await driver.component.getAttribute('class');
301 | expect(className).toMatch(/withSwitch/);
302 | });
303 |
304 | test("component handles icon position styling", async ({ initTestBed, createExpandableItemDriver }) => {
305 | await initTestBed(`<ExpandableItem summary="Test" iconPosition="end">Content</ExpandableItem>`, {});
306 | const driver = await createExpandableItemDriver();
307 |
308 | const className = await driver.getSummary().getAttribute('class');
309 | expect(className).toMatch(/iconEnd/);
310 | });
311 |
312 | // =============================================================================
313 | // EDGE CASE TESTS
314 | // =============================================================================
315 |
316 | test("component handles null and undefined props gracefully", async ({ initTestBed, createExpandableItemDriver }) => {
317 | await initTestBed(`<ExpandableItem>Content without summary</ExpandableItem>`, {});
318 | const driver = await createExpandableItemDriver();
319 |
320 | await expect(driver.component).toBeVisible();
321 | await expect(driver.getSummary()).toBeVisible();
322 | });
323 |
324 | test("component handles empty summary", async ({ initTestBed, createExpandableItemDriver }) => {
325 | await initTestBed(`<ExpandableItem summary="">Content</ExpandableItem>`, {});
326 | const driver = await createExpandableItemDriver();
327 |
328 | await expect(driver.component).toBeVisible();
329 | await expect(driver.getSummary()).toBeVisible();
330 | });
331 |
332 | test("component handles empty content", async ({ initTestBed, createExpandableItemDriver }) => {
333 | await initTestBed(`<ExpandableItem summary="Test"></ExpandableItem>`, {});
334 | const driver = await createExpandableItemDriver();
335 |
336 | await expect(driver.component).toBeVisible();
337 | await driver.getSummary().click();
338 | await expect(driver.getContent()).toBeVisible();
339 | });
340 |
341 | test("component handles special characters in summary", async ({ initTestBed, createExpandableItemDriver }) => {
342 | await initTestBed(`<ExpandableItem summary="Test with émojis 🚀 & quotes">Content</ExpandableItem>`, {});
343 | const driver = await createExpandableItemDriver();
344 |
345 | await expect(driver.getSummaryContent()).toContainText("Test with émojis 🚀 & quotes");
346 | });
347 |
348 | test("component handles complex summary content", async ({ initTestBed, createExpandableItemDriver }) => {
349 | await initTestBed(`
350 | <ExpandableItem summary="Complex Summary">
351 | Content here
352 | </ExpandableItem>
353 | `, {});
354 | const driver = await createExpandableItemDriver();
355 |
356 | await expect(driver.getSummary()).toBeVisible();
357 | await expect(driver.getSummaryContent()).toContainText("Complex Summary");
358 | });
359 |
360 | test("component handles disabled state interaction", async ({ initTestBed, createExpandableItemDriver }) => {
361 | await initTestBed(`<ExpandableItem summary="Test" enabled="false">Content</ExpandableItem>`, {});
362 | const driver = await createExpandableItemDriver();
363 |
364 | // Should not expand when disabled - use force click since the element is aria-disabled
365 | await driver.getSummary().click({ force: true });
366 | await expect(driver.getContent()).not.toBeVisible();
367 | });
368 |
369 | // =============================================================================
370 | // PERFORMANCE TESTS
371 | // =============================================================================
372 |
373 | test("component memoization prevents unnecessary re-renders", async ({ initTestBed, createExpandableItemDriver }) => {
374 | const { testStateDriver } = await initTestBed(`
375 | <ExpandableItem
376 | summary="Test"
377 | onExpandedChange="testState = (testState || 0) + 1">
378 | Content
379 | </ExpandableItem>
380 | `, {});
381 | const driver = await createExpandableItemDriver();
382 |
383 | // Expand
384 | await driver.getSummary().click();
385 | await expect.poll(testStateDriver.testState).toEqual(1);
386 |
387 | // Collapse
388 | await driver.getSummary().click();
389 | await expect.poll(testStateDriver.testState).toEqual(2);
390 | });
391 |
392 | test("component handles rapid toggling", async ({ initTestBed, createExpandableItemDriver }) => {
393 | await initTestBed(`<ExpandableItem summary="Test">Content</ExpandableItem>`, {});
394 | const driver = await createExpandableItemDriver();
395 |
396 | // Rapid clicks
397 | await driver.getSummary().click();
398 | await driver.getSummary().click();
399 | await driver.getSummary().click();
400 |
401 | // Should end up collapsed (odd number of clicks)
402 | await expect(driver.getContent()).toBeVisible();
403 | });
404 |
405 | test("component handles large content efficiently", async ({ initTestBed, createExpandableItemDriver }) => {
406 | const largeContent = "Very long content ".repeat(100);
407 | await initTestBed(`<ExpandableItem summary="Test">${largeContent}</ExpandableItem>`, {});
408 | const driver = await createExpandableItemDriver();
409 |
410 | await driver.getSummary().click();
411 | await expect(driver.getContent()).toBeVisible();
412 | await expect(driver.getContent()).toContainText("Very long content");
413 | });
414 |
415 | // =============================================================================
416 | // INTEGRATION TESTS
417 | // =============================================================================
418 |
419 | test("component works in layout contexts", async ({ initTestBed, createExpandableItemDriver }) => {
420 | await initTestBed(`
421 | <VStack gap="2">
422 | <ExpandableItem summary="First Item">First content</ExpandableItem>
423 | <ExpandableItem summary="Second Item">Second content</ExpandableItem>
424 | </VStack>
425 | `, {});
426 | const driver = await createExpandableItemDriver();
427 |
428 | await expect(driver.component).toBeVisible();
429 | // Use first() to get the first ExpandableItem's summary content
430 | await expect(driver.getSummaryContent().first()).toContainText("First Item");
431 | });
432 |
433 | test("component API methods work correctly", async ({ initTestBed, page }) => {
434 | await initTestBed(`
435 | <Fragment>
436 | <ExpandableItem id="expandable" summary="Test">Content</ExpandableItem>
437 | <Button testId="expandBtn" onClick="expandable.expand()">Expand</Button>
438 | <Button testId="collapseBtn" onClick="expandable.collapse()">Collapse</Button>
439 | <Button testId="toggleBtn" onClick="expandable.toggle()">Toggle</Button>
440 | <Button testId="checkBtn" onClick="testState = expandable.isExpanded()">Check</Button>
441 | </Fragment>
442 | `, {});
443 |
444 | const { testStateDriver } = await initTestBed(`
445 | <Fragment>
446 | <ExpandableItem id="expandable" summary="Test">Content</ExpandableItem>
447 | <Button testId="expandBtn" onClick="expandable.expand()">Expand</Button>
448 | <Button testId="collapseBtn" onClick="expandable.collapse()">Collapse</Button>
449 | <Button testId="toggleBtn" onClick="expandable.toggle()">Toggle</Button>
450 | <Button testId="checkBtn" onClick="testState = expandable.isExpanded()">Check</Button>
451 | </Fragment>
452 | `, {});
453 |
454 | // Test expand API
455 | await page.getByTestId("expandBtn").click();
456 | await expect(page.locator('[class*="_content_"]')).toBeVisible();
457 |
458 | // Test isExpanded API
459 | await page.getByTestId("checkBtn").click();
460 | await expect.poll(testStateDriver.testState).toEqual(true);
461 |
462 | // Test collapse API
463 | await page.getByTestId("collapseBtn").click();
464 | await expect(page.locator('[class*="_content_"]')).not.toBeVisible();
465 |
466 | // Test toggle API
467 | await page.getByTestId("toggleBtn").click();
468 | await expect(page.locator('[class*="_content_"]')).toBeVisible();
469 | });
470 |
471 | test("component works with forms", async ({ initTestBed, createExpandableItemDriver }) => {
472 | await initTestBed(`
473 | <Form>
474 | <ExpandableItem summary="Advanced Options">
475 | <FormItem label="Advanced Setting">
476 | <TextBox name="advanced" />
477 | </FormItem>
478 | </ExpandableItem>
479 | </Form>
480 | `, {});
481 | const driver = await createExpandableItemDriver();
482 |
483 | await driver.getSummary().click();
484 | await expect(driver.getContent()).toBeVisible();
485 | await expect(driver.getContent()).toContainText("Advanced Setting");
486 | });
487 |
488 | test("component event handlers work correctly", async ({ initTestBed, createExpandableItemDriver }) => {
489 | const { testStateDriver } = await initTestBed(`
490 | <ExpandableItem
491 | summary="Test"
492 | onExpandedChange="arg =>testState = arg">
493 | Content
494 | </ExpandableItem>
495 | `, {});
496 | const driver = await createExpandableItemDriver();
497 |
498 | // Click to expand - should set testState to true
499 | await driver.getSummary().click();
500 | await expect.poll(testStateDriver.testState).toEqual(true);
501 |
502 | // Click to collapse - should set testState to false
503 | await driver.getSummary().click();
504 | await expect.poll(testStateDriver.testState).toEqual(false);
505 | });
506 |
507 | test("component works with switch variant", async ({ initTestBed, createExpandableItemDriver, page }) => {
508 | await initTestBed(`<ExpandableItem summary="Test" withSwitch="true">Content</ExpandableItem>`, {});
509 | const driver = await createExpandableItemDriver();
510 |
511 | // With switch variant, users should click the summary (button) to toggle
512 | // The switch itself is decorative and aria-hidden
513 | await driver.getSummary().click();
514 | await expect(driver.getContent()).toBeVisible();
515 |
516 | // Click the summary again to toggle back
517 | await driver.getSummary().click();
518 | await expect(driver.getContent()).not.toBeVisible();
519 | });
520 |
521 | test("component works in nested scenarios", async ({ initTestBed, createExpandableItemDriver }) => {
522 | await initTestBed(`
523 | <ExpandableItem summary="Parent">
524 | <ExpandableItem summary="Child">
525 | Nested content
526 | </ExpandableItem>
527 | </ExpandableItem>
528 | `, {});
529 | const driver = await createExpandableItemDriver();
530 |
531 | // Expand parent
532 | await driver.getSummary().click();
533 | await expect(driver.getContent()).toBeVisible();
534 |
535 | // Should see nested expandable item
536 | await expect(driver.getContent()).toContainText("Child");
537 | });
538 |
```
--------------------------------------------------------------------------------
/xmlui/scripts/generate-docs/get-docs.mjs:
--------------------------------------------------------------------------------
```
1 | import { basename, join, extname, relative } from "path";
2 | import { lstatSync } from "fs";
3 | import { unlink, readdir, mkdir, writeFile, rm, readFile } from "fs/promises";
4 | import { ErrorWithSeverity, LOGGER_LEVELS, logger } from "./logger.mjs";
5 | import { winPathToPosix, deleteFileIfExists, fromKebabtoReadable } from "./utils.mjs";
6 | import { DocsGenerator } from "./DocsGenerator.mjs";
7 | import { collectedComponentMetadata } from "../../dist/metadata/xmlui-metadata.js";
8 | import { FOLDERS } from "./folders.mjs";
9 | import { existsSync } from "fs";
10 | import { configManager, pathResolver } from "./configuration-management.mjs";
11 | import {
12 | COMPONENT_STATES,
13 | FILE_EXTENSIONS,
14 | FOLDER_NAMES,
15 | SUMMARY_CONFIG,
16 | PACKAGE_PATTERNS,
17 | METADATA_PROPERTIES,
18 | TEMPLATE_STRINGS,
19 | ERROR_HANDLING,
20 | URL_REFERENCES,
21 | COMPONENT_NAVIGATION,
22 | FILE_PATHS,
23 | PATH_CONSTANTS,
24 | TEXT_CONSTANTS,
25 | ERROR_CONTEXTS,
26 | COMPONENT_NAV_ERRORS,
27 | EXTENSIONS_NAVIGATION,
28 | } from "./constants.mjs";
29 | import { handleNonFatalError, withErrorHandling } from "./error-handling.mjs";
30 |
31 | // --- Main
32 |
33 | // Prefilter metadata by isHtmlTag
34 | const filterByProps = { [METADATA_PROPERTIES.IS_HTML_TAG]: true };
35 | const [components] = partitionMetadata(collectedComponentMetadata, filterByProps);
36 |
37 | await generateComponents(components);
38 |
39 | // --- Extensions
40 | const extensionsConfig = await configManager.loadExtensionsConfig();
41 | const packagesMetadata = await dynamicallyLoadExtensionPackages(extensionsConfig);
42 | await generateExtensionPackages(packagesMetadata, extensionsConfig);
43 |
44 | /**
45 | * Generates NavLink elements for components and replaces the generated content in Main.xmlui
46 | * @param {Record<string, string>} componentsAndFileNames The components and their filenames
47 | */
48 | async function generateComponentRefLinks(
49 | componentsAndFileNames,
50 | navConfigObj = COMPONENT_NAVIGATION,
51 | ) {
52 | try {
53 | // Get component names (excluding the summary file)
54 | const componentNames = Object.keys(componentsAndFileNames)
55 | .filter((name) => name !== SUMMARY_CONFIG.COMPONENTS.fileName)
56 | .sort();
57 |
58 | // Create NavLink elements for each component
59 | const componentNavLinks = componentNames.map((componentName) =>
60 | navConfigObj.TEMPLATES.NAVLINK(componentName),
61 | );
62 |
63 | // Add the Components Overview link at the top
64 | const allNavLinks = [
65 | navConfigObj.OVERVIEW_LINK
66 | ? navConfigObj.TEMPLATES.OVERVIEW_NAVLINK(
67 | navConfigObj.OVERVIEW_LINK.LABEL,
68 | navConfigObj.OVERVIEW_LINK.TO,
69 | )
70 | : [],
71 | ...componentNavLinks,
72 | ];
73 |
74 | // Join with newlines - store in memory instead of writing to file
75 | const navLinksContent = allNavLinks.join(TEXT_CONSTANTS.NEWLINE_SEPARATOR);
76 |
77 | // Find and display content between GENERATED CONTENT delimiters in Main.xmlui
78 | const { indentationDepth } = await findAndDisplayGeneratedContent();
79 |
80 | // Replace the generated content with the in-memory NavLinks content
81 | await replaceGeneratedContentInMainXmlui(navLinksContent, indentationDepth);
82 | } catch (error) {
83 | throw new Error(COMPONENT_NAV_ERRORS.COMPONENT_NAV_FAILED);
84 | }
85 | }
86 |
87 | /**
88 | * Finds and displays the content between GENERATED CONTENT delimiters in Main.xmlui
89 | */
90 | async function findAndDisplayGeneratedContent() {
91 | try {
92 | const mainXmluiPath = join(FOLDERS.docsRoot, FILE_PATHS.MAIN_XMLUI);
93 |
94 | if (!existsSync(mainXmluiPath)) {
95 | throw new Error(COMPONENT_NAV_ERRORS.MAIN_XMLUI_NOT_FOUND(mainXmluiPath));
96 | }
97 |
98 | const fileContent = await readFile(mainXmluiPath, TEXT_CONSTANTS.UTF8_ENCODING);
99 |
100 | // Define the delimiter patterns using regex
101 | const startDelimiterRegex = COMPONENT_NAVIGATION.DELIMITERS.START_REGEX;
102 | const endDelimiterRegex = COMPONENT_NAVIGATION.DELIMITERS.END_REGEX;
103 |
104 | const startMatch = fileContent.match(startDelimiterRegex);
105 | const endMatch = fileContent.match(endDelimiterRegex);
106 |
107 | if (!startMatch) {
108 | throw new Error(COMPONENT_NAV_ERRORS.START_DELIMITER_NOT_FOUND);
109 | }
110 |
111 | if (!endMatch) {
112 | throw new Error(COMPONENT_NAV_ERRORS.END_DELIMITER_NOT_FOUND);
113 | }
114 |
115 | const startIndex = startMatch.index;
116 | const endIndex = endMatch.index;
117 |
118 | if (startIndex >= endIndex) {
119 | throw new Error(COMPONENT_NAV_ERRORS.INVALID_DELIMITER_ORDER);
120 | }
121 |
122 | // Extract content between delimiters (excluding the delimiters themselves)
123 | const generatedContentStart = startIndex + startMatch[0].length;
124 | const generatedContent = fileContent.substring(generatedContentStart, endIndex);
125 |
126 | // Calculate indentation depth
127 | const indentationDepth = calculateIndentationDepth(generatedContent);
128 |
129 | return { indentationDepth };
130 | } catch (error) {
131 | return { indentationDepth: 0 };
132 | }
133 | }
134 |
135 | /**
136 | * Generates a comprehensive components overview file with a table of all components
137 | * @param {string} overviewFile - Path to the overview file to generate
138 | * @param {string} summaryTitle - Title for the overview section
139 | * @param {object} componentsAndFileNames - Object containing component metadata
140 | */
141 | async function generateComponentsOverview(overviewFile, summaryTitle, componentsAndFileNames) {
142 | try {
143 | // Get component names (excluding the summary file)
144 | const componentNames = Object.keys(componentsAndFileNames)
145 | .filter((name) => name !== SUMMARY_CONFIG.COMPONENTS.fileName)
146 | .sort();
147 |
148 | // Log the number of components
149 | logger.info(`Components Overview: ${componentNames.length} components`);
150 |
151 | // Create table header
152 | const tableHeader = `# ${summaryTitle} [#components-overview]
153 |
154 | | Component | Description |
155 | | :---: | --- |`;
156 |
157 | // Create table rows for each component from the original metadata
158 | const tableRows = componentNames.map((componentName) => {
159 | // Get description from original metadata
160 | const originalMetadata = collectedComponentMetadata[componentName];
161 | const description = originalMetadata?.description || TEXT_CONSTANTS.NO_DESCRIPTION_AVAILABLE;
162 |
163 | // Format the table row with correct relative path
164 | return `| [${componentName}](./${componentName}) | ${description} |`;
165 | });
166 |
167 | // Combine header and rows
168 | const tableContent = [tableHeader, ...tableRows].join(TEXT_CONSTANTS.NEWLINE_SEPARATOR);
169 |
170 | // Write to file
171 | await writeFile(overviewFile, tableContent);
172 | } catch (error) {
173 | throw new Error(COMPONENT_NAV_ERRORS.OVERVIEW_GENERATION_FAILED);
174 | }
175 | }
176 |
177 | // --- Helpers
178 |
179 | async function generateExtensionPackages(metadata, extensionsConfig) {
180 | const outputPaths = pathResolver.getOutputPaths();
181 | const extensionsFolder = outputPaths.extensions;
182 |
183 | const extensionNamesAndCompNames = [];
184 | const unlistedFolders = [];
185 | for (const packageName in metadata) {
186 | // Just to be sure we don't generate anything internal
187 | if (metadata[packageName].state === COMPONENT_STATES.INTERNAL) {
188 | continue;
189 | }
190 |
191 | const packageFolder = join(extensionsFolder, packageName);
192 |
193 | if (!existsSync(packageFolder)) {
194 | await mkdir(packageFolder, { recursive: true });
195 | }
196 |
197 | const extensionGenerator = new DocsGenerator(
198 | metadata[packageName].metadata,
199 | {
200 | sourceFolder: metadata[packageName].sourceFolder,
201 | outFolder: packageFolder,
202 | examplesFolder: join(FOLDERS.docsRoot, FOLDER_NAMES.COMPONENT_SAMPLES),
203 | },
204 | { excludeComponentStatuses: extensionsConfig?.excludeComponentStatuses },
205 | );
206 |
207 | if (extensionsConfig?.cleanFolder) {
208 | await cleanFolder(packageFolder);
209 | }
210 |
211 | let componentsAndFileNames = extensionGenerator.generateDocs();
212 | if (Object.keys(componentsAndFileNames).length === 0) {
213 | if (existsSync(packageFolder)) {
214 | await rm(packageFolder, { recursive: true });
215 | }
216 | unlistedFolders.push(packageName);
217 | continue;
218 | }
219 |
220 | const summaryTitle = SUMMARY_CONFIG.EXTENSIONS.title;
221 | const summaryFileName = SUMMARY_CONFIG.EXTENSIONS.fileName;
222 |
223 | // In both of these cases, we are writing to the same file
224 | const indexFile = join(packageFolder, `${summaryFileName}.md`);
225 | deleteFileIfExists(indexFile);
226 |
227 | await extensionGenerator.exportMetadataToJson(FOLDER_NAMES.EXTENSIONS, packageName);
228 |
229 | componentsAndFileNames = insertKeyAt(summaryFileName, summaryTitle, componentsAndFileNames, 0);
230 | await extensionGenerator.generatePackageDescription(
231 | metadata[packageName].description,
232 | TEMPLATE_STRINGS.PACKAGE_HEADER(packageName),
233 | indexFile,
234 | );
235 |
236 | // Generate a _meta.json for the files in the extension
237 | extensionGenerator.writeMetaSummary(componentsAndFileNames, packageFolder);
238 |
239 | extensionNamesAndCompNames.push({
240 | packageName,
241 | fileNames: Object.keys(componentsAndFileNames).filter(
242 | (n) => SUMMARY_CONFIG.EXTENSIONS.fileName !== n,
243 | ),
244 | });
245 | }
246 |
247 | const packageGroupsWithComponentLinks = extensionNamesAndCompNames.map((ext) => {
248 | logger.info(`Generating NavLinks for Extension Package: ${ext.packageName}`);
249 |
250 | const compLinks = ext.fileNames.map((compName) => {
251 | return EXTENSIONS_NAVIGATION.TEMPLATES.NAVLINK(ext.packageName, compName);
252 | });
253 |
254 | // Add the Extensions Overview link at the top
255 | const packageLinks = [
256 | EXTENSIONS_NAVIGATION.TEMPLATES.OVERVIEW_NAVLINK(
257 | EXTENSIONS_NAVIGATION.OVERVIEW_LINK.LABEL,
258 | EXTENSIONS_NAVIGATION.OVERVIEW_LINK.TO(ext.packageName),
259 | ),
260 | ...compLinks,
261 | ];
262 |
263 | return EXTENSIONS_NAVIGATION.TEMPLATES.NAVGROUP(
264 | fromKebabtoReadable(ext.packageName),
265 | packageLinks.join("\n"),
266 | );
267 | });
268 | // Join with newlines - store in memory instead of writing to file
269 | const navLinksContent = packageGroupsWithComponentLinks.join(TEXT_CONSTANTS.NEWLINE_SEPARATOR);
270 |
271 | // Find and display content between GENERATED CONTENT delimiters in Main.xmlui
272 | const { indentationDepth } = await findAndDisplayGeneratedContent();
273 |
274 | // Replace the generated content with the in-memory NavLinks content
275 | await replaceGeneratedContentInMainXmlui(
276 | navLinksContent,
277 | indentationDepth,
278 | EXTENSIONS_NAVIGATION,
279 | );
280 |
281 | // generate a _meta.json for the folder names
282 | await withErrorHandling(
283 | async () => {
284 | const extensionPackagesMetafile = join(extensionsFolder, FILE_EXTENSIONS.METADATA);
285 |
286 | const folderNames = Object.fromEntries(
287 | Object.keys(metadata)
288 | .filter((m) => !unlistedFolders.includes(m))
289 | .map((name) => {
290 | return [name, `${fromKebabtoReadable(name)} Package`];
291 | }),
292 | );
293 |
294 | /* const existingMeta = JSON.parse(readFileSync(extensionPackagesMetafile, "utf-8"));
295 | const updatedMeta = Object.entries(folderNames).reduce((acc, [key, value], idx) => {
296 | return insertKeyAt(key, value, acc, Object.keys(acc).length === 0 ? 0 : idx + 1);
297 | }, existingMeta || {}); */
298 |
299 | // Do not include the summary file in the _meta.json
300 | deleteFileIfExists(extensionPackagesMetafile);
301 | await writeFile(extensionPackagesMetafile, JSON.stringify(folderNames, null, 2));
302 | },
303 | ERROR_CONTEXTS.EXTENSION_PACKAGES_METADATA,
304 | ERROR_HANDLING.EXIT_CODES.FILE_NOT_FOUND,
305 | );
306 | }
307 |
308 | async function generateComponents(metadata) {
309 | const componentsConfig = await configManager.loadComponentsConfig();
310 | const outputPaths = pathResolver.getOutputPaths();
311 | const outputFolder = outputPaths.components;
312 |
313 | const metadataGenerator = new DocsGenerator(
314 | metadata,
315 | {
316 | sourceFolder: pathResolver.resolvePath(
317 | PATH_CONSTANTS.XMLUI_SRC_COMPONENTS,
318 | PATH_CONSTANTS.WORKSPACE,
319 | ),
320 | // --- CHANGE: Now documents are generated in the a new folder, outside of pages
321 | outFolder: outputFolder,
322 | // outFolder: join(FOLDERS.docsRoot, FOLDER_NAMES.PAGES, FOLDER_NAMES.COMPONENTS),
323 | examplesFolder: pathResolver.resolvePath(
324 | PATH_CONSTANTS.DOCS_COMPONENT_SAMPLES,
325 | PATH_CONSTANTS.WORKSPACE,
326 | ),
327 | },
328 | { excludeComponentStatuses: componentsConfig?.excludeComponentStatuses },
329 | );
330 |
331 | if (componentsConfig?.cleanFolder) {
332 | // --- CHANGE: Now documents are generated in the a new folder, outside of pages
333 | // await cleanFolder(join(FOLDERS.pages, FOLDER_NAMES.COMPONENTS));
334 | await cleanFolder(outputFolder);
335 | }
336 |
337 | let componentsAndFileNames = metadataGenerator.generateDocs();
338 |
339 | // Generate ComponentRefLinks.txt with component names
340 | await generateComponentRefLinks(componentsAndFileNames);
341 |
342 | const summaryTitle = SUMMARY_CONFIG.COMPONENTS.title;
343 | const summaryFileName = SUMMARY_CONFIG.COMPONENTS.fileName;
344 | await metadataGenerator.exportMetadataToJson(FOLDER_NAMES.COMPONENTS);
345 | componentsAndFileNames = insertKeyAt(summaryFileName, summaryTitle, componentsAndFileNames, 0);
346 |
347 | // Generate the overview file for components
348 | const overviewFile = join(outputFolder, `${summaryFileName}.md`);
349 | await generateComponentsOverview(overviewFile, summaryTitle, componentsAndFileNames);
350 |
351 | metadataGenerator.writeMetaSummary(componentsAndFileNames, outputFolder);
352 |
353 | await metadataGenerator.generatePermalinksForHeaders();
354 |
355 | await metadataGenerator.createMetadataJsonForLanding(URL_REFERENCES.DOCS, "components");
356 | }
357 |
358 | // NOTE: Unused - we are not generating Html component docs
359 | async function generateHtmlTagComponents(metadata) {
360 | const componentsConfig = await configManager.loadComponentsConfig();
361 | const outputPaths = pathResolver.getOutputPaths();
362 | const metadataGenerator = new DocsGenerator(
363 | metadata,
364 | {
365 | sourceFolder: pathResolver.resolvePath("xmlui/src/components", "project"),
366 | outFolder: outputPaths.pages + "/components",
367 | examplesFolder: pathResolver.resolvePath("docs/component-samples", "project"),
368 | },
369 | { excludeComponentStatuses: componentsConfig?.excludeComponentStatuses },
370 | );
371 | }
372 |
373 | async function cleanFolder(folderToClean) {
374 | if (!existsSync(folderToClean)) return;
375 |
376 | // NOTE: This is the important part: we only delete .mdx and .md files and the _meta.json
377 | const cleanCondition = (file) =>
378 | FILE_EXTENSIONS.MARKDOWN.includes(extname(file)) || basename(file) === FILE_EXTENSIONS.METADATA;
379 |
380 | await withErrorHandling(
381 | async () => {
382 | const files = await readdir(folderToClean);
383 | const unlinkPromises = files
384 | .filter(cleanCondition)
385 | .map((file) => unlink(join(folderToClean, file)));
386 |
387 | await Promise.all(unlinkPromises).catch((err) => {
388 | throw new ErrorWithSeverity(err.message, LOGGER_LEVELS.error);
389 | });
390 | },
391 | ERROR_CONTEXTS.FOLDER_CLEANUP,
392 | ERROR_HANDLING.EXIT_CODES.FILE_NOT_FOUND,
393 | );
394 | }
395 |
396 | /**
397 | * Dynamically loads extension package metadata from the `packages` folder.
398 | * Loading of metadata is only possible if the package has a `dist` folder
399 | * and is built using the `build:meta` script which produces a {file-name}-metadata.js file.
400 | * To do this, the package must export a `componentMetadata` object in the `meta` folder containing:
401 | * - name: The name of the package
402 | * - description: A brief description of the package
403 | * - metadata: An object containing component metadata
404 | * - state: The state of the package (e.g., "experimental", "stable", "internal")
405 | *
406 | * @param {object} extensionsConfig Configuration object for extensions
407 | * @returns {Promise<Record<
408 | * string,
409 | * { sourceFolder: string, description: string, metadata: Record<string, any> }
410 | * >>} imported metadata
411 | */
412 | async function dynamicallyLoadExtensionPackages(extensionsConfig) {
413 | const defaultPackageState = COMPONENT_STATES.EXPERIMENTAL;
414 |
415 | // --- Find all packages in the `packages` folder that start with xmlui- OR are listed in the config
416 | const extensionPackagesFolder = join(FOLDERS.projectRoot, PATH_CONSTANTS.PACKAGES);
417 | let packageDirectories = [];
418 | if (!!extensionsConfig?.includeByName && extensionsConfig.includeByName.length > 0) {
419 | packageDirectories = extensionsConfig.includeByName
420 | .map((name) => join(extensionPackagesFolder, name))
421 | .filter((dir) => existsSync(dir) && lstatSync(dir).isDirectory());
422 | } else {
423 | packageDirectories = (await readdir(extensionPackagesFolder))
424 | .filter((entry) => {
425 | return (
426 | entry.startsWith(PACKAGE_PATTERNS.XMLUI_PREFIX) &&
427 | lstatSync(join(extensionPackagesFolder, entry)).isDirectory()
428 | );
429 | })
430 | .map((dir) => join(extensionPackagesFolder, dir));
431 | }
432 | if (extensionsConfig?.excludeByName && extensionsConfig.excludeByName.length > 0) {
433 | packageDirectories = packageDirectories.filter(
434 | (dir) => !extensionsConfig?.excludeByName?.includes(basename(dir)),
435 | );
436 | }
437 |
438 | const importedMetadata = {};
439 | for (let dir of packageDirectories) {
440 | const extensionPackage = {
441 | sourceFolder: dir, //join(dir, FOLDER_NAMES.SRC),
442 | description: "",
443 | metadata: {},
444 | state: defaultPackageState,
445 | };
446 | try {
447 | const packageFolderDist = join(dir, FOLDER_NAMES.DIST);
448 | if (!existsSync(packageFolderDist)) {
449 | continue;
450 | }
451 | const distContents = await readdir(packageFolderDist);
452 | for (const file of distContents) {
453 | let filePath = join(packageFolderDist, file);
454 | if (
455 | filePath.endsWith(`${basename(dir)}${PACKAGE_PATTERNS.METADATA_SUFFIX}`) &&
456 | existsSync(filePath)
457 | ) {
458 | filePath = winPathToPosix(relative(FOLDERS.script, filePath));
459 | const { componentMetadata } = await import(filePath);
460 | if (!componentMetadata) {
461 | continue;
462 | }
463 | if (!componentMetadata.metadata) {
464 | continue;
465 | }
466 |
467 | extensionPackage.metadata = componentMetadata.metadata ?? {};
468 | extensionPackage.description = componentMetadata.description ?? "";
469 | extensionPackage.state = componentMetadata.state ?? defaultPackageState;
470 | }
471 | }
472 | // Ignore internal packages
473 | if (extensionPackage.state === COMPONENT_STATES.INTERNAL) {
474 | continue;
475 | }
476 | importedMetadata[basename(dir)] = extensionPackage;
477 | } catch (error) {
478 | handleNonFatalError(error, `${ERROR_CONTEXTS.LOADING_EXTENSION_PACKAGE}: ${basename(dir)}`);
479 | }
480 | }
481 | return importedMetadata;
482 | }
483 |
484 | function partitionMetadata(metadata, filterByProps) {
485 | const [components, htmlTagComponents] = partitionObject(metadata, (compData, c) => {
486 | if (!filterByProps || Object.keys(filterByProps).length === 0) {
487 | return true;
488 | }
489 | for (const [propName, propValue] of Object.entries(filterByProps)) {
490 | if (compData[propName] === undefined) {
491 | return true;
492 | }
493 | if (compData[propName] === propValue) {
494 | return false;
495 | }
496 | }
497 | });
498 |
499 | return [components, htmlTagComponents];
500 | }
501 |
502 | /**
503 | * Partition an object into two objects based on a discriminator function.
504 | * @template T
505 | * @param {Record<string, T>} obj Input object to partition
506 | * @param {(value: T, key: string) => boolean} discriminator Function to decide partitioning
507 | * @returns {[Record<string, T>, Record<string, T>]} An array containing two disjoint objects
508 | */
509 | function partitionObject(obj, discriminator) {
510 | return Object.entries(obj).reduce(
511 | ([pass, fail], [key, value]) => {
512 | if (discriminator(value, key)) {
513 | return [{ ...pass, [key]: value }, fail];
514 | } else {
515 | return [pass, { ...fail, [key]: value }];
516 | }
517 | },
518 | [{}, {}],
519 | );
520 | }
521 |
522 | function insertKeyAt(key, value, obj, pos) {
523 | return Object.keys(obj).reduce((ac, a, i) => {
524 | if (i === pos) ac[key] = value;
525 | ac[a] = obj[a];
526 | return ac;
527 | }, {});
528 | }
529 |
530 | /**
531 | * Calculates the indentation depth of content by finding the minimum leading whitespace
532 | * @param {string} content - The content to analyze
533 | * @returns {number} The number of whitespace characters representing the indentation depth
534 | */
535 | function calculateIndentationDepth(content) {
536 | if (!content || content.trim().length === 0) {
537 | return 0;
538 | }
539 |
540 | const lines = content.split("\n");
541 | let minIndentation = Infinity;
542 |
543 | for (const line of lines) {
544 | // Skip empty lines
545 | if (line.trim().length === 0) {
546 | continue;
547 | }
548 |
549 | // Count leading whitespace characters
550 | const leadingWhitespace = line.match(/^(\s*)/)[1];
551 | const indentationCount = leadingWhitespace.length;
552 |
553 | // Track minimum indentation
554 | if (indentationCount < minIndentation) {
555 | minIndentation = indentationCount;
556 | }
557 | }
558 |
559 | // Return 0 if no lines had content or all lines were empty
560 | return minIndentation === Infinity ? 0 : minIndentation;
561 | }
562 |
563 | /**
564 | * Replaces the content between GENERATED CONTENT delimiters in Main.xmlui with NavLink content
565 | * @param {string} navLinksContent - The NavLink content to insert
566 | * @param {number} indentationDepth - Number of spaces to indent each line
567 | */
568 | async function replaceGeneratedContentInMainXmlui(
569 | navLinksContent,
570 | indentationDepth,
571 | NavConfigObj = COMPONENT_NAVIGATION,
572 | ) {
573 | try {
574 | const mainXmluiPath = join(FOLDERS.docsRoot, FILE_PATHS.MAIN_XMLUI);
575 |
576 | if (!existsSync(mainXmluiPath)) {
577 | throw new Error(COMPONENT_NAV_ERRORS.MAIN_XMLUI_NOT_FOUND(mainXmluiPath));
578 | }
579 |
580 | // Read the Main.xmlui file
581 | const fileContent = await readFile(mainXmluiPath, TEXT_CONSTANTS.UTF8_ENCODING);
582 |
583 | // Define the delimiter patterns using regex
584 | const startDelimiterRegex = NavConfigObj.DELIMITERS.START_REGEX;
585 | const endDelimiterRegex = NavConfigObj.DELIMITERS.END_REGEX;
586 |
587 | const startMatch = fileContent.match(startDelimiterRegex);
588 | const endMatch = fileContent.match(endDelimiterRegex);
589 |
590 | if (!startMatch || !endMatch) {
591 | throw new Error(COMPONENT_NAV_ERRORS.DELIMITERS_NOT_FOUND);
592 | }
593 |
594 | const startIndex = startMatch.index;
595 | const endIndex = endMatch.index;
596 |
597 | // Create indented content from the in-memory NavLinks content
598 | const indentString = " ".repeat(indentationDepth);
599 | const indentedLines = navLinksContent
600 | .split(TEXT_CONSTANTS.NEWLINE_SEPARATOR)
601 | .filter((line) => line.trim().length > 0) // Remove empty lines
602 | .map((line) => indentString + line);
603 |
604 | // Build the new content with proper formatting
605 | const newGeneratedContent =
606 | TEXT_CONSTANTS.NEWLINE_SEPARATOR +
607 | indentedLines.join(TEXT_CONSTANTS.NEWLINE_SEPARATOR) +
608 | TEXT_CONSTANTS.NEWLINE_SEPARATOR +
609 | indentString;
610 |
611 | // Replace the content between delimiters
612 | const beforeDelimiter = fileContent.substring(0, startIndex + startMatch[0].length);
613 | const afterDelimiter = fileContent.substring(endIndex);
614 | const newFileContent = beforeDelimiter + newGeneratedContent + afterDelimiter;
615 |
616 | // Write the updated content back to the file
617 | await writeFile(mainXmluiPath, newFileContent, TEXT_CONSTANTS.UTF8_ENCODING);
618 | } catch (error) {
619 | throw new Error(COMPONENT_NAV_ERRORS.CONTENT_REPLACEMENT_FAILED);
620 | }
621 | }
622 |
```
--------------------------------------------------------------------------------
/xmlui/src/components/TimeInput/TimeInput.module.scss:
--------------------------------------------------------------------------------
```scss
1 | @use "../../components-core/theming/themes" as t;
2 |
3 | // --- This code snippet is required to collect the theme variables used in this module
4 | $themeVars: ();
5 | @function createThemeVar($componentVariable) {
6 | $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global;
7 | @return t.getThemeVar($themeVars, $componentVariable);
8 | }
9 |
10 | $componentName: "TimeInput";
11 | $themeVars: t.composePaddingVars($themeVars, $componentName);
12 |
13 | // --- TimeInput specific theme variables
14 | $color-divider-TimeInput: createThemeVar("color-divider-#{$componentName}");
15 | $spacing-divider-TimeInput: createThemeVar("spacing-divider-#{$componentName}");
16 | $width-input-TimeInput: createThemeVar("width-input-#{$componentName}");
17 | $minWidth-input-TimeInput: createThemeVar("minWidth-input-#{$componentName}");
18 | $textAlign-input-TimeInput: createThemeVar("textAlign-input-#{$componentName}");
19 | $fontSize-input-TimeInput: createThemeVar("fontSize-input-#{$componentName}");
20 | $borderRadius-input-TimeInput: createThemeVar("borderRadius-input-#{$componentName}");
21 | $backgroundColor-input-TimeInput-invalid: createThemeVar(
22 | "backgroundColor-input-#{$componentName}-invalid"
23 | );
24 | $padding-button-TimeInput: createThemeVar("padding-button-#{$componentName}");
25 | $borderRadius-button-TimeInput: createThemeVar("borderRadius-button-#{$componentName}");
26 | $hoverColor-button-TimeInput: createThemeVar("hoverColor-button-#{$componentName}");
27 | $disabledColor-button-TimeInput: createThemeVar("disabledColor-button-#{$componentName}");
28 | $outlineColor-button-TimeInput--focused: createThemeVar(
29 | "outlineColor-button-#{$componentName}--focused"
30 | );
31 | $outlineWidth-button-TimeInput--focused: createThemeVar(
32 | "outlineWidth-button-#{$componentName}--focused"
33 | );
34 | $outlineOffset-button-TimeInput--focused: createThemeVar(
35 | "outlineOffset-button-#{$componentName}--focused"
36 | );
37 | $minWidth-ampm-TimeInput: createThemeVar("minWidth-ampm-#{$componentName}");
38 | $fontSize-ampm-TimeInput: createThemeVar("fontSize-ampm-#{$componentName}");
39 |
40 | // New theme variables for better theming
41 | $opacity-TimeInput--disabled: createThemeVar("opacity-#{$componentName}--disabled");
42 | $backgroundColor-TimeInput--hover: createThemeVar("backgroundColor-#{$componentName}--hover");
43 | $margin-input-TimeInput: createThemeVar("margin-input-#{$componentName}");
44 | $transition-background-TimeInput: createThemeVar("transition-background-#{$componentName}");
45 | $backgroundColor-menu-TimeInput: createThemeVar("backgroundColor-menu-#{$componentName}");
46 | $borderRadius-menu-TimeInput: createThemeVar("borderRadius-menu-#{$componentName}");
47 | $boxShadow-menu-TimeInput: createThemeVar("boxShadow-menu-#{$componentName}");
48 | $borderColor-menu-TimeInput: createThemeVar("borderColor-menu-#{$componentName}");
49 | $backgroundColor-item-TimeInput--hover: createThemeVar(
50 | "backgroundColor-item-#{$componentName}--hover"
51 | );
52 | $backgroundColor-item-TimeInput--active: createThemeVar(
53 | "backgroundColor-item-#{$componentName}--active"
54 | );
55 | $maxHeight-menu-TimeInput: createThemeVar("maxHeight-menu-#{$componentName}");
56 | $padding-item-TimeInput: createThemeVar("padding-item-#{$componentName}");
57 | $opacity-item-TimeInput--disabled: createThemeVar("opacity-item-#{$componentName}--disabled");
58 | $margin-icon-TimeInput: createThemeVar("margin-icon-#{$componentName}");
59 | $outlineColor-ampm-TimeInput--focused: createThemeVar(
60 | "outlineColor-ampm-#{$componentName}--focused"
61 | );
62 | $outlineWidth-ampm-TimeInput--focused: createThemeVar(
63 | "outlineWidth-ampm-#{$componentName}--focused"
64 | );
65 | $outlineOffset-ampm-TimeInput--focused: createThemeVar(
66 | "outlineOffset-ampm-#{$componentName}--focused"
67 | );
68 |
69 | // Variables for default variant
70 | $borderRadius-TimeInput--default: createThemeVar("Input:borderRadius-#{$componentName}--default");
71 | $borderColor-TimeInput--default: createThemeVar("Input:borderColor-#{$componentName}--default");
72 | $borderWidth-TimeInput--default: createThemeVar("Input:borderWidth-#{$componentName}--default");
73 | $borderStyle-TimeInput--default: createThemeVar("Input:borderStyle-#{$componentName}--default");
74 | $fontSize-TimeInput--default: createThemeVar("Input:fontSize-#{$componentName}--default");
75 | $backgroundColor-TimeInput--default: createThemeVar("Input:backgroundColor-#{$componentName}--default");
76 | $boxShadow-TimeInput--default: createThemeVar("Input:boxShadow-#{$componentName}--default");
77 | $textColor-TimeInput--default: createThemeVar("Input:textColor-#{$componentName}--default");
78 | $borderColor-TimeInput--default--hover: createThemeVar("Input:borderColor-#{$componentName}--default--hover");
79 | $backgroundColor-TimeInput--default--hover: createThemeVar("Input:backgroundColor-#{$componentName}--default--hover");
80 | $boxShadow-TimeInput--default--hover: createThemeVar("Input:boxShadow-#{$componentName}--default--hover");
81 | $textColor-TimeInput--default--hover: createThemeVar("Input:textColor-#{$componentName}--default--hover");
82 | $borderColor-TimeInput--default--focus: createThemeVar("Input:borderColor-#{$componentName}--default--focus");
83 | $backgroundColor-TimeInput--default--focus: createThemeVar("Input:backgroundColor-#{$componentName}--default--focus");
84 | $boxShadow-TimeInput--default--focus: createThemeVar("Input:boxShadow-#{$componentName}--default--focus");
85 | $textColor-TimeInput--default--focus: createThemeVar("Input:textColor-#{$componentName}--default--focus");
86 | $outlineWidth-TimeInput--default--focus: createThemeVar("Input:outlineWidth-#{$componentName}--default--focus");
87 | $outlineColor-TimeInput--default--focus: createThemeVar("Input:outlineColor-#{$componentName}--default--focus");
88 | $outlineStyle-TimeInput--default--focus: createThemeVar("Input:outlineStyle-#{$componentName}--default--focus");
89 | $outlineOffset-TimeInput--default--focus: createThemeVar("Input:outlineOffset-#{$componentName}--default--focus");
90 | $color-adornment-TimeInput--default: createThemeVar("Input:color-adornment-#{$componentName}--default");
91 |
92 | // Variables for error variant
93 | $borderRadius-TimeInput--error: createThemeVar("Input:borderRadius-#{$componentName}--error");
94 | $borderColor-TimeInput--error: createThemeVar("Input:borderColor-#{$componentName}--error");
95 | $borderWidth-TimeInput--error: createThemeVar("Input:borderWidth-#{$componentName}--error");
96 | $borderStyle-TimeInput--error: createThemeVar("Input:borderStyle-#{$componentName}--error");
97 | $fontSize-TimeInput--error: createThemeVar("Input:fontSize-#{$componentName}--error");
98 | $backgroundColor-TimeInput--error: createThemeVar("Input:backgroundColor-#{$componentName}--error");
99 | $boxShadow-TimeInput--error: createThemeVar("Input:boxShadow-#{$componentName}--error");
100 | $textColor-TimeInput--error: createThemeVar("Input:textColor-#{$componentName}--error");
101 | $borderColor-TimeInput--error--hover: createThemeVar("Input:borderColor-#{$componentName}--error--hover");
102 | $backgroundColor-TimeInput--error--hover: createThemeVar("Input:backgroundColor-#{$componentName}--error--hover");
103 | $boxShadow-TimeInput--error--hover: createThemeVar("Input:boxShadow-#{$componentName}--error--hover");
104 | $textColor-TimeInput--error--hover: createThemeVar("Input:textColor-#{$componentName}--error--hover");
105 | $borderColor-TimeInput--error--focus: createThemeVar("Input:borderColor-#{$componentName}--error--focus");
106 | $backgroundColor-TimeInput--error--focus: createThemeVar("Input:backgroundColor-#{$componentName}--error--focus");
107 | $boxShadow-TimeInput--error--focus: createThemeVar("Input:boxShadow-#{$componentName}--error--focus");
108 | $textColor-TimeInput--error--focus: createThemeVar("Input:textColor-#{$componentName}--error--focus");
109 | $outlineWidth-TimeInput--error--focus: createThemeVar("Input:outlineWidth-#{$componentName}--error--focus");
110 | $outlineColor-TimeInput--error--focus: createThemeVar("Input:outlineColor-#{$componentName}--error--focus");
111 | $outlineStyle-TimeInput--error--focus: createThemeVar("Input:outlineStyle-#{$componentName}--error--focus");
112 | $outlineOffset-TimeInput--error--focus: createThemeVar("Input:outlineOffset-#{$componentName}--error--focus");
113 | $color-adornment-TimeInput--error: createThemeVar("Input:color-adornment-#{$componentName}--error");
114 |
115 | // Variables for warning variant
116 | $borderRadius-TimeInput--warning: createThemeVar("Input:borderRadius-#{$componentName}--warning");
117 | $borderColor-TimeInput--warning: createThemeVar("Input:borderColor-#{$componentName}--warning");
118 | $borderWidth-TimeInput--warning: createThemeVar("Input:borderWidth-#{$componentName}--warning");
119 | $borderStyle-TimeInput--warning: createThemeVar("Input:borderStyle-#{$componentName}--warning");
120 | $fontSize-TimeInput--warning: createThemeVar("Input:fontSize-#{$componentName}--warning");
121 | $backgroundColor-TimeInput--warning: createThemeVar("Input:backgroundColor-#{$componentName}--warning");
122 | $boxShadow-TimeInput--warning: createThemeVar("Input:boxShadow-#{$componentName}--warning");
123 | $textColor-TimeInput--warning: createThemeVar("Input:textColor-#{$componentName}--warning");
124 | $borderColor-TimeInput--warning--hover: createThemeVar("Input:borderColor-#{$componentName}--warning--hover");
125 | $backgroundColor-TimeInput--warning--hover: createThemeVar("Input:backgroundColor-#{$componentName}--warning--hover");
126 | $boxShadow-TimeInput--warning--hover: createThemeVar("Input:boxShadow-#{$componentName}--warning--hover");
127 | $textColor-TimeInput--warning--hover: createThemeVar("Input:textColor-#{$componentName}--warning--hover");
128 | $borderColor-TimeInput--warning--focus: createThemeVar("Input:borderColor-#{$componentName}--warning--focus");
129 | $backgroundColor-TimeInput--warning--focus: createThemeVar("Input:backgroundColor-#{$componentName}--warning--focus");
130 | $boxShadow-TimeInput--warning--focus: createThemeVar("Input:boxShadow-#{$componentName}--warning--focus");
131 | $textColor-TimeInput--warning--focus: createThemeVar("Input:textColor-#{$componentName}--warning--focus");
132 | $outlineWidth-TimeInput--warning--focus: createThemeVar("Input:outlineWidth-#{$componentName}--warning--focus");
133 | $outlineColor-TimeInput--warning--focus: createThemeVar("Input:outlineColor-#{$componentName}--warning--focus");
134 | $outlineStyle-TimeInput--warning--focus: createThemeVar("Input:outlineStyle-#{$componentName}--warning--focus");
135 | $outlineOffset-TimeInput--warning--focus: createThemeVar("Input:outlineOffset-#{$componentName}--warning--focus");
136 | $color-adornment-TimeInput--warning: createThemeVar("Input:color-adornment-#{$componentName}--warning");
137 |
138 | // Variables for success variant
139 | $borderRadius-TimeInput--success: createThemeVar("Input:borderRadius-#{$componentName}--success");
140 | $borderColor-TimeInput--success: createThemeVar("Input:borderColor-#{$componentName}--success");
141 | $borderWidth-TimeInput--success: createThemeVar("Input:borderWidth-#{$componentName}--success");
142 | $borderStyle-TimeInput--success: createThemeVar("Input:borderStyle-#{$componentName}--success");
143 | $fontSize-TimeInput--success: createThemeVar("Input:fontSize-#{$componentName}--success");
144 | $backgroundColor-TimeInput--success: createThemeVar("Input:backgroundColor-#{$componentName}--success");
145 | $boxShadow-TimeInput--success: createThemeVar("Input:boxShadow-#{$componentName}--success");
146 | $textColor-TimeInput--success: createThemeVar("Input:textColor-#{$componentName}--success");
147 | $borderColor-TimeInput--success--hover: createThemeVar("Input:borderColor-#{$componentName}--success--hover");
148 | $backgroundColor-TimeInput--success--hover: createThemeVar("Input:backgroundColor-#{$componentName}--success--hover");
149 | $boxShadow-TimeInput--success--hover: createThemeVar("Input:boxShadow-#{$componentName}--success--hover");
150 | $textColor-TimeInput--success--hover: createThemeVar("Input:textColor-#{$componentName}--success--hover");
151 | $borderColor-TimeInput--success--focus: createThemeVar("Input:borderColor-#{$componentName}--success--focus");
152 | $backgroundColor-TimeInput--success--focus: createThemeVar("Input:backgroundColor-#{$componentName}--success--focus");
153 | $boxShadow-TimeInput--success--focus: createThemeVar("Input:boxShadow-#{$componentName}--success--focus");
154 | $textColor-TimeInput--success--focus: createThemeVar("Input:textColor-#{$componentName}--success--focus");
155 | $outlineWidth-TimeInput--success--focus: createThemeVar("Input:outlineWidth-#{$componentName}--success--focus");
156 | $outlineColor-TimeInput--success--focus: createThemeVar("Input:outlineColor-#{$componentName}--success--focus");
157 | $outlineStyle-TimeInput--success--focus: createThemeVar("Input:outlineStyle-#{$componentName}--success--focus");
158 | $outlineOffset-TimeInput--success--focus: createThemeVar("Input:outlineOffset-#{$componentName}--success--focus");
159 | $color-adornment-TimeInput--success: createThemeVar("Input:color-adornment-#{$componentName}--success");
160 |
161 | // Variables for @layer section
162 | $gap-adornment-TimeInput: createThemeVar("Input:gap-adornment-#{$componentName}");
163 | $backgroundColor-TimeInput--disabled: createThemeVar("Input:backgroundColor-#{$componentName}--disabled");
164 | $textColor-TimeInput--disabled: createThemeVar("Input:textColor-#{$componentName}--disabled");
165 | $borderColor-TimeInput--disabled: createThemeVar("Input:borderColor-#{$componentName}--disabled");
166 |
167 | // --- CSS properties of a particular TimeInput variant
168 | @mixin variant($variantName) {
169 | border-radius: createThemeVar("Input:borderRadius-#{$componentName}--#{$variantName}");
170 | border-color: createThemeVar("Input:borderColor-#{$componentName}--#{$variantName}");
171 | border-width: createThemeVar("Input:borderWidth-#{$componentName}--#{$variantName}");
172 | border-style: createThemeVar("Input:borderStyle-#{$componentName}--#{$variantName}");
173 | background-color: createThemeVar("Input:backgroundColor-#{$componentName}--#{$variantName}");
174 | box-shadow: createThemeVar("Input:boxShadow-#{$componentName}--#{$variantName}");
175 | color: createThemeVar("Input:textColor-#{$componentName}--#{$variantName}");
176 | font-size: createThemeVar("Input:fontSize-#{$componentName}--#{$variantName}");
177 |
178 | &:hover {
179 | border-color: createThemeVar("Input:borderColor-#{$componentName}--#{$variantName}--hover");
180 | background-color: createThemeVar(
181 | "Input:backgroundColor-#{$componentName}--#{$variantName}--hover"
182 | );
183 | box-shadow: createThemeVar("Input:boxShadow-#{$componentName}--#{$variantName}--hover");
184 | color: createThemeVar("Input:textColor-#{$componentName}--#{$variantName}--hover");
185 | }
186 |
187 | &:focus-within {
188 | border-color: createThemeVar("Input:borderColor-#{$componentName}--#{$variantName}--focus");
189 | background-color: createThemeVar(
190 | "Input:backgroundColor-#{$componentName}--#{$variantName}--focus"
191 | );
192 | box-shadow: createThemeVar("Input:boxShadow-#{$componentName}--#{$variantName}--focus");
193 | color: createThemeVar("Input:textColor-#{$componentName}--#{$variantName}--focus");
194 | }
195 |
196 | &:has(.reactTimeInputWrapper input:focus-visible) {
197 | outline-width: createThemeVar("Input:outlineWidth-#{$componentName}--#{$variantName}--focus");
198 | outline-color: createThemeVar("Input:outlineColor-#{$componentName}--#{$variantName}--focus");
199 | outline-style: createThemeVar("Input:outlineStyle-#{$componentName}--#{$variantName}--focus");
200 | outline-offset: createThemeVar("Input:outlineOffset-#{$componentName}--#{$variantName}--focus");
201 | }
202 |
203 | .adornment {
204 | color: createThemeVar("Input:color-adornment-#{$componentName}--#{$variantName}");
205 | }
206 | }
207 |
208 | @layer components {
209 | .timeInputWrapper {
210 | display: flex;
211 | align-items: center;
212 | width: fit-content;
213 | border-style: solid;
214 | border-width: 1px;
215 | transition: $transition-background-TimeInput;
216 | overflow: hidden;
217 | gap: $gap-adornment-TimeInput;
218 | @include t.paddingVars($themeVars, $componentName);
219 |
220 | &.disabled {
221 | opacity: $opacity-TimeInput--disabled;
222 | pointer-events: none;
223 | }
224 |
225 | &.readOnly {
226 | .timeInputWrapper input {
227 | cursor: default;
228 | }
229 | }
230 |
231 | @include variant("default");
232 |
233 | &.error {
234 | @include variant("error");
235 | }
236 | &.warning {
237 | @include variant("warning");
238 | }
239 | &.valid {
240 | @include variant("success");
241 | }
242 |
243 | &:has(input:is(:disabled)) {
244 | cursor: not-allowed;
245 | background-color: $backgroundColor-TimeInput--disabled;
246 | color: $textColor-TimeInput--disabled;
247 | border-color: $borderColor-TimeInput--disabled;
248 | }
249 |
250 | button {
251 | background: transparent;
252 | border: none;
253 | cursor: pointer;
254 | color: inherit;
255 | padding: $padding-button-TimeInput;
256 | border-radius: $borderRadius-TimeInput--default;
257 |
258 | &:hover {
259 | background-color: $backgroundColor-TimeInput--hover;
260 | }
261 |
262 | &:focus {
263 | outline: 2px solid $outlineColor-TimeInput--default--focus;
264 | outline-offset: 2px;
265 | }
266 |
267 | &:disabled {
268 | cursor: not-allowed;
269 | opacity: $opacity-TimeInput--disabled;
270 | }
271 | }
272 | }
273 |
274 | // TimeInput internal components styling (moved from TimeInput.css)
275 | .wrapper {
276 | display: flex;
277 | flex-grow: 1;
278 | flex-shrink: 0;
279 | align-items: center; // Ensure all child elements are vertically centered
280 | }
281 |
282 | .inputGroup {
283 | display: flex;
284 | height: 80%;
285 | align-items: center;
286 | flex-grow: 1;
287 | min-width: 0; // Allow shrinking
288 | gap: 0; // Remove gap since dividers handle spacing
289 | box-sizing: border-box;
290 | }
291 |
292 | .input {
293 | min-width: $minWidth-input-TimeInput;
294 | position: relative;
295 | border: 0;
296 | background: none;
297 | color: currentColor;
298 | font: inherit;
299 | font-size: inherit;
300 | box-sizing: content-box;
301 | border-radius: $borderRadius-input-TimeInput;
302 | -webkit-appearance: textfield;
303 | -moz-appearance: textfield;
304 | appearance: textfield;
305 |
306 | // Specific widths for different input types
307 | &.hour,
308 | &.hour12,
309 | &.hour24,
310 | &.minute,
311 | &.second {
312 | // Use character-based width for better font-size independence
313 | width: 2.5ch;
314 | min-width: 2.5ch;
315 | max-width: 2.5ch;
316 | text-align: center;
317 | box-sizing: border-box !important;
318 | }
319 |
320 | // Hide spin buttons for number inputs
321 | &::-webkit-outer-spin-button,
322 | &::-webkit-inner-spin-button {
323 | -webkit-appearance: none !important;
324 | -moz-appearance: none !important;
325 | appearance: none !important;
326 | margin: 0 !important;
327 | display: none !important;
328 | }
329 |
330 | // Hide spin buttons for Firefox
331 | &[type="number"] {
332 | -moz-appearance: textfield !important;
333 | appearance: textfield !important;
334 | }
335 |
336 | &:invalid {
337 | background: $backgroundColor-input-TimeInput-invalid;
338 | }
339 |
340 | &.invalid {
341 | background-color: $backgroundColor-input-TimeInput-invalid;
342 | }
343 |
344 | &.hasLeadingZero {
345 | // Use character-based width for more predictable sizing with digits
346 | min-width: 2.5ch; // Enough space for 2 digits + padding
347 | text-align: center;
348 | }
349 | }
350 |
351 | .amPm {
352 | min-width: $minWidth-ampm-TimeInput !important;
353 | width: 4ch !important; // Fixed width to accommodate both AM and PM
354 | height: 100% !important;
355 | font: inherit;
356 | font-size: $fontSize-ampm-TimeInput;
357 | display: inline-flex !important;
358 | align-items: center;
359 | justify-content: space-between;
360 | border: 0 !important;
361 | border-radius: $borderRadius-input-TimeInput;
362 | background: none !important;
363 | cursor: pointer;
364 | box-sizing: border-box;
365 | color: currentColor;
366 | position: relative;
367 | outline-offset: -2px;
368 |
369 | &:hover {
370 | background-color: $backgroundColor-TimeInput--hover !important;
371 | }
372 |
373 | &:focus {
374 | outline: $outlineWidth-ampm-TimeInput--focused solid $outlineColor-ampm-TimeInput--focused;
375 | outline-offset: $outlineOffset-ampm-TimeInput--focused;
376 | }
377 |
378 | &:disabled,
379 | &[data-disabled] {
380 | cursor: not-allowed;
381 | opacity: $opacity-TimeInput--disabled;
382 | pointer-events: none;
383 | }
384 | }
385 |
386 | // New button-based AM/PM selector
387 | .amPmButton {
388 | min-width: $minWidth-ampm-TimeInput !important;
389 | font: inherit;
390 | font-size: $fontSize-ampm-TimeInput;
391 | display: inline-flex !important;
392 | align-items: center;
393 | justify-content: center;
394 | border: 0 !important;
395 | border-radius: $borderRadius-input-TimeInput;
396 | background: none !important;
397 | cursor: pointer;
398 | box-sizing: border-box;
399 | color: currentColor;
400 | position: relative;
401 |
402 | &:hover {
403 | background-color: $backgroundColor-TimeInput--hover !important;
404 | }
405 |
406 | &:focus {
407 | outline: $outlineWidth-ampm-TimeInput--focused solid $outlineColor-ampm-TimeInput--focused;
408 | outline-offset: $outlineOffset-ampm-TimeInput--focused;
409 | }
410 |
411 | &:disabled {
412 | cursor: not-allowed;
413 | opacity: $opacity-TimeInput--disabled;
414 | pointer-events: none;
415 | }
416 | }
417 |
418 | .amPmValue {
419 | display: flex;
420 | min-width: 0;
421 | }
422 |
423 | .amPmIcon {
424 | margin-left: $margin-icon-TimeInput;
425 | flex-shrink: 0;
426 | width: 1em; // Scale with font size
427 | height: 1em; // Scale with font size
428 | }
429 |
430 | .amPmContent {
431 | position: relative;
432 | width: 100%;
433 | max-height: $maxHeight-menu-TimeInput;
434 | z-index: 1000;
435 | overflow: auto;
436 | background-color: $backgroundColor-menu-TimeInput;
437 | border-radius: $borderRadius-menu-TimeInput;
438 | box-shadow: $boxShadow-menu-TimeInput;
439 | border: 1px solid $borderColor-menu-TimeInput;
440 | min-width: var(--radix-select-trigger-width);
441 | max-width: var(--radix-select-content-available-width);
442 | }
443 |
444 | .amPmItem {
445 | position: relative;
446 | display: flex;
447 | cursor: pointer;
448 | user-select: none;
449 | align-items: center;
450 | outline: none;
451 | padding: $padding-item-TimeInput;
452 |
453 | &[data-highlighted] {
454 | background-color: $backgroundColor-item-TimeInput--hover;
455 | }
456 |
457 | &[data-state="checked"] {
458 | background-color: $backgroundColor-item-TimeInput--active;
459 | }
460 |
461 | &[data-disabled] {
462 | pointer-events: none;
463 | opacity: $opacity-item-TimeInput--disabled;
464 | cursor: not-allowed;
465 | font-style: italic;
466 | }
467 | }
468 |
469 | .button {
470 | border: 0;
471 | height: 1.5em;
472 | background: transparent;
473 | padding: $padding-button-TimeInput;
474 | border-radius: $borderRadius-button-TimeInput;
475 |
476 | &:enabled {
477 | cursor: pointer;
478 |
479 | &:hover .buttonIcon,
480 | &:focus .buttonIcon {
481 | stroke: $hoverColor-button-TimeInput;
482 | }
483 | }
484 |
485 | &:disabled .buttonIcon {
486 | stroke: $disabledColor-button-TimeInput;
487 | }
488 |
489 | &:focus {
490 | outline: $outlineWidth-button-TimeInput--focused solid $outlineColor-button-TimeInput--focused;
491 | outline-offset: $outlineOffset-button-TimeInput--focused;
492 | }
493 |
494 | svg {
495 | display: inherit;
496 | }
497 | }
498 |
499 | .clearButton {
500 | @extend .button;
501 | display: flex;
502 | align-items: center;
503 | justify-content: center;
504 | }
505 |
506 | .buttonIcon {
507 | // Base styling for button icons
508 | display: inherit;
509 | width: 1em; // Scale with font size
510 | height: 1em; // Scale with font size
511 | }
512 |
513 | .clearButtonIcon {
514 | @extend .buttonIcon;
515 | }
516 | }
517 |
518 | // --- We export the theme variables to add them to the component renderer
519 | :export {
520 | themeVars: t.json-stringify($themeVars);
521 | }
522 |
```