This is page 34 of 140. Use http://codebase.md/xmlui-org/xmlui/tools/vscode/resources/xmlui-markup-syntax-highlighting.png?lines=false&page={x} to view the full context.
# Directory Structure
```
├── .changeset
│ ├── config.json
│ ├── silver-llamas-cough.md
│ └── true-jeans-agree.md
├── .eslintrc.cjs
├── .github
│ ├── build-checklist.png
│ ├── ISSUE_TEMPLATE
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ └── workflows
│ ├── deploy-blog.yml
│ ├── deploy-docs-optimized.yml
│ ├── deploy-docs.yml
│ ├── prepare-versions.yml
│ ├── release-packages.yml
│ ├── run-all-tests.yml
│ └── run-smoke-tests.yml
├── .gitignore
├── .prettierrc.js
├── .vscode
│ ├── launch.json
│ └── settings.json
├── blog
│ ├── .gitignore
│ ├── .gitkeep
│ ├── CHANGELOG.md
│ ├── extensions.ts
│ ├── index.html
│ ├── index.ts
│ ├── package.json
│ ├── public
│ │ ├── blog
│ │ │ ├── images
│ │ │ │ ├── an-advanced-codefence.mp4
│ │ │ │ ├── blog-page-component.png
│ │ │ │ ├── blog-scrabble.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
│ │ └── web.config
│ ├── scripts
│ │ ├── download-latest-xmlui.js
│ │ ├── generate-rss.js
│ │ ├── get-releases.js
│ │ └── utils.js
│ ├── src
│ │ ├── components
│ │ │ ├── BlogOverview.xmlui
│ │ │ ├── BlogPage.xmlui
│ │ │ └── PageNotFound.xmlui
│ │ ├── config.ts
│ │ ├── Main.xmlui
│ │ └── themes
│ │ └── blog-theme.ts
│ └── tsconfig.json
├── CONTRIBUTING.md
├── docs
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── ComponentRefLinks.txt
│ ├── content
│ │ ├── _meta.json
│ │ ├── components
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── APICall.md
│ │ │ ├── App.md
│ │ │ ├── AppHeader.md
│ │ │ ├── AppState.md
│ │ │ ├── AutoComplete.md
│ │ │ ├── Avatar.md
│ │ │ ├── Backdrop.md
│ │ │ ├── Badge.md
│ │ │ ├── BarChart.md
│ │ │ ├── Bookmark.md
│ │ │ ├── Breakout.md
│ │ │ ├── Button.md
│ │ │ ├── Card.md
│ │ │ ├── Carousel.md
│ │ │ ├── ChangeListener.md
│ │ │ ├── Checkbox.md
│ │ │ ├── CHStack.md
│ │ │ ├── ColorPicker.md
│ │ │ ├── Column.md
│ │ │ ├── ContentSeparator.md
│ │ │ ├── CVStack.md
│ │ │ ├── DataSource.md
│ │ │ ├── DateInput.md
│ │ │ ├── DatePicker.md
│ │ │ ├── DonutChart.md
│ │ │ ├── DropdownMenu.md
│ │ │ ├── EmojiSelector.md
│ │ │ ├── ExpandableItem.md
│ │ │ ├── FileInput.md
│ │ │ ├── FileUploadDropZone.md
│ │ │ ├── FlowLayout.md
│ │ │ ├── Footer.md
│ │ │ ├── Form.md
│ │ │ ├── FormItem.md
│ │ │ ├── FormSection.md
│ │ │ ├── Fragment.md
│ │ │ ├── H1.md
│ │ │ ├── H2.md
│ │ │ ├── H3.md
│ │ │ ├── H4.md
│ │ │ ├── H5.md
│ │ │ ├── H6.md
│ │ │ ├── Heading.md
│ │ │ ├── HSplitter.md
│ │ │ ├── HStack.md
│ │ │ ├── Icon.md
│ │ │ ├── IFrame.md
│ │ │ ├── Image.md
│ │ │ ├── Items.md
│ │ │ ├── LabelList.md
│ │ │ ├── Legend.md
│ │ │ ├── LineChart.md
│ │ │ ├── Link.md
│ │ │ ├── List.md
│ │ │ ├── Logo.md
│ │ │ ├── Markdown.md
│ │ │ ├── MenuItem.md
│ │ │ ├── MenuSeparator.md
│ │ │ ├── ModalDialog.md
│ │ │ ├── NavGroup.md
│ │ │ ├── NavLink.md
│ │ │ ├── NavPanel.md
│ │ │ ├── NoResult.md
│ │ │ ├── NumberBox.md
│ │ │ ├── Option.md
│ │ │ ├── Page.md
│ │ │ ├── PageMetaTitle.md
│ │ │ ├── Pages.md
│ │ │ ├── Pagination.md
│ │ │ ├── PasswordInput.md
│ │ │ ├── PieChart.md
│ │ │ ├── ProgressBar.md
│ │ │ ├── Queue.md
│ │ │ ├── RadioGroup.md
│ │ │ ├── RealTimeAdapter.md
│ │ │ ├── Redirect.md
│ │ │ ├── Select.md
│ │ │ ├── Slider.md
│ │ │ ├── Slot.md
│ │ │ ├── SpaceFiller.md
│ │ │ ├── Spinner.md
│ │ │ ├── Splitter.md
│ │ │ ├── Stack.md
│ │ │ ├── StickyBox.md
│ │ │ ├── SubMenuItem.md
│ │ │ ├── Switch.md
│ │ │ ├── TabItem.md
│ │ │ ├── Table.md
│ │ │ ├── TableOfContents.md
│ │ │ ├── Tabs.md
│ │ │ ├── Text.md
│ │ │ ├── TextArea.md
│ │ │ ├── TextBox.md
│ │ │ ├── Theme.md
│ │ │ ├── TimeInput.md
│ │ │ ├── Timer.md
│ │ │ ├── ToneChangerButton.md
│ │ │ ├── ToneSwitch.md
│ │ │ ├── Tooltip.md
│ │ │ ├── Tree.md
│ │ │ ├── VSplitter.md
│ │ │ ├── VStack.md
│ │ │ ├── xmlui-animations
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── Animation.md
│ │ │ │ ├── FadeAnimation.md
│ │ │ │ ├── FadeInAnimation.md
│ │ │ │ ├── FadeOutAnimation.md
│ │ │ │ ├── ScaleAnimation.md
│ │ │ │ └── SlideInAnimation.md
│ │ │ ├── xmlui-pdf
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Pdf.md
│ │ │ ├── xmlui-spreadsheet
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Spreadsheet.md
│ │ │ └── xmlui-website-blocks
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── Carousel.md
│ │ │ ├── HelloMd.md
│ │ │ ├── HeroSection.md
│ │ │ └── ScrollToTop.md
│ │ └── extensions
│ │ ├── _meta.json
│ │ ├── xmlui-animations
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── Animation.md
│ │ │ ├── FadeAnimation.md
│ │ │ ├── FadeInAnimation.md
│ │ │ ├── FadeOutAnimation.md
│ │ │ ├── ScaleAnimation.md
│ │ │ └── SlideInAnimation.md
│ │ └── xmlui-website-blocks
│ │ ├── _meta.json
│ │ ├── _overview.md
│ │ ├── Carousel.md
│ │ ├── HelloMd.md
│ │ ├── HeroSection.md
│ │ └── ScrollToTop.md
│ ├── extensions.ts
│ ├── index.html
│ ├── index.ts
│ ├── package.json
│ ├── public
│ │ ├── feed.rss
│ │ ├── mockServiceWorker.js
│ │ ├── pages
│ │ │ ├── _meta.json
│ │ │ ├── app-structure.md
│ │ │ ├── build-editor-component.md
│ │ │ ├── build-hello-world-component.md
│ │ │ ├── components-intro.md
│ │ │ ├── context-variables.md
│ │ │ ├── forms.md
│ │ │ ├── globals.md
│ │ │ ├── glossary.md
│ │ │ ├── helper-tags.md
│ │ │ ├── hosted-deployment.md
│ │ │ ├── howto
│ │ │ │ ├── assign-a-complex-json-literal-to-a-component-variable.md
│ │ │ │ ├── chain-a-refetch.md
│ │ │ │ ├── control-cache-invalidation.md
│ │ │ │ ├── debug-a-component.md
│ │ │ │ ├── delay-a-datasource-until-another-datasource-is-ready.md
│ │ │ │ ├── delegate-a-method.md
│ │ │ │ ├── do-custom-form-validation.md
│ │ │ │ ├── expose-a-method-from-a-component.md
│ │ │ │ ├── filter-and-transform-data-from-an-api.md
│ │ │ │ ├── group-items-in-list-by-a-property.md
│ │ │ │ ├── handle-background-operations.md
│ │ │ │ ├── hide-an-element-until-its-datasource-is-ready.md
│ │ │ │ ├── make-a-set-of-equal-width-cards.md
│ │ │ │ ├── make-a-table-responsive.md
│ │ │ │ ├── make-navpanel-width-responsive.md
│ │ │ │ ├── modify-a-value-reported-in-a-column.md
│ │ │ │ ├── paginate-a-list.md
│ │ │ │ ├── pass-data-to-a-modal-dialog.md
│ │ │ │ ├── react-to-button-click-not-keystrokes.md
│ │ │ │ ├── set-the-initial-value-of-a-select-from-fetched-data.md
│ │ │ │ ├── share-a-modaldialog-across-components.md
│ │ │ │ ├── sync-selections-between-table-and-list-views.md
│ │ │ │ ├── update-ui-optimistically.md
│ │ │ │ ├── use-built-in-form-validation.md
│ │ │ │ └── use-the-same-modaldialog-to-add-or-edit.md
│ │ │ ├── howto.md
│ │ │ ├── intro.md
│ │ │ ├── layout.md
│ │ │ ├── markup.md
│ │ │ ├── mcp.md
│ │ │ ├── modal-dialogs.md
│ │ │ ├── news-and-reviews.md
│ │ │ ├── reactive-intro.md
│ │ │ ├── refactoring.md
│ │ │ ├── routing-and-links.md
│ │ │ ├── samples
│ │ │ │ ├── color-palette.xmlui
│ │ │ │ ├── color-values.xmlui
│ │ │ │ ├── shadow-sizes.xmlui
│ │ │ │ ├── spacing-sizes.xmlui
│ │ │ │ ├── swatch.xmlui
│ │ │ │ ├── theme-gallery-brief.xmlui
│ │ │ │ └── theme-gallery.xmlui
│ │ │ ├── scoping.md
│ │ │ ├── scripting.md
│ │ │ ├── styles-and-themes
│ │ │ │ ├── common-units.md
│ │ │ │ ├── layout-props.md
│ │ │ │ ├── theme-variable-defaults.md
│ │ │ │ ├── theme-variables.md
│ │ │ │ └── themes.md
│ │ │ ├── template-properties.md
│ │ │ ├── test.md
│ │ │ ├── tutorial-01.md
│ │ │ ├── tutorial-02.md
│ │ │ ├── tutorial-03.md
│ │ │ ├── tutorial-04.md
│ │ │ ├── tutorial-05.md
│ │ │ ├── tutorial-06.md
│ │ │ ├── tutorial-07.md
│ │ │ ├── tutorial-08.md
│ │ │ ├── tutorial-09.md
│ │ │ ├── tutorial-10.md
│ │ │ ├── tutorial-11.md
│ │ │ ├── tutorial-12.md
│ │ │ ├── universal-properties.md
│ │ │ ├── user-defined-components.md
│ │ │ ├── vscode.md
│ │ │ ├── working-with-markdown.md
│ │ │ ├── working-with-text.md
│ │ │ ├── xmlui-animations
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── Animation.md
│ │ │ │ ├── FadeAnimation.md
│ │ │ │ ├── FadeInAnimation.md
│ │ │ │ ├── FadeOutAnimation.md
│ │ │ │ ├── ScaleAnimation.md
│ │ │ │ └── SlideInAnimation.md
│ │ │ ├── xmlui-charts
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── BarChart.md
│ │ │ │ ├── DonutChart.md
│ │ │ │ ├── LabelList.md
│ │ │ │ ├── Legend.md
│ │ │ │ ├── LineChart.md
│ │ │ │ └── PieChart.md
│ │ │ ├── xmlui-pdf
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Pdf.md
│ │ │ └── xmlui-spreadsheet
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ └── Spreadsheet.md
│ │ ├── resources
│ │ │ ├── devdocs
│ │ │ │ ├── debug-proxy-object-2.png
│ │ │ │ ├── debug-proxy-object.png
│ │ │ │ ├── table_editor_01.png
│ │ │ │ ├── table_editor_02.png
│ │ │ │ ├── table_editor_03.png
│ │ │ │ ├── table_editor_04.png
│ │ │ │ ├── table_editor_05.png
│ │ │ │ ├── table_editor_06.png
│ │ │ │ ├── table_editor_07.png
│ │ │ │ ├── table_editor_08.png
│ │ │ │ ├── table_editor_09.png
│ │ │ │ ├── table_editor_10.png
│ │ │ │ ├── table_editor_11.png
│ │ │ │ ├── table-editor-01.png
│ │ │ │ ├── table-editor-02.png
│ │ │ │ ├── table-editor-03.png
│ │ │ │ ├── table-editor-04.png
│ │ │ │ ├── table-editor-06.png
│ │ │ │ ├── table-editor-07.png
│ │ │ │ ├── table-editor-08.png
│ │ │ │ ├── table-editor-09.png
│ │ │ │ └── xmlui-rendering-of-tiptap-markdown.png
│ │ │ ├── favicon.ico
│ │ │ ├── files
│ │ │ │ ├── clients.json
│ │ │ │ ├── daily-revenue.json
│ │ │ │ ├── dashboard-stats.json
│ │ │ │ ├── demo.xmlui
│ │ │ │ ├── demo.xmlui.xs
│ │ │ │ ├── downloads
│ │ │ │ │ └── downloads.json
│ │ │ │ ├── for-download
│ │ │ │ │ ├── index-with-api.html
│ │ │ │ │ ├── index.html
│ │ │ │ │ ├── mockApi.js
│ │ │ │ │ ├── start-darwin.sh
│ │ │ │ │ ├── start-linux.sh
│ │ │ │ │ ├── start.bat
│ │ │ │ │ └── xmlui
│ │ │ │ │ └── xmlui-standalone.umd.js
│ │ │ │ ├── getting-started
│ │ │ │ │ ├── cl-tutorial-final.zip
│ │ │ │ │ ├── cl-tutorial.zip
│ │ │ │ │ ├── cl-tutorial2.zip
│ │ │ │ │ ├── cl-tutorial3.zip
│ │ │ │ │ ├── cl-tutorial4.zip
│ │ │ │ │ ├── cl-tutorial5.zip
│ │ │ │ │ ├── cl-tutorial6.zip
│ │ │ │ │ ├── getting-started.zip
│ │ │ │ │ ├── hello-xmlui.zip
│ │ │ │ │ ├── xmlui-empty.zip
│ │ │ │ │ └── xmlui-starter.zip
│ │ │ │ ├── howto
│ │ │ │ │ └── component-icons
│ │ │ │ │ └── up-arrow.svg
│ │ │ │ ├── invoices.json
│ │ │ │ ├── monthly-status.json
│ │ │ │ ├── news-and-reviews.json
│ │ │ │ ├── products.json
│ │ │ │ ├── releases.json
│ │ │ │ ├── tutorials
│ │ │ │ │ ├── datasource
│ │ │ │ │ │ └── api.ts
│ │ │ │ │ └── p2do
│ │ │ │ │ ├── api.ts
│ │ │ │ │ └── todo-logo.svg
│ │ │ │ └── xmlui.json
│ │ │ ├── github.svg
│ │ │ ├── images
│ │ │ │ ├── apiaction-tutorial
│ │ │ │ │ ├── add-success.png
│ │ │ │ │ ├── apiaction-param.png
│ │ │ │ │ ├── change-completed.png
│ │ │ │ │ ├── change-in-progress.png
│ │ │ │ │ ├── confirm-delete.png
│ │ │ │ │ ├── data-error.png
│ │ │ │ │ ├── data-progress.png
│ │ │ │ │ ├── data-success.png
│ │ │ │ │ ├── display-1.png
│ │ │ │ │ ├── item-deleted.png
│ │ │ │ │ ├── item-updated.png
│ │ │ │ │ ├── missing-api-key.png
│ │ │ │ │ ├── new-item-added.png
│ │ │ │ │ └── test-message.png
│ │ │ │ ├── chat-api
│ │ │ │ │ └── domain-model.svg
│ │ │ │ ├── components
│ │ │ │ │ ├── image
│ │ │ │ │ │ └── breakfast.jpg
│ │ │ │ │ ├── markdown
│ │ │ │ │ │ └── colors.png
│ │ │ │ │ └── modal
│ │ │ │ │ ├── deep_link_dialog_1.jpg
│ │ │ │ │ └── deep_link_dialog_2.jpg
│ │ │ │ ├── create-apps
│ │ │ │ │ ├── collapsed-vertical.png
│ │ │ │ │ ├── using-forms-warning-dialog.png
│ │ │ │ │ └── using-forms.png
│ │ │ │ ├── datasource-tutorial
│ │ │ │ │ ├── data-with-header.png
│ │ │ │ │ ├── filtered-data.png
│ │ │ │ │ ├── filtered-items.png
│ │ │ │ │ ├── initial-page-items.png
│ │ │ │ │ ├── list-items.png
│ │ │ │ │ ├── next-page-items.png
│ │ │ │ │ ├── no-data.png
│ │ │ │ │ ├── pagination-1.jpg
│ │ │ │ │ ├── pagination-1.png
│ │ │ │ │ ├── polling-1.png
│ │ │ │ │ ├── refetch-data.png
│ │ │ │ │ ├── slow-loading.png
│ │ │ │ │ ├── test-message.png
│ │ │ │ │ ├── Thumbs.db
│ │ │ │ │ ├── unconventional-data.png
│ │ │ │ │ └── unfiltered-items.png
│ │ │ │ ├── flower.jpg
│ │ │ │ ├── get-started
│ │ │ │ │ ├── add-new-contact.png
│ │ │ │ │ ├── app-modified.png
│ │ │ │ │ ├── app-start.png
│ │ │ │ │ ├── app-with-boxes.png
│ │ │ │ │ ├── app-with-toast.png
│ │ │ │ │ ├── boilerplate-structure.png
│ │ │ │ │ ├── cl-initial.png
│ │ │ │ │ ├── cl-start.png
│ │ │ │ │ ├── contact-counts.png
│ │ │ │ │ ├── contact-dialog-title.png
│ │ │ │ │ ├── contact-dialog.png
│ │ │ │ │ ├── contact-menus.png
│ │ │ │ │ ├── contact-predicates.png
│ │ │ │ │ ├── context-menu.png
│ │ │ │ │ ├── dashboard-numbers.png
│ │ │ │ │ ├── default-contact-list.png
│ │ │ │ │ ├── delete-contact.png
│ │ │ │ │ ├── delete-task.png
│ │ │ │ │ ├── detailed-template.png
│ │ │ │ │ ├── edit-contact-details.png
│ │ │ │ │ ├── edited-contact-saved.png
│ │ │ │ │ ├── empty-sections.png
│ │ │ │ │ ├── filter-completed.png
│ │ │ │ │ ├── fullwidth-desktop.png
│ │ │ │ │ ├── fullwidth-mobile.png
│ │ │ │ │ ├── initial-table.png
│ │ │ │ │ ├── items-and-badges.png
│ │ │ │ │ ├── loading-message.png
│ │ │ │ │ ├── new-contact-button.png
│ │ │ │ │ ├── new-contact-saved.png
│ │ │ │ │ ├── no-empty-sections.png
│ │ │ │ │ ├── personal-todo-initial.png
│ │ │ │ │ ├── piechart.png
│ │ │ │ │ ├── review-today.png
│ │ │ │ │ ├── rudimentary-dashboard.png
│ │ │ │ │ ├── section-collapsed.png
│ │ │ │ │ ├── sectioned-items.png
│ │ │ │ │ ├── sections-ordered.png
│ │ │ │ │ ├── spacex-list-with-links.png
│ │ │ │ │ ├── spacex-list.png
│ │ │ │ │ ├── start-personal-todo-1.png
│ │ │ │ │ ├── submit-new-contact.png
│ │ │ │ │ ├── submit-new-task.png
│ │ │ │ │ ├── syntax-highlighting.png
│ │ │ │ │ ├── table-with-badge.png
│ │ │ │ │ ├── template-with-card.png
│ │ │ │ │ ├── test-emulated-api.png
│ │ │ │ │ ├── Thumbs.db
│ │ │ │ │ ├── todo-logo.png
│ │ │ │ │ └── xmlui-tools.png
│ │ │ │ ├── HelloApp.png
│ │ │ │ ├── HelloApp2.png
│ │ │ │ ├── logos
│ │ │ │ │ ├── xmlui1.svg
│ │ │ │ │ ├── xmlui2.svg
│ │ │ │ │ ├── xmlui3.svg
│ │ │ │ │ ├── xmlui4.svg
│ │ │ │ │ ├── xmlui5.svg
│ │ │ │ │ ├── xmlui6.svg
│ │ │ │ │ └── xmlui7.svg
│ │ │ │ ├── pdf
│ │ │ │ │ └── dummy-pdf.jpg
│ │ │ │ ├── rendering-engine
│ │ │ │ │ ├── AppEngine-flow.svg
│ │ │ │ │ ├── Component.svg
│ │ │ │ │ ├── CompoundComponent.svg
│ │ │ │ │ ├── RootComponent.svg
│ │ │ │ │ └── tree-with-containers.svg
│ │ │ │ ├── reviewers-guide
│ │ │ │ │ ├── AppEngine-flow.svg
│ │ │ │ │ └── incbutton-in-action.png
│ │ │ │ ├── tools
│ │ │ │ │ └── boilerplate-structure.png
│ │ │ │ ├── try.svg
│ │ │ │ ├── tutorial
│ │ │ │ │ ├── app-chat-history.png
│ │ │ │ │ ├── app-content-placeholder.png
│ │ │ │ │ ├── app-header-and-content.png
│ │ │ │ │ ├── app-links-channel-selected.png
│ │ │ │ │ ├── app-links-click.png
│ │ │ │ │ ├── app-navigation.png
│ │ │ │ │ ├── finished-ex01.png
│ │ │ │ │ ├── finished-ex02.png
│ │ │ │ │ ├── hello.png
│ │ │ │ │ ├── splash-screen-advanced.png
│ │ │ │ │ ├── splash-screen-after-click.png
│ │ │ │ │ ├── splash-screen-centered.png
│ │ │ │ │ ├── splash-screen-events.png
│ │ │ │ │ ├── splash-screen-expression.png
│ │ │ │ │ ├── splash-screen-reuse-after.png
│ │ │ │ │ ├── splash-screen-reuse-before.png
│ │ │ │ │ └── splash-screen.png
│ │ │ │ └── tutorial-01.png
│ │ │ ├── llms.txt
│ │ │ ├── logo-dark.svg
│ │ │ ├── logo.svg
│ │ │ ├── pg-popout.svg
│ │ │ └── xmlui-logo.svg
│ │ ├── serve.json
│ │ └── web.config
│ ├── scripts
│ │ ├── download-latest-xmlui.js
│ │ ├── generate-rss.js
│ │ ├── get-releases.js
│ │ └── utils.js
│ ├── src
│ │ ├── components
│ │ │ ├── BlogOverview.xmlui
│ │ │ ├── BlogPage.xmlui
│ │ │ ├── Boxes.xmlui
│ │ │ ├── Breadcrumb.xmlui
│ │ │ ├── ChangeLog.xmlui
│ │ │ ├── ColorPalette.xmlui
│ │ │ ├── DocumentLinks.xmlui
│ │ │ ├── DocumentPage.xmlui
│ │ │ ├── DocumentPageNoTOC.xmlui
│ │ │ ├── Icons.xmlui
│ │ │ ├── IncButton.xmlui
│ │ │ ├── IncButton2.xmlui
│ │ │ ├── NameValue.xmlui
│ │ │ ├── PageNotFound.xmlui
│ │ │ ├── PaletteItem.xmlui
│ │ │ ├── Palettes.xmlui
│ │ │ ├── SectionHeader.xmlui
│ │ │ ├── TBD.xmlui
│ │ │ ├── Test.xmlui
│ │ │ ├── ThemesIntro.xmlui
│ │ │ ├── ThousandThemes.xmlui
│ │ │ ├── TubeStops.xmlui
│ │ │ ├── TubeStops.xmlui.xs
│ │ │ └── TwoColumnCode.xmlui
│ │ ├── config.ts
│ │ ├── Main.xmlui
│ │ └── themes
│ │ ├── docs-theme.ts
│ │ ├── earthtone.ts
│ │ ├── xmlui-gray-on-default.ts
│ │ ├── xmlui-green-on-default.ts
│ │ └── xmlui-orange-on-default.ts
│ └── tsconfig.json
├── LICENSE
├── package-lock.json
├── package.json
├── packages
│ ├── tsconfig.json
│ ├── xmlui-animations
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── Animation.tsx
│ │ ├── AnimationNative.tsx
│ │ ├── FadeAnimation.tsx
│ │ ├── FadeInAnimation.tsx
│ │ ├── FadeOutAnimation.tsx
│ │ ├── index.tsx
│ │ ├── ScaleAnimation.tsx
│ │ └── SlideInAnimation.tsx
│ ├── xmlui-devtools
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ ├── src
│ │ │ ├── devtools
│ │ │ │ ├── DevTools.tsx
│ │ │ │ ├── DevToolsNative.module.scss
│ │ │ │ ├── DevToolsNative.tsx
│ │ │ │ ├── ModalDialog.module.scss
│ │ │ │ ├── ModalDialog.tsx
│ │ │ │ ├── ModalVisibilityContext.tsx
│ │ │ │ ├── Tooltip.module.scss
│ │ │ │ ├── Tooltip.tsx
│ │ │ │ └── utils.ts
│ │ │ ├── editor
│ │ │ │ └── Editor.tsx
│ │ │ └── index.tsx
│ │ └── vite.config-overrides.ts
│ ├── xmlui-hello-world
│ │ ├── .gitignore
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── HelloWorld.module.scss
│ │ ├── HelloWorld.tsx
│ │ ├── HelloWorldNative.tsx
│ │ └── index.tsx
│ ├── xmlui-os-frames
│ │ ├── .gitignore
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── index.tsx
│ │ ├── IPhoneFrame.module.scss
│ │ ├── IPhoneFrame.tsx
│ │ ├── MacOSAppFrame.module.scss
│ │ ├── MacOSAppFrame.tsx
│ │ ├── WindowsAppFrame.module.scss
│ │ └── WindowsAppFrame.tsx
│ ├── xmlui-pdf
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ ├── components
│ │ │ │ └── Pdf.xmlui
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── index.tsx
│ │ ├── LazyPdfNative.tsx
│ │ ├── Pdf.module.scss
│ │ └── Pdf.tsx
│ ├── xmlui-playground
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── hooks
│ │ │ ├── usePlayground.ts
│ │ │ └── useToast.ts
│ │ ├── index.tsx
│ │ ├── playground
│ │ │ ├── Box.module.scss
│ │ │ ├── Box.tsx
│ │ │ ├── CodeSelector.tsx
│ │ │ ├── ConfirmationDialog.module.scss
│ │ │ ├── ConfirmationDialog.tsx
│ │ │ ├── Editor.tsx
│ │ │ ├── Header.module.scss
│ │ │ ├── Header.tsx
│ │ │ ├── Playground.tsx
│ │ │ ├── PlaygroundContent.module.scss
│ │ │ ├── PlaygroundContent.tsx
│ │ │ ├── PlaygroundNative.module.scss
│ │ │ ├── PlaygroundNative.tsx
│ │ │ ├── Preview.module.scss
│ │ │ ├── Preview.tsx
│ │ │ ├── Select.module.scss
│ │ │ ├── StandalonePlayground.tsx
│ │ │ ├── StandalonePlaygroundNative.module.scss
│ │ │ ├── StandalonePlaygroundNative.tsx
│ │ │ ├── ThemeSwitcher.module.scss
│ │ │ ├── ThemeSwitcher.tsx
│ │ │ ├── ToneSwitcher.tsx
│ │ │ ├── Tooltip.module.scss
│ │ │ ├── Tooltip.tsx
│ │ │ └── utils.ts
│ │ ├── providers
│ │ │ ├── Toast.module.scss
│ │ │ └── ToastProvider.tsx
│ │ ├── state
│ │ │ └── store.ts
│ │ ├── themes
│ │ │ └── theme.ts
│ │ └── utils
│ │ └── helpers.ts
│ ├── xmlui-search
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── index.tsx
│ │ ├── Search.module.scss
│ │ └── Search.tsx
│ ├── xmlui-spreadsheet
│ │ ├── .gitignore
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── index.tsx
│ │ ├── Spreadsheet.tsx
│ │ └── SpreadsheetNative.tsx
│ └── xmlui-website-blocks
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── demo
│ │ ├── components
│ │ │ ├── HeroBackgroundBreakoutPage.xmlui
│ │ │ ├── HeroBackgroundsPage.xmlui
│ │ │ ├── HeroContentsPage.xmlui
│ │ │ ├── HeroTextAlignPage.xmlui
│ │ │ ├── HeroTextPage.xmlui
│ │ │ └── HeroTonesPage.xmlui
│ │ ├── Main.xmlui
│ │ └── themes
│ │ └── default.ts
│ ├── index.html
│ ├── index.ts
│ ├── meta
│ │ └── componentsMetadata.ts
│ ├── package.json
│ ├── public
│ │ └── resources
│ │ ├── building.jpg
│ │ └── xmlui-logo.svg
│ └── src
│ ├── Carousel
│ │ ├── Carousel.module.scss
│ │ ├── Carousel.tsx
│ │ ├── CarouselContext.tsx
│ │ └── CarouselNative.tsx
│ ├── FancyButton
│ │ ├── FancyButton.module.scss
│ │ ├── FancyButton.tsx
│ │ └── FancyButton.xmlui
│ ├── Hello
│ │ ├── Hello.tsx
│ │ ├── Hello.xmlui
│ │ └── Hello.xmlui.xs
│ ├── HeroSection
│ │ ├── HeroSection.module.scss
│ │ ├── HeroSection.spec.ts
│ │ ├── HeroSection.tsx
│ │ └── HeroSectionNative.tsx
│ ├── index.tsx
│ ├── ScrollToTop
│ │ ├── ScrollToTop.module.scss
│ │ ├── ScrollToTop.tsx
│ │ └── ScrollToTopNative.tsx
│ └── vite-env.d.ts
├── playwright.config.ts
├── README.md
├── tools
│ ├── codefence
│ │ └── xmlui-code-fence-docs.md
│ ├── create-app
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── create-app.ts
│ │ ├── helpers
│ │ │ ├── copy.ts
│ │ │ ├── get-pkg-manager.ts
│ │ │ ├── git.ts
│ │ │ ├── install.ts
│ │ │ ├── is-folder-empty.ts
│ │ │ ├── is-writeable.ts
│ │ │ ├── make-dir.ts
│ │ │ └── validate-pkg.ts
│ │ ├── index.ts
│ │ ├── package.json
│ │ ├── templates
│ │ │ ├── default
│ │ │ │ └── ts
│ │ │ │ ├── gitignore
│ │ │ │ ├── index.html
│ │ │ │ ├── index.ts
│ │ │ │ ├── public
│ │ │ │ │ ├── mockServiceWorker.js
│ │ │ │ │ ├── resources
│ │ │ │ │ │ ├── favicon.ico
│ │ │ │ │ │ └── xmlui-logo.svg
│ │ │ │ │ └── serve.json
│ │ │ │ └── src
│ │ │ │ ├── components
│ │ │ │ │ ├── ApiAware.xmlui
│ │ │ │ │ ├── Home.xmlui
│ │ │ │ │ ├── IncButton.xmlui
│ │ │ │ │ └── PagePanel.xmlui
│ │ │ │ ├── config.ts
│ │ │ │ └── Main.xmlui
│ │ │ ├── index.ts
│ │ │ └── types.ts
│ │ └── tsconfig.json
│ ├── create-xmlui-hello-world
│ │ ├── index.js
│ │ └── package.json
│ └── vscode
│ ├── .gitignore
│ ├── .vscode
│ │ ├── launch.json
│ │ └── tasks.json
│ ├── .vscodeignore
│ ├── build.sh
│ ├── CHANGELOG.md
│ ├── esbuild.js
│ ├── eslint.config.mjs
│ ├── formatter-docs.md
│ ├── generate-test-sample.sh
│ ├── LICENSE.md
│ ├── package-lock.json
│ ├── package.json
│ ├── README.md
│ ├── resources
│ │ ├── xmlui-logo.png
│ │ └── xmlui-markup-syntax-highlighting.png
│ ├── src
│ │ ├── extension.ts
│ │ └── server.ts
│ ├── syntaxes
│ │ └── xmlui.tmLanguage.json
│ ├── test-samples
│ │ └── sample.xmlui
│ ├── tsconfig.json
│ └── tsconfig.tsbuildinfo
├── turbo.json
└── xmlui
├── .gitignore
├── bin
│ ├── bootstrap.cjs
│ ├── bootstrap.js
│ ├── build-lib.ts
│ ├── build.ts
│ ├── index.ts
│ ├── preview.ts
│ ├── start.ts
│ ├── vite-xmlui-plugin.ts
│ └── viteConfig.ts
├── CHANGELOG.md
├── conventions
│ ├── component-qa-checklist.md
│ ├── copilot-conventions.md
│ ├── create-xmlui-components.md
│ ├── mermaid.md
│ ├── testing-conventions.md
│ └── xmlui-in-a-nutshell.md
├── dev-docs
│ ├── accessibility.md
│ ├── build-system.md
│ ├── build-xmlui.md
│ ├── component-behaviors.md
│ ├── components-with-options.md
│ ├── containers.md
│ ├── data-operations.md
│ ├── glossary.md
│ ├── index.md
│ ├── next
│ │ ├── component-dev-guide.md
│ │ ├── configuration-management-enhancement-summary.md
│ │ ├── documentation-scripts-refactoring-complete-summary.md
│ │ ├── documentation-scripts-refactoring-plan.md
│ │ ├── duplicate-pattern-extraction-summary.md
│ │ ├── error-handling-standardization-summary.md
│ │ ├── generating-component-reference.md
│ │ ├── index.md
│ │ ├── logging-consistency-implementation-summary.md
│ │ ├── project-build.md
│ │ ├── project-structure.md
│ │ ├── theme-context.md
│ │ ├── tiptap-design-considerations.md
│ │ ├── working-with-code.md
│ │ ├── xmlui-runtime-architecture
│ │ └── xmlui-wcag-accessibility-report.md
│ ├── react-fundamentals.md
│ ├── release-method.md
│ ├── standalone-app.md
│ ├── ud-components.md
│ └── xmlui-repo.md
├── package.json
├── scripts
│ ├── coverage-only.js
│ ├── e2e-test-summary.js
│ ├── generate-docs
│ │ ├── build-downloads-map.mjs
│ │ ├── build-pages-map.mjs
│ │ ├── components-config.json
│ │ ├── configuration-management.mjs
│ │ ├── constants.mjs
│ │ ├── create-theme-files.mjs
│ │ ├── DocsGenerator.mjs
│ │ ├── error-handling.mjs
│ │ ├── extensions-config.json
│ │ ├── folders.mjs
│ │ ├── generate-summary-files.mjs
│ │ ├── get-docs.mjs
│ │ ├── input-handler.mjs
│ │ ├── logger.mjs
│ │ ├── logging-standards.mjs
│ │ ├── MetadataProcessor.mjs
│ │ ├── pattern-utilities.mjs
│ │ └── utils.mjs
│ ├── get-langserver-metadata.js
│ ├── inline-links.mjs
│ └── README-e2e-summary.md
├── src
│ ├── abstractions
│ │ ├── _conventions.md
│ │ ├── ActionDefs.ts
│ │ ├── AppContextDefs.ts
│ │ ├── ComponentDefs.ts
│ │ ├── ContainerDefs.ts
│ │ ├── ExtensionDefs.ts
│ │ ├── FunctionDefs.ts
│ │ ├── RendererDefs.ts
│ │ ├── scripting
│ │ │ ├── BlockScope.ts
│ │ │ ├── Compilation.ts
│ │ │ ├── LogicalThread.ts
│ │ │ ├── LoopScope.ts
│ │ │ ├── modules.ts
│ │ │ ├── ScriptParserError.ts
│ │ │ ├── Token.ts
│ │ │ ├── TryScope.ts
│ │ │ └── TryScopeExp.ts
│ │ └── ThemingDefs.ts
│ ├── components
│ │ ├── _conventions.md
│ │ ├── abstractions.ts
│ │ ├── Accordion
│ │ │ ├── Accordion.md
│ │ │ ├── Accordion.module.scss
│ │ │ ├── Accordion.spec.ts
│ │ │ ├── Accordion.tsx
│ │ │ ├── AccordionContext.tsx
│ │ │ ├── AccordionItem.tsx
│ │ │ ├── AccordionItemNative.tsx
│ │ │ └── AccordionNative.tsx
│ │ ├── Animation
│ │ │ └── AnimationNative.tsx
│ │ ├── APICall
│ │ │ ├── APICall.md
│ │ │ ├── APICall.spec.ts
│ │ │ ├── APICall.tsx
│ │ │ └── APICallNative.tsx
│ │ ├── App
│ │ │ ├── App.md
│ │ │ ├── App.module.scss
│ │ │ ├── App.spec.ts
│ │ │ ├── App.tsx
│ │ │ ├── AppLayoutContext.ts
│ │ │ ├── AppNative.tsx
│ │ │ ├── AppStateContext.ts
│ │ │ ├── doc-resources
│ │ │ │ ├── condensed-sticky.xmlui
│ │ │ │ ├── condensed.xmlui
│ │ │ │ ├── horizontal-sticky.xmlui
│ │ │ │ ├── horizontal.xmlui
│ │ │ │ ├── vertical-full-header.xmlui
│ │ │ │ ├── vertical-sticky.xmlui
│ │ │ │ └── vertical.xmlui
│ │ │ ├── IndexerContext.ts
│ │ │ ├── LinkInfoContext.ts
│ │ │ ├── SearchContext.tsx
│ │ │ ├── Sheet.module.scss
│ │ │ └── Sheet.tsx
│ │ ├── AppHeader
│ │ │ ├── AppHeader.md
│ │ │ ├── AppHeader.module.scss
│ │ │ ├── AppHeader.spec.ts
│ │ │ ├── AppHeader.tsx
│ │ │ └── AppHeaderNative.tsx
│ │ ├── AppState
│ │ │ ├── AppState.md
│ │ │ ├── AppState.spec.ts
│ │ │ ├── AppState.tsx
│ │ │ └── AppStateNative.tsx
│ │ ├── AutoComplete
│ │ │ ├── AutoComplete.md
│ │ │ ├── AutoComplete.module.scss
│ │ │ ├── AutoComplete.spec.ts
│ │ │ ├── AutoComplete.tsx
│ │ │ ├── AutoCompleteContext.tsx
│ │ │ └── AutoCompleteNative.tsx
│ │ ├── Avatar
│ │ │ ├── Avatar.md
│ │ │ ├── Avatar.module.scss
│ │ │ ├── Avatar.spec.ts
│ │ │ ├── Avatar.tsx
│ │ │ └── AvatarNative.tsx
│ │ ├── Backdrop
│ │ │ ├── Backdrop.md
│ │ │ ├── Backdrop.module.scss
│ │ │ ├── Backdrop.spec.ts
│ │ │ ├── Backdrop.tsx
│ │ │ └── BackdropNative.tsx
│ │ ├── Badge
│ │ │ ├── Badge.md
│ │ │ ├── Badge.module.scss
│ │ │ ├── Badge.spec.ts
│ │ │ ├── Badge.tsx
│ │ │ └── BadgeNative.tsx
│ │ ├── Bookmark
│ │ │ ├── Bookmark.md
│ │ │ ├── Bookmark.module.scss
│ │ │ ├── Bookmark.spec.ts
│ │ │ ├── Bookmark.tsx
│ │ │ └── BookmarkNative.tsx
│ │ ├── Breakout
│ │ │ ├── Breakout.module.scss
│ │ │ ├── Breakout.spec.ts
│ │ │ ├── Breakout.tsx
│ │ │ └── BreakoutNative.tsx
│ │ ├── Button
│ │ │ ├── Button-style.spec.ts
│ │ │ ├── Button.md
│ │ │ ├── Button.module.scss
│ │ │ ├── Button.spec.ts
│ │ │ ├── Button.tsx
│ │ │ └── ButtonNative.tsx
│ │ ├── Card
│ │ │ ├── Card.md
│ │ │ ├── Card.module.scss
│ │ │ ├── Card.spec.ts
│ │ │ ├── Card.tsx
│ │ │ └── CardNative.tsx
│ │ ├── Carousel
│ │ │ ├── Carousel.md
│ │ │ ├── Carousel.module.scss
│ │ │ ├── Carousel.spec.ts
│ │ │ ├── Carousel.tsx
│ │ │ ├── CarouselContext.tsx
│ │ │ ├── CarouselItem.tsx
│ │ │ ├── CarouselItemNative.tsx
│ │ │ └── CarouselNative.tsx
│ │ ├── ChangeListener
│ │ │ ├── ChangeListener.md
│ │ │ ├── ChangeListener.spec.ts
│ │ │ ├── ChangeListener.tsx
│ │ │ └── ChangeListenerNative.tsx
│ │ ├── chart-color-schemes.ts
│ │ ├── Charts
│ │ │ ├── AreaChart
│ │ │ │ ├── AreaChart.md
│ │ │ │ ├── AreaChart.spec.ts
│ │ │ │ ├── AreaChart.tsx
│ │ │ │ └── AreaChartNative.tsx
│ │ │ ├── BarChart
│ │ │ │ ├── BarChart.md
│ │ │ │ ├── BarChart.module.scss
│ │ │ │ ├── BarChart.spec.ts
│ │ │ │ ├── BarChart.tsx
│ │ │ │ └── BarChartNative.tsx
│ │ │ ├── DonutChart
│ │ │ │ ├── DonutChart.spec.ts
│ │ │ │ └── DonutChart.tsx
│ │ │ ├── LabelList
│ │ │ │ ├── LabelList.spec.ts
│ │ │ │ ├── LabelList.tsx
│ │ │ │ ├── LabelListNative.module.scss
│ │ │ │ └── LabelListNative.tsx
│ │ │ ├── Legend
│ │ │ │ ├── Legend.spec.ts
│ │ │ │ ├── Legend.tsx
│ │ │ │ └── LegendNative.tsx
│ │ │ ├── LineChart
│ │ │ │ ├── LineChart.md
│ │ │ │ ├── LineChart.module.scss
│ │ │ │ ├── LineChart.spec.ts
│ │ │ │ ├── LineChart.tsx
│ │ │ │ └── LineChartNative.tsx
│ │ │ ├── PieChart
│ │ │ │ ├── PieChart.md
│ │ │ │ ├── PieChart.spec.ts
│ │ │ │ ├── PieChart.tsx
│ │ │ │ ├── PieChartNative.module.scss
│ │ │ │ └── PieChartNative.tsx
│ │ │ ├── RadarChart
│ │ │ │ ├── RadarChart.md
│ │ │ │ ├── RadarChart.spec.ts
│ │ │ │ ├── RadarChart.tsx
│ │ │ │ └── RadarChartNative.tsx
│ │ │ ├── Tooltip
│ │ │ │ ├── TooltipContent.module.scss
│ │ │ │ ├── TooltipContent.spec.ts
│ │ │ │ └── TooltipContent.tsx
│ │ │ └── utils
│ │ │ ├── abstractions.ts
│ │ │ └── ChartProvider.tsx
│ │ ├── Checkbox
│ │ │ ├── Checkbox.md
│ │ │ ├── Checkbox.spec.ts
│ │ │ └── Checkbox.tsx
│ │ ├── CodeBlock
│ │ │ ├── CodeBlock.module.scss
│ │ │ ├── CodeBlock.spec.ts
│ │ │ ├── CodeBlock.tsx
│ │ │ ├── CodeBlockNative.tsx
│ │ │ └── highlight-code.ts
│ │ ├── collectedComponentMetadata.ts
│ │ ├── ColorPicker
│ │ │ ├── ColorPicker.md
│ │ │ ├── ColorPicker.module.scss
│ │ │ ├── ColorPicker.spec.ts
│ │ │ ├── ColorPicker.tsx
│ │ │ └── ColorPickerNative.tsx
│ │ ├── Column
│ │ │ ├── Column.md
│ │ │ ├── Column.tsx
│ │ │ ├── ColumnNative.tsx
│ │ │ ├── doc-resources
│ │ │ │ └── list-component-data.js
│ │ │ └── TableContext.tsx
│ │ ├── component-utils.ts
│ │ ├── ComponentProvider.tsx
│ │ ├── ComponentRegistryContext.tsx
│ │ ├── container-helpers.tsx
│ │ ├── ContentSeparator
│ │ │ ├── ContentSeparator.md
│ │ │ ├── ContentSeparator.module.scss
│ │ │ ├── ContentSeparator.spec.ts
│ │ │ ├── ContentSeparator.tsx
│ │ │ └── ContentSeparatorNative.tsx
│ │ ├── DataSource
│ │ │ ├── DataSource.md
│ │ │ └── DataSource.tsx
│ │ ├── DateInput
│ │ │ ├── DateInput.md
│ │ │ ├── DateInput.module.scss
│ │ │ ├── DateInput.spec.ts
│ │ │ ├── DateInput.tsx
│ │ │ └── DateInputNative.tsx
│ │ ├── DatePicker
│ │ │ ├── DatePicker.md
│ │ │ ├── DatePicker.module.scss
│ │ │ ├── DatePicker.spec.ts
│ │ │ ├── DatePicker.tsx
│ │ │ └── DatePickerNative.tsx
│ │ ├── DropdownMenu
│ │ │ ├── DropdownMenu.md
│ │ │ ├── DropdownMenu.module.scss
│ │ │ ├── DropdownMenu.spec.ts
│ │ │ ├── DropdownMenu.tsx
│ │ │ ├── DropdownMenuNative.tsx
│ │ │ ├── MenuItem.md
│ │ │ └── SubMenuItem.md
│ │ ├── EmojiSelector
│ │ │ ├── EmojiSelector.md
│ │ │ ├── EmojiSelector.spec.ts
│ │ │ ├── EmojiSelector.tsx
│ │ │ └── EmojiSelectorNative.tsx
│ │ ├── ExpandableItem
│ │ │ ├── ExpandableItem.module.scss
│ │ │ ├── ExpandableItem.spec.ts
│ │ │ ├── ExpandableItem.tsx
│ │ │ └── ExpandableItemNative.tsx
│ │ ├── FileInput
│ │ │ ├── FileInput.md
│ │ │ ├── FileInput.module.scss
│ │ │ ├── FileInput.spec.ts
│ │ │ ├── FileInput.tsx
│ │ │ └── FileInputNative.tsx
│ │ ├── FileUploadDropZone
│ │ │ ├── FileUploadDropZone.md
│ │ │ ├── FileUploadDropZone.module.scss
│ │ │ ├── FileUploadDropZone.spec.ts
│ │ │ ├── FileUploadDropZone.tsx
│ │ │ └── FileUploadDropZoneNative.tsx
│ │ ├── FlowLayout
│ │ │ ├── FlowLayout.md
│ │ │ ├── FlowLayout.module.scss
│ │ │ ├── FlowLayout.spec.ts
│ │ │ ├── FlowLayout.spec.ts-snapshots
│ │ │ │ └── Edge-cases-boxShadow-is-not-clipped-1-non-smoke-darwin.png
│ │ │ ├── FlowLayout.tsx
│ │ │ └── FlowLayoutNative.tsx
│ │ ├── Footer
│ │ │ ├── Footer.md
│ │ │ ├── Footer.module.scss
│ │ │ ├── Footer.spec.ts
│ │ │ ├── Footer.tsx
│ │ │ └── FooterNative.tsx
│ │ ├── Form
│ │ │ ├── Form.md
│ │ │ ├── Form.module.scss
│ │ │ ├── Form.spec.ts
│ │ │ ├── Form.tsx
│ │ │ ├── formActions.ts
│ │ │ ├── FormContext.ts
│ │ │ └── FormNative.tsx
│ │ ├── FormItem
│ │ │ ├── FormItem.md
│ │ │ ├── FormItem.module.scss
│ │ │ ├── FormItem.spec.ts
│ │ │ ├── FormItem.tsx
│ │ │ ├── FormItemNative.tsx
│ │ │ ├── HelperText.module.scss
│ │ │ ├── HelperText.tsx
│ │ │ ├── ItemWithLabel.tsx
│ │ │ └── Validations.ts
│ │ ├── FormSection
│ │ │ ├── FormSection.md
│ │ │ ├── FormSection.ts
│ │ │ └── FormSection.xmlui
│ │ ├── Fragment
│ │ │ ├── Fragment.spec.ts
│ │ │ └── Fragment.tsx
│ │ ├── Heading
│ │ │ ├── abstractions.ts
│ │ │ ├── H1.md
│ │ │ ├── H1.spec.ts
│ │ │ ├── H2.md
│ │ │ ├── H2.spec.ts
│ │ │ ├── H3.md
│ │ │ ├── H3.spec.ts
│ │ │ ├── H4.md
│ │ │ ├── H4.spec.ts
│ │ │ ├── H5.md
│ │ │ ├── H5.spec.ts
│ │ │ ├── H6.md
│ │ │ ├── H6.spec.ts
│ │ │ ├── Heading.md
│ │ │ ├── Heading.module.scss
│ │ │ ├── Heading.spec.ts
│ │ │ ├── Heading.tsx
│ │ │ └── HeadingNative.tsx
│ │ ├── HoverCard
│ │ │ ├── HoverCard.tsx
│ │ │ └── HovercardNative.tsx
│ │ ├── HtmlTags
│ │ │ ├── HtmlTags.module.scss
│ │ │ ├── HtmlTags.spec.ts
│ │ │ └── HtmlTags.tsx
│ │ ├── Icon
│ │ │ ├── AdmonitionDanger.tsx
│ │ │ ├── AdmonitionInfo.tsx
│ │ │ ├── AdmonitionNote.tsx
│ │ │ ├── AdmonitionTip.tsx
│ │ │ ├── AdmonitionWarning.tsx
│ │ │ ├── ApiIcon.tsx
│ │ │ ├── ArrowDropDown.module.scss
│ │ │ ├── ArrowDropDown.tsx
│ │ │ ├── ArrowDropUp.module.scss
│ │ │ ├── ArrowDropUp.tsx
│ │ │ ├── ArrowLeft.module.scss
│ │ │ ├── ArrowLeft.tsx
│ │ │ ├── ArrowRight.module.scss
│ │ │ ├── ArrowRight.tsx
│ │ │ ├── Attach.tsx
│ │ │ ├── Binding.module.scss
│ │ │ ├── Binding.tsx
│ │ │ ├── BoardIcon.tsx
│ │ │ ├── BoxIcon.tsx
│ │ │ ├── CheckIcon.tsx
│ │ │ ├── ChevronDownIcon.tsx
│ │ │ ├── ChevronLeft.tsx
│ │ │ ├── ChevronRight.tsx
│ │ │ ├── ChevronUpIcon.tsx
│ │ │ ├── CodeFileIcon.tsx
│ │ │ ├── CodeSandbox.tsx
│ │ │ ├── CompactListIcon.tsx
│ │ │ ├── ContentCopyIcon.tsx
│ │ │ ├── DarkToLightIcon.tsx
│ │ │ ├── DatabaseIcon.module.scss
│ │ │ ├── DatabaseIcon.tsx
│ │ │ ├── DocFileIcon.tsx
│ │ │ ├── DocIcon.tsx
│ │ │ ├── DotMenuHorizontalIcon.tsx
│ │ │ ├── DotMenuIcon.tsx
│ │ │ ├── EmailIcon.tsx
│ │ │ ├── EmptyFolderIcon.tsx
│ │ │ ├── ErrorIcon.tsx
│ │ │ ├── ExpressionIcon.tsx
│ │ │ ├── FillPlusCricleIcon.tsx
│ │ │ ├── FilterIcon.tsx
│ │ │ ├── FolderIcon.tsx
│ │ │ ├── GlobeIcon.tsx
│ │ │ ├── HomeIcon.tsx
│ │ │ ├── HyperLinkIcon.tsx
│ │ │ ├── Icon.md
│ │ │ ├── Icon.module.scss
│ │ │ ├── Icon.spec.ts
│ │ │ ├── Icon.tsx
│ │ │ ├── IconNative.tsx
│ │ │ ├── ImageFileIcon.tsx
│ │ │ ├── Inspect.tsx
│ │ │ ├── LightToDark.tsx
│ │ │ ├── LinkIcon.tsx
│ │ │ ├── ListIcon.tsx
│ │ │ ├── LooseListIcon.tsx
│ │ │ ├── MoonIcon.tsx
│ │ │ ├── MoreOptionsIcon.tsx
│ │ │ ├── NoSortIcon.tsx
│ │ │ ├── PDFIcon.tsx
│ │ │ ├── PenIcon.tsx
│ │ │ ├── PhoneIcon.tsx
│ │ │ ├── PhotoIcon.tsx
│ │ │ ├── PlusIcon.tsx
│ │ │ ├── SearchIcon.tsx
│ │ │ ├── ShareIcon.tsx
│ │ │ ├── SortAscendingIcon.tsx
│ │ │ ├── SortDescendingIcon.tsx
│ │ │ ├── StarsIcon.tsx
│ │ │ ├── SunIcon.tsx
│ │ │ ├── svg
│ │ │ │ ├── admonition_danger.svg
│ │ │ │ ├── admonition_info.svg
│ │ │ │ ├── admonition_note.svg
│ │ │ │ ├── admonition_tip.svg
│ │ │ │ ├── admonition_warning.svg
│ │ │ │ ├── api.svg
│ │ │ │ ├── arrow-dropdown.svg
│ │ │ │ ├── arrow-left.svg
│ │ │ │ ├── arrow-right.svg
│ │ │ │ ├── arrow-up.svg
│ │ │ │ ├── attach.svg
│ │ │ │ ├── binding.svg
│ │ │ │ ├── box.svg
│ │ │ │ ├── bulb.svg
│ │ │ │ ├── code-file.svg
│ │ │ │ ├── code-sandbox.svg
│ │ │ │ ├── dark_to_light.svg
│ │ │ │ ├── database.svg
│ │ │ │ ├── doc.svg
│ │ │ │ ├── empty-folder.svg
│ │ │ │ ├── expression.svg
│ │ │ │ ├── eye-closed.svg
│ │ │ │ ├── eye-dark.svg
│ │ │ │ ├── eye.svg
│ │ │ │ ├── file-text.svg
│ │ │ │ ├── filter.svg
│ │ │ │ ├── folder.svg
│ │ │ │ ├── img.svg
│ │ │ │ ├── inspect.svg
│ │ │ │ ├── light_to_dark.svg
│ │ │ │ ├── moon.svg
│ │ │ │ ├── pdf.svg
│ │ │ │ ├── photo.svg
│ │ │ │ ├── share.svg
│ │ │ │ ├── stars.svg
│ │ │ │ ├── sun.svg
│ │ │ │ ├── trending-down.svg
│ │ │ │ ├── trending-level.svg
│ │ │ │ ├── trending-up.svg
│ │ │ │ ├── txt.svg
│ │ │ │ ├── unknown-file.svg
│ │ │ │ ├── unlink.svg
│ │ │ │ └── xls.svg
│ │ │ ├── TableDeleteColumnIcon.tsx
│ │ │ ├── TableDeleteRowIcon.tsx
│ │ │ ├── TableInsertColumnIcon.tsx
│ │ │ ├── TableInsertRowIcon.tsx
│ │ │ ├── TrashIcon.tsx
│ │ │ ├── TrendingDownIcon.tsx
│ │ │ ├── TrendingLevelIcon.tsx
│ │ │ ├── TrendingUpIcon.tsx
│ │ │ ├── TxtIcon.tsx
│ │ │ ├── UnknownFileIcon.tsx
│ │ │ ├── UnlinkIcon.tsx
│ │ │ ├── UserIcon.tsx
│ │ │ ├── WarningIcon.tsx
│ │ │ └── XlsIcon.tsx
│ │ ├── IconProvider.tsx
│ │ ├── IconRegistryContext.tsx
│ │ ├── IFrame
│ │ │ ├── IFrame.md
│ │ │ ├── IFrame.module.scss
│ │ │ ├── IFrame.spec.ts
│ │ │ ├── IFrame.tsx
│ │ │ └── IFrameNative.tsx
│ │ ├── Image
│ │ │ ├── Image.md
│ │ │ ├── Image.module.scss
│ │ │ ├── Image.spec.ts
│ │ │ ├── Image.tsx
│ │ │ └── ImageNative.tsx
│ │ ├── Input
│ │ │ ├── index.ts
│ │ │ ├── InputAdornment.module.scss
│ │ │ ├── InputAdornment.tsx
│ │ │ ├── InputDivider.module.scss
│ │ │ ├── InputDivider.tsx
│ │ │ ├── InputLabel.module.scss
│ │ │ ├── InputLabel.tsx
│ │ │ ├── PartialInput.module.scss
│ │ │ └── PartialInput.tsx
│ │ ├── InspectButton
│ │ │ ├── InspectButton.module.scss
│ │ │ └── InspectButton.tsx
│ │ ├── Items
│ │ │ ├── Items.md
│ │ │ ├── Items.spec.ts
│ │ │ ├── Items.tsx
│ │ │ └── ItemsNative.tsx
│ │ ├── Link
│ │ │ ├── Link.md
│ │ │ ├── Link.module.scss
│ │ │ ├── Link.spec.ts
│ │ │ ├── Link.tsx
│ │ │ └── LinkNative.tsx
│ │ ├── List
│ │ │ ├── doc-resources
│ │ │ │ └── list-component-data.js
│ │ │ ├── List.md
│ │ │ ├── List.module.scss
│ │ │ ├── List.spec.ts
│ │ │ ├── List.tsx
│ │ │ └── ListNative.tsx
│ │ ├── Logo
│ │ │ ├── doc-resources
│ │ │ │ └── xmlui-logo.svg
│ │ │ ├── Logo.md
│ │ │ ├── Logo.tsx
│ │ │ └── LogoNative.tsx
│ │ ├── Markdown
│ │ │ ├── CodeText.module.scss
│ │ │ ├── CodeText.tsx
│ │ │ ├── Markdown.md
│ │ │ ├── Markdown.module.scss
│ │ │ ├── Markdown.spec.ts
│ │ │ ├── Markdown.tsx
│ │ │ ├── MarkdownNative.tsx
│ │ │ ├── parse-binding-expr.ts
│ │ │ └── utils.ts
│ │ ├── metadata-helpers.ts
│ │ ├── ModalDialog
│ │ │ ├── ConfirmationModalContextProvider.tsx
│ │ │ ├── Dialog.module.scss
│ │ │ ├── Dialog.tsx
│ │ │ ├── ModalDialog.md
│ │ │ ├── ModalDialog.module.scss
│ │ │ ├── ModalDialog.spec.ts
│ │ │ ├── ModalDialog.tsx
│ │ │ ├── ModalDialogNative.tsx
│ │ │ └── ModalVisibilityContext.tsx
│ │ ├── NavGroup
│ │ │ ├── NavGroup.md
│ │ │ ├── NavGroup.module.scss
│ │ │ ├── NavGroup.spec.ts
│ │ │ ├── NavGroup.tsx
│ │ │ ├── NavGroupContext.ts
│ │ │ └── NavGroupNative.tsx
│ │ ├── NavLink
│ │ │ ├── NavLink.md
│ │ │ ├── NavLink.module.scss
│ │ │ ├── NavLink.spec.ts
│ │ │ ├── NavLink.tsx
│ │ │ └── NavLinkNative.tsx
│ │ ├── NavPanel
│ │ │ ├── NavPanel.md
│ │ │ ├── NavPanel.module.scss
│ │ │ ├── NavPanel.spec.ts
│ │ │ ├── NavPanel.tsx
│ │ │ └── NavPanelNative.tsx
│ │ ├── NestedApp
│ │ │ ├── AppWithCodeView.module.scss
│ │ │ ├── AppWithCodeView.tsx
│ │ │ ├── AppWithCodeViewNative.tsx
│ │ │ ├── defaultProps.tsx
│ │ │ ├── logo.svg
│ │ │ ├── NestedApp.module.scss
│ │ │ ├── NestedApp.tsx
│ │ │ ├── NestedAppNative.tsx
│ │ │ ├── Tooltip.module.scss
│ │ │ ├── Tooltip.tsx
│ │ │ └── utils.ts
│ │ ├── NoResult
│ │ │ ├── NoResult.md
│ │ │ ├── NoResult.module.scss
│ │ │ ├── NoResult.spec.ts
│ │ │ ├── NoResult.tsx
│ │ │ └── NoResultNative.tsx
│ │ ├── NumberBox
│ │ │ ├── numberbox-abstractions.ts
│ │ │ ├── NumberBox.md
│ │ │ ├── NumberBox.module.scss
│ │ │ ├── NumberBox.spec.ts
│ │ │ ├── NumberBox.tsx
│ │ │ └── NumberBoxNative.tsx
│ │ ├── Option
│ │ │ ├── Option.md
│ │ │ ├── Option.spec.ts
│ │ │ ├── Option.tsx
│ │ │ ├── OptionNative.tsx
│ │ │ └── OptionTypeProvider.tsx
│ │ ├── PageMetaTitle
│ │ │ ├── PageMetaTilteNative.tsx
│ │ │ ├── PageMetaTitle.md
│ │ │ ├── PageMetaTitle.spec.ts
│ │ │ └── PageMetaTitle.tsx
│ │ ├── Pages
│ │ │ ├── Page.md
│ │ │ ├── Pages.md
│ │ │ ├── Pages.module.scss
│ │ │ ├── Pages.tsx
│ │ │ └── PagesNative.tsx
│ │ ├── Pagination
│ │ │ ├── Pagination.md
│ │ │ ├── Pagination.module.scss
│ │ │ ├── Pagination.spec.ts
│ │ │ ├── Pagination.tsx
│ │ │ └── PaginationNative.tsx
│ │ ├── PositionedContainer
│ │ │ ├── PositionedContainer.module.scss
│ │ │ ├── PositionedContainer.tsx
│ │ │ └── PositionedContainerNative.tsx
│ │ ├── ProfileMenu
│ │ │ ├── ProfileMenu.module.scss
│ │ │ └── ProfileMenu.tsx
│ │ ├── ProgressBar
│ │ │ ├── ProgressBar.md
│ │ │ ├── ProgressBar.module.scss
│ │ │ ├── ProgressBar.spec.ts
│ │ │ ├── ProgressBar.tsx
│ │ │ └── ProgressBarNative.tsx
│ │ ├── Queue
│ │ │ ├── Queue.md
│ │ │ ├── Queue.spec.ts
│ │ │ ├── Queue.tsx
│ │ │ ├── queueActions.ts
│ │ │ └── QueueNative.tsx
│ │ ├── RadioGroup
│ │ │ ├── RadioGroup.md
│ │ │ ├── RadioGroup.module.scss
│ │ │ ├── RadioGroup.spec.ts
│ │ │ ├── RadioGroup.tsx
│ │ │ ├── RadioGroupNative.tsx
│ │ │ ├── RadioItem.tsx
│ │ │ └── RadioItemNative.tsx
│ │ ├── RealTimeAdapter
│ │ │ ├── RealTimeAdapter.tsx
│ │ │ └── RealTimeAdapterNative.tsx
│ │ ├── Redirect
│ │ │ ├── Redirect.md
│ │ │ ├── Redirect.spec.ts
│ │ │ └── Redirect.tsx
│ │ ├── ResponsiveBar
│ │ │ ├── README.md
│ │ │ ├── ResponsiveBar.md
│ │ │ ├── ResponsiveBar.module.scss
│ │ │ ├── ResponsiveBar.spec.ts
│ │ │ ├── ResponsiveBar.tsx
│ │ │ └── ResponsiveBarNative.tsx
│ │ ├── Select
│ │ │ ├── HiddenOption.tsx
│ │ │ ├── OptionContext.ts
│ │ │ ├── Select.md
│ │ │ ├── Select.module.scss
│ │ │ ├── Select.spec.ts
│ │ │ ├── Select.tsx
│ │ │ ├── SelectContext.tsx
│ │ │ └── SelectNative.tsx
│ │ ├── SelectionStore
│ │ │ ├── SelectionStore.md
│ │ │ ├── SelectionStore.tsx
│ │ │ └── SelectionStoreNative.tsx
│ │ ├── Slider
│ │ │ ├── Slider.md
│ │ │ ├── Slider.module.scss
│ │ │ ├── Slider.spec.ts
│ │ │ ├── Slider.tsx
│ │ │ └── SliderNative.tsx
│ │ ├── Slot
│ │ │ ├── Slot.md
│ │ │ ├── Slot.spec.ts
│ │ │ └── Slot.ts
│ │ ├── SlotItem.tsx
│ │ ├── SpaceFiller
│ │ │ ├── SpaceFiller.md
│ │ │ ├── SpaceFiller.module.scss
│ │ │ ├── SpaceFiller.spec.ts
│ │ │ ├── SpaceFiller.tsx
│ │ │ └── SpaceFillerNative.tsx
│ │ ├── Spinner
│ │ │ ├── Spinner.md
│ │ │ ├── Spinner.module.scss
│ │ │ ├── Spinner.spec.ts
│ │ │ ├── Spinner.tsx
│ │ │ └── SpinnerNative.tsx
│ │ ├── Splitter
│ │ │ ├── HSplitter.md
│ │ │ ├── HSplitter.spec.ts
│ │ │ ├── Splitter.md
│ │ │ ├── Splitter.module.scss
│ │ │ ├── Splitter.spec.ts
│ │ │ ├── Splitter.tsx
│ │ │ ├── SplitterNative.tsx
│ │ │ ├── utils.ts
│ │ │ ├── VSplitter.md
│ │ │ └── VSplitter.spec.ts
│ │ ├── Stack
│ │ │ ├── CHStack.md
│ │ │ ├── CHStack.spec.ts
│ │ │ ├── CVStack.md
│ │ │ ├── CVStack.spec.ts
│ │ │ ├── HStack.md
│ │ │ ├── HStack.spec.ts
│ │ │ ├── Stack.md
│ │ │ ├── Stack.module.scss
│ │ │ ├── Stack.spec.ts
│ │ │ ├── Stack.tsx
│ │ │ ├── StackNative.tsx
│ │ │ ├── VStack.md
│ │ │ └── VStack.spec.ts
│ │ ├── StickyBox
│ │ │ ├── StickyBox.md
│ │ │ ├── StickyBox.module.scss
│ │ │ ├── StickyBox.tsx
│ │ │ └── StickyBoxNative.tsx
│ │ ├── Switch
│ │ │ ├── Switch.md
│ │ │ ├── Switch.spec.ts
│ │ │ └── Switch.tsx
│ │ ├── Table
│ │ │ ├── doc-resources
│ │ │ │ └── list-component-data.js
│ │ │ ├── react-table-config.d.ts
│ │ │ ├── Table.md
│ │ │ ├── Table.module.scss
│ │ │ ├── Table.spec.ts
│ │ │ ├── Table.tsx
│ │ │ ├── TableNative.tsx
│ │ │ └── useRowSelection.tsx
│ │ ├── TableOfContents
│ │ │ ├── TableOfContents.module.scss
│ │ │ ├── TableOfContents.spec.ts
│ │ │ ├── TableOfContents.tsx
│ │ │ └── TableOfContentsNative.tsx
│ │ ├── Tabs
│ │ │ ├── TabContext.tsx
│ │ │ ├── TabItem.md
│ │ │ ├── TabItem.tsx
│ │ │ ├── TabItemNative.tsx
│ │ │ ├── Tabs.md
│ │ │ ├── Tabs.module.scss
│ │ │ ├── Tabs.spec.ts
│ │ │ ├── Tabs.tsx
│ │ │ └── TabsNative.tsx
│ │ ├── Text
│ │ │ ├── Text.md
│ │ │ ├── Text.module.scss
│ │ │ ├── Text.spec.ts
│ │ │ ├── Text.tsx
│ │ │ └── TextNative.tsx
│ │ ├── TextArea
│ │ │ ├── TextArea.md
│ │ │ ├── TextArea.module.scss
│ │ │ ├── TextArea.spec.ts
│ │ │ ├── TextArea.tsx
│ │ │ ├── TextAreaNative.tsx
│ │ │ ├── TextAreaResizable.tsx
│ │ │ └── useComposedRef.ts
│ │ ├── TextBox
│ │ │ ├── TextBox.md
│ │ │ ├── TextBox.module.scss
│ │ │ ├── TextBox.spec.ts
│ │ │ ├── TextBox.tsx
│ │ │ └── TextBoxNative.tsx
│ │ ├── Theme
│ │ │ ├── NotificationToast.tsx
│ │ │ ├── Theme.md
│ │ │ ├── Theme.module.scss
│ │ │ ├── Theme.spec.ts
│ │ │ ├── Theme.tsx
│ │ │ └── ThemeNative.tsx
│ │ ├── TimeInput
│ │ │ ├── TimeInput.md
│ │ │ ├── TimeInput.module.scss
│ │ │ ├── TimeInput.spec.ts
│ │ │ ├── TimeInput.tsx
│ │ │ ├── TimeInputNative.tsx
│ │ │ └── utils.ts
│ │ ├── Timer
│ │ │ ├── Timer.md
│ │ │ ├── Timer.spec.ts
│ │ │ ├── Timer.tsx
│ │ │ └── TimerNative.tsx
│ │ ├── Toggle
│ │ │ ├── Toggle.module.scss
│ │ │ └── Toggle.tsx
│ │ ├── ToneChangerButton
│ │ │ ├── ToneChangerButton.md
│ │ │ ├── ToneChangerButton.spec.ts
│ │ │ └── ToneChangerButton.tsx
│ │ ├── ToneSwitch
│ │ │ ├── ToneSwitch.md
│ │ │ ├── ToneSwitch.module.scss
│ │ │ ├── ToneSwitch.spec.ts
│ │ │ ├── ToneSwitch.tsx
│ │ │ └── ToneSwitchNative.tsx
│ │ ├── Tooltip
│ │ │ ├── Tooltip.md
│ │ │ ├── Tooltip.module.scss
│ │ │ ├── Tooltip.spec.ts
│ │ │ ├── Tooltip.tsx
│ │ │ └── TooltipNative.tsx
│ │ ├── Tree
│ │ │ ├── testData.ts
│ │ │ ├── Tree-dynamic.spec.ts
│ │ │ ├── Tree-icons.spec.ts
│ │ │ ├── Tree.md
│ │ │ ├── Tree.spec.ts
│ │ │ ├── TreeComponent.module.scss
│ │ │ ├── TreeComponent.tsx
│ │ │ └── TreeNative.tsx
│ │ ├── TreeDisplay
│ │ │ ├── TreeDisplay.md
│ │ │ ├── TreeDisplay.module.scss
│ │ │ ├── TreeDisplay.tsx
│ │ │ └── TreeDisplayNative.tsx
│ │ ├── ValidationSummary
│ │ │ ├── ValidationSummary.module.scss
│ │ │ └── ValidationSummary.tsx
│ │ └── VisuallyHidden.tsx
│ ├── components-core
│ │ ├── abstractions
│ │ │ ├── ComponentRenderer.ts
│ │ │ ├── LoaderRenderer.ts
│ │ │ ├── standalone.ts
│ │ │ └── treeAbstractions.ts
│ │ ├── action
│ │ │ ├── actions.ts
│ │ │ ├── APICall.tsx
│ │ │ ├── FileDownloadAction.tsx
│ │ │ ├── FileUploadAction.tsx
│ │ │ ├── NavigateAction.tsx
│ │ │ └── TimedAction.tsx
│ │ ├── ApiBoundComponent.tsx
│ │ ├── appContext
│ │ │ ├── date-functions.ts
│ │ │ ├── math-function.ts
│ │ │ └── misc-utils.ts
│ │ ├── AppContext.tsx
│ │ ├── behaviors
│ │ │ ├── Behavior.tsx
│ │ │ └── CoreBehaviors.tsx
│ │ ├── component-hooks.ts
│ │ ├── ComponentDecorator.tsx
│ │ ├── ComponentViewer.tsx
│ │ ├── CompoundComponent.tsx
│ │ ├── constants.ts
│ │ ├── DebugViewProvider.tsx
│ │ ├── descriptorHelper.ts
│ │ ├── devtools
│ │ │ ├── InspectorDialog.module.scss
│ │ │ ├── InspectorDialog.tsx
│ │ │ └── InspectorDialogVisibilityContext.tsx
│ │ ├── EngineError.ts
│ │ ├── event-handlers.ts
│ │ ├── InspectorButton.module.scss
│ │ ├── InspectorContext.tsx
│ │ ├── interception
│ │ │ ├── abstractions.ts
│ │ │ ├── ApiInterceptor.ts
│ │ │ ├── ApiInterceptorProvider.tsx
│ │ │ ├── apiInterceptorWorker.ts
│ │ │ ├── Backend.ts
│ │ │ ├── Errors.ts
│ │ │ ├── IndexedDb.ts
│ │ │ ├── initMock.ts
│ │ │ ├── InMemoryDb.ts
│ │ │ ├── ReadonlyCollection.ts
│ │ │ └── useApiInterceptorContext.tsx
│ │ ├── loader
│ │ │ ├── ApiLoader.tsx
│ │ │ ├── DataLoader.tsx
│ │ │ ├── ExternalDataLoader.tsx
│ │ │ ├── Loader.tsx
│ │ │ ├── MockLoaderRenderer.tsx
│ │ │ └── PageableLoader.tsx
│ │ ├── LoaderComponent.tsx
│ │ ├── markup-check.ts
│ │ ├── parts.ts
│ │ ├── renderers.ts
│ │ ├── rendering
│ │ │ ├── AppContent.tsx
│ │ │ ├── AppRoot.tsx
│ │ │ ├── AppWrapper.tsx
│ │ │ ├── buildProxy.ts
│ │ │ ├── collectFnVarDeps.ts
│ │ │ ├── ComponentAdapter.tsx
│ │ │ ├── ComponentWrapper.tsx
│ │ │ ├── Container.tsx
│ │ │ ├── containers.ts
│ │ │ ├── ContainerWrapper.tsx
│ │ │ ├── ErrorBoundary.module.scss
│ │ │ ├── ErrorBoundary.tsx
│ │ │ ├── InvalidComponent.module.scss
│ │ │ ├── InvalidComponent.tsx
│ │ │ ├── nodeUtils.ts
│ │ │ ├── reducer.ts
│ │ │ ├── renderChild.tsx
│ │ │ ├── StandaloneComponent.tsx
│ │ │ ├── StateContainer.tsx
│ │ │ ├── UnknownComponent.module.scss
│ │ │ ├── UnknownComponent.tsx
│ │ │ └── valueExtractor.ts
│ │ ├── reportEngineError.ts
│ │ ├── RestApiProxy.ts
│ │ ├── script-runner
│ │ │ ├── asyncProxy.ts
│ │ │ ├── AttributeValueParser.ts
│ │ │ ├── bannedFunctions.ts
│ │ │ ├── BindingTreeEvaluationContext.ts
│ │ │ ├── eval-tree-async.ts
│ │ │ ├── eval-tree-common.ts
│ │ │ ├── eval-tree-sync.ts
│ │ │ ├── ParameterParser.ts
│ │ │ ├── process-statement-async.ts
│ │ │ ├── process-statement-common.ts
│ │ │ ├── process-statement-sync.ts
│ │ │ ├── ScriptingSourceTree.ts
│ │ │ ├── simplify-expression.ts
│ │ │ ├── statement-queue.ts
│ │ │ └── visitors.ts
│ │ ├── StandaloneApp.tsx
│ │ ├── StandaloneExtensionManager.ts
│ │ ├── TableOfContentsContext.tsx
│ │ ├── theming
│ │ │ ├── _themes.scss
│ │ │ ├── component-layout-resolver.ts
│ │ │ ├── extendThemeUtils.ts
│ │ │ ├── hvar.ts
│ │ │ ├── layout-resolver.ts
│ │ │ ├── parse-layout-props.ts
│ │ │ ├── StyleContext.tsx
│ │ │ ├── StyleRegistry.ts
│ │ │ ├── ThemeContext.tsx
│ │ │ ├── ThemeProvider.tsx
│ │ │ ├── themes
│ │ │ │ ├── base-utils.ts
│ │ │ │ ├── palette.ts
│ │ │ │ ├── root.ts
│ │ │ │ ├── solid.ts
│ │ │ │ ├── theme-colors.ts
│ │ │ │ └── xmlui.ts
│ │ │ ├── themeVars.module.scss
│ │ │ ├── themeVars.ts
│ │ │ ├── transformThemeVars.ts
│ │ │ └── utils.ts
│ │ ├── utils
│ │ │ ├── actionUtils.ts
│ │ │ ├── audio-utils.ts
│ │ │ ├── base64-utils.ts
│ │ │ ├── compound-utils.ts
│ │ │ ├── css-utils.ts
│ │ │ ├── DataLoaderQueryKeyGenerator.ts
│ │ │ ├── date-utils.ts
│ │ │ ├── extractParam.ts
│ │ │ ├── hooks.tsx
│ │ │ ├── LruCache.ts
│ │ │ ├── mergeProps.ts
│ │ │ ├── misc.ts
│ │ │ ├── request-params.ts
│ │ │ ├── statementUtils.ts
│ │ │ └── treeUtils.ts
│ │ └── xmlui-parser.ts
│ ├── index-standalone.ts
│ ├── index.scss
│ ├── index.ts
│ ├── language-server
│ │ ├── server-common.ts
│ │ ├── server-web-worker.ts
│ │ ├── server.ts
│ │ ├── services
│ │ │ ├── common
│ │ │ │ ├── docs-generation.ts
│ │ │ │ ├── lsp-utils.ts
│ │ │ │ ├── metadata-utils.ts
│ │ │ │ └── syntax-node-utilities.ts
│ │ │ ├── completion.ts
│ │ │ ├── diagnostic.ts
│ │ │ ├── format.ts
│ │ │ └── hover.ts
│ │ └── xmlui-metadata-generated.js
│ ├── logging
│ │ ├── LoggerContext.tsx
│ │ ├── LoggerInitializer.tsx
│ │ ├── LoggerService.ts
│ │ └── xmlui.ts
│ ├── logo.svg
│ ├── parsers
│ │ ├── common
│ │ │ ├── GenericToken.ts
│ │ │ ├── InputStream.ts
│ │ │ └── utils.ts
│ │ ├── scripting
│ │ │ ├── code-behind-collect.ts
│ │ │ ├── Lexer.ts
│ │ │ ├── modules.ts
│ │ │ ├── Parser.ts
│ │ │ ├── ParserError.ts
│ │ │ ├── ScriptingNodeTypes.ts
│ │ │ ├── TokenTrait.ts
│ │ │ ├── TokenType.ts
│ │ │ └── tree-visitor.ts
│ │ ├── style-parser
│ │ │ ├── errors.ts
│ │ │ ├── source-tree.ts
│ │ │ ├── StyleInputStream.ts
│ │ │ ├── StyleLexer.ts
│ │ │ ├── StyleParser.ts
│ │ │ └── tokens.ts
│ │ └── xmlui-parser
│ │ ├── CharacterCodes.ts
│ │ ├── diagnostics.ts
│ │ ├── fileExtensions.ts
│ │ ├── index.ts
│ │ ├── lint.ts
│ │ ├── parser.ts
│ │ ├── ParserError.ts
│ │ ├── scanner.ts
│ │ ├── syntax-kind.ts
│ │ ├── syntax-node.ts
│ │ ├── transform.ts
│ │ ├── utils.ts
│ │ ├── xmlui-serializer.ts
│ │ └── xmlui-tree.ts
│ ├── react-app-env.d.ts
│ ├── syntax
│ │ ├── monaco
│ │ │ ├── grammar.monacoLanguage.ts
│ │ │ ├── index.ts
│ │ │ ├── xmlui-dark.ts
│ │ │ ├── xmlui-light.ts
│ │ │ └── xmluiscript.monacoLanguage.ts
│ │ └── textMate
│ │ ├── index.ts
│ │ ├── xmlui-dark.json
│ │ ├── xmlui-light.json
│ │ ├── xmlui.json
│ │ └── xmlui.tmLanguage.json
│ ├── testing
│ │ ├── assertions.ts
│ │ ├── component-test-helpers.ts
│ │ ├── ComponentDrivers.ts
│ │ ├── drivers
│ │ │ ├── DateInputDriver.ts
│ │ │ ├── index.ts
│ │ │ ├── ModalDialogDriver.ts
│ │ │ ├── NumberBoxDriver.ts
│ │ │ ├── TextBoxDriver.ts
│ │ │ ├── TimeInputDriver.ts
│ │ │ ├── TimerDriver.ts
│ │ │ └── TreeDriver.ts
│ │ ├── fixtures.ts
│ │ ├── index.ts
│ │ ├── infrastructure
│ │ │ ├── index.html
│ │ │ ├── main.tsx
│ │ │ ├── public
│ │ │ │ ├── mockServiceWorker.js
│ │ │ │ ├── resources
│ │ │ │ │ ├── bell.svg
│ │ │ │ │ ├── box.svg
│ │ │ │ │ ├── doc.svg
│ │ │ │ │ ├── eye.svg
│ │ │ │ │ ├── flower-640x480.jpg
│ │ │ │ │ ├── sun.svg
│ │ │ │ │ ├── test-image-100x100.jpg
│ │ │ │ │ └── txt.svg
│ │ │ │ └── serve.json
│ │ │ └── TestBed.tsx
│ │ └── themed-app-test-helpers.ts
│ └── vite-env.d.ts
├── tests
│ ├── components
│ │ ├── CodeBlock
│ │ │ └── hightlight-code.test.ts
│ │ ├── playground-pattern.test.ts
│ │ └── Tree
│ │ └── Tree-states.test.ts
│ ├── components-core
│ │ ├── abstractions
│ │ │ └── treeAbstractions.test.ts
│ │ ├── container
│ │ │ └── buildProxy.test.ts
│ │ ├── interception
│ │ │ ├── orderBy.test.ts
│ │ │ ├── ReadOnlyCollection.test.ts
│ │ │ └── request-param-converter.test.ts
│ │ ├── scripts-runner
│ │ │ ├── AttributeValueParser.test.ts
│ │ │ ├── eval-tree-arrow-async.test.ts
│ │ │ ├── eval-tree-arrow.test.ts
│ │ │ ├── eval-tree-func-decl-async.test.ts
│ │ │ ├── eval-tree-func-decl.test.ts
│ │ │ ├── eval-tree-pre-post.test.ts
│ │ │ ├── eval-tree-regression.test.ts
│ │ │ ├── eval-tree.test.ts
│ │ │ ├── function-proxy.test.ts
│ │ │ ├── parser-regression.test.ts
│ │ │ ├── process-event.test.ts
│ │ │ ├── process-function.test.ts
│ │ │ ├── process-implicit-context.test.ts
│ │ │ ├── process-statement-asgn.test.ts
│ │ │ ├── process-statement-destruct.test.ts
│ │ │ ├── process-statement-regs.test.ts
│ │ │ ├── process-statement-sync.test.ts
│ │ │ ├── process-statement.test.ts
│ │ │ ├── process-switch-sync.test.ts
│ │ │ ├── process-switch.test.ts
│ │ │ ├── process-try-sync.test.ts
│ │ │ ├── process-try.test.ts
│ │ │ └── test-helpers.ts
│ │ ├── test-metadata-handler.ts
│ │ ├── theming
│ │ │ ├── border-segments.test.ts
│ │ │ ├── component-layout.resolver.test.ts
│ │ │ ├── layout-property-parser.test.ts
│ │ │ ├── layout-resolver.test.ts
│ │ │ ├── layout-resolver2.test.ts
│ │ │ ├── layout-vp-override.test.ts
│ │ │ └── padding-segments.test.ts
│ │ └── utils
│ │ ├── date-utils.test.ts
│ │ ├── format-human-elapsed-time.test.ts
│ │ └── LruCache.test.ts
│ ├── language-server
│ │ ├── completion.test.ts
│ │ ├── format.test.ts
│ │ ├── hover.test.ts
│ │ └── mockData.ts
│ └── parsers
│ ├── common
│ │ └── input-stream.test.ts
│ ├── markdown
│ │ └── parse-binding-expression.test.ts
│ ├── parameter-parser.test.ts
│ ├── paremeter-parser.test.ts
│ ├── scripting
│ │ ├── eval-tree-arrow.test.ts
│ │ ├── eval-tree-pre-post.test.ts
│ │ ├── eval-tree.test.ts
│ │ ├── function-proxy.test.ts
│ │ ├── lexer-literals.test.ts
│ │ ├── lexer-misc.test.ts
│ │ ├── module-parse.test.ts
│ │ ├── parser-arrow.test.ts
│ │ ├── parser-assignments.test.ts
│ │ ├── parser-binary.test.ts
│ │ ├── parser-destructuring.test.ts
│ │ ├── parser-errors.test.ts
│ │ ├── parser-expressions.test.ts
│ │ ├── parser-function.test.ts
│ │ ├── parser-literals.test.ts
│ │ ├── parser-primary.test.ts
│ │ ├── parser-regex.test.ts
│ │ ├── parser-statements.test.ts
│ │ ├── parser-unary.test.ts
│ │ ├── process-event.test.ts
│ │ ├── process-implicit-context.test.ts
│ │ ├── process-statement-asgn.test.ts
│ │ ├── process-statement-destruct.test.ts
│ │ ├── process-statement-regs.test.ts
│ │ ├── process-statement-sync.test.ts
│ │ ├── process-statement.test.ts
│ │ ├── process-switch-sync.test.ts
│ │ ├── process-switch.test.ts
│ │ ├── process-try-sync.test.ts
│ │ ├── process-try.test.ts
│ │ ├── simplify-expression.test.ts
│ │ ├── statement-hooks.test.ts
│ │ └── test-helpers.ts
│ ├── style-parser
│ │ ├── generateHvarChain.test.ts
│ │ ├── parseHVar.test.ts
│ │ ├── parser.test.ts
│ │ └── tokens.test.ts
│ └── xmlui
│ ├── lint.test.ts
│ ├── parser.test.ts
│ ├── scanner.test.ts
│ ├── transform.attr.test.ts
│ ├── transform.circular.test.ts
│ ├── transform.element.test.ts
│ ├── transform.errors.test.ts
│ ├── transform.escape.test.ts
│ ├── transform.regression.test.ts
│ ├── transform.script.test.ts
│ ├── transform.test.ts
│ └── xmlui.ts
├── tests-e2e
│ ├── api-bound-component-regression.spec.ts
│ ├── api-call-as-extracted-component.spec.ts
│ ├── assign-to-object-or-array-regression.spec.ts
│ ├── binding-regression.spec.ts
│ ├── children-as-template-context-vars.spec.ts
│ ├── compound-component.spec.ts
│ ├── context-vars-regression.spec.ts
│ ├── data-bindings.spec.ts
│ ├── datasource-and-api-usage-in-var.spec.ts
│ ├── datasource-direct-binding.spec.ts
│ ├── datasource-onLoaded-regression.spec.ts
│ ├── modify-array-item-regression.spec.ts
│ ├── namespaces.spec.ts
│ ├── push-to-array-regression.spec.ts
│ ├── screen-breakpoints.spec.ts
│ ├── scripting.spec.ts
│ ├── state-scope-in-pages.spec.ts
│ └── state-var-scopes.spec.ts
├── tsconfig.bin.json
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
└── vitest.config.ts
```
# Files
--------------------------------------------------------------------------------
/xmlui/src/components-core/rendering/valueExtractor.ts:
--------------------------------------------------------------------------------
```typescript
import type { MutableRefObject } from "react";
import memoizeOne from "memoize-one";
import { isPlainObject, isString } from "lodash-es";
import type { AppContextObject } from "../../abstractions/AppContextDefs";
import type { MemoedVars } from "../abstractions/ComponentRenderer";
import { parseParameterString } from "../script-runner/ParameterParser";
import type { ComponentApi, ContainerState } from "../rendering/ContainerWrapper";
import { isPrimitive, pickFromObject, shallowCompare } from "../utils/misc";
import { collectVariableDependencies } from "../script-runner/visitors";
import { extractParam } from "../utils/extractParam";
import { StyleParser, toCssVar } from "../../parsers/style-parser/StyleParser";
import type { ValueExtractor } from "../../abstractions/RendererDefs";
function parseStringArray(input: string): string[] {
const trimmedInput = input.trim();
if (trimmedInput.startsWith("[") && trimmedInput.endsWith("]")) {
const content = trimmedInput.slice(1, -1);
const items = content.split(",").map((item) => item.trim());
return items.map((item) => item.replace(/^['"]|['"]$/g, ""));
} else {
throw new Error("Invalid array format");
}
}
function collectParams(expression: any) {
const params = [];
if (typeof expression === "string") {
params.push(...parseParameterString(expression));
} else if (Array.isArray(expression)) {
expression.forEach((exp) => {
params.push(...collectParams(exp));
});
} else if (isPlainObject(expression)) {
Object.entries(expression).forEach(([key, value]) => {
params.push(...collectParams(value));
});
}
return params;
}
export function asOptionalBoolean(value: any, defValue?: boolean | undefined) {
if (value === undefined || value === null) return defValue;
// Empty array returns false
if (Array.isArray(value) && value.length === 0) {
return false;
}
// Empty object returns false
if (typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length === 0) {
return false;
}
if (typeof value === "number") {
return value !== 0;
}
if (typeof value === "string") {
value = value.trim().toLowerCase();
if (value === "") {
return false;
}
if (value === "true") {
return true;
}
if (value === "false") {
return false;
}
if (value !== "") {
return true;
}
}
if (typeof value === "boolean") {
return value;
}
return true;
}
// This function represents the extractor function we pass to extractValue
export function createValueExtractor(
state: ContainerState,
appContext: AppContextObject | undefined,
referenceTrackedApi: Record<string, ComponentApi>,
memoedVarsRef: MutableRefObject<MemoedVars>
): ValueExtractor {
// --- Extract the parameter and retrieve as is is
const extractor = (expression?: any, strict?: boolean): any => {
if (!expression) {
return expression;
}
if (isPrimitive(expression) && !isString(expression)) {
return expression;
}
let expressionString = expression;
if (typeof expression !== "string") {
if (strict) {
return expression;
}
expressionString = JSON.stringify(expression);
}
if (!memoedVarsRef.current.has(expressionString)) {
const params = collectParams(expression);
memoedVarsRef.current.set(expressionString, {
getDependencies: memoizeOne((_expressionString, referenceTrackedApi) => {
let ret = new Set<string>();
params.forEach((param) => {
if (param.type === "expression") {
ret = new Set([...ret, ...collectVariableDependencies(param.value, referenceTrackedApi)]);
}
});
return Array.from(ret);
}),
obtainValue: memoizeOne(
(expression, state, appContext, strict, deps, appContextDeps) => {
// console.log("COMP, BUST, obtain value called with", expression, state, appContext, deps, appContextDeps);
return extractParam(state, expression, appContext, strict);
},
(
[_newExpression, _newState, _newAppContext, _newStrict, newDeps, newAppContextDeps],
[_lastExpression, _lastState, _lastAppContext, _lastStrict, lastDeps, lastAppContextDeps]
) => {
return shallowCompare(newDeps, lastDeps) && shallowCompare(newAppContextDeps, lastAppContextDeps);
}
),
});
}
const expressionDependencies = memoedVarsRef.current
.get(expressionString)!
.getDependencies(expressionString, referenceTrackedApi);
const depValues = pickFromObject(state, expressionDependencies);
const appContextDepValues = pickFromObject(appContext, expressionDependencies);
// console.log("COMP, obtain value called with", depValues, appContextDepValues, expressionDependencies);
return memoedVarsRef.current.get(expressionString)!.obtainValue!(
expression,
state,
appContext,
strict,
depValues,
appContextDepValues
);
};
// --- Extract a string value
extractor.asString = (expression?: any) => {
return extractor(expression)?.toString() ?? "";
};
// --- Extract an optional string value
extractor.asOptionalString = <T extends string>(expression?: any, defValue?: string) => {
const value = extractor(expression)?.toString();
if (value === undefined || value === null) return defValue;
return value as T;
};
extractor.asDisplayText = (expression?: any) => {
let text = extractor(expression)?.toString();
if (text) {
let replaced = "";
let spaceFound = false;
for (const char of text) {
if (char === " " || char === "\t") {
replaced += spaceFound ? "\xa0" : " ";
spaceFound = true;
} else {
replaced += char;
spaceFound = char === "\xa0";
}
}
text = replaced;
}
return text;
};
// ---Extract an array of strings
extractor.asOptionalStringArray = (expression?: any) => {
const value = extractor(expression);
if (value === undefined || value === null) return [];
if (typeof value === "string" && value.trim() !== "") {
//console.log(parseStringArray(value));
return parseStringArray(value);
}
if (Array.isArray(value)) {
return value.map((item) => item.toString());
}
throw new Error(`An array of strings expected but ${typeof value} received.`);
};
// --- Extract a numeric value
extractor.asNumber = (expression?: any) => {
const value = extractor(expression);
if (typeof value === "number") return value;
throw new Error(`A numeric value expected but ${typeof value} received.`);
};
// --- Extract an optional numeric value
extractor.asOptionalNumber = (expression?: any, defValue?: number) => {
const value = extractor(expression);
if (value === undefined || value === null) return defValue;
if (typeof value === "string" && !isNaN(parseFloat(value))) {
return Number(value);
}
if (typeof value === "number") return value;
throw new Error(`A numeric value expected but ${typeof value} received.`);
};
// --- Extract a Boolean value
extractor.asBoolean = (expression?: any) => {
return !!extractor(expression);
};
// --- Extract an optional Boolean value
extractor.asOptionalBoolean = (expression?: any, defValue?: boolean) => {
return asOptionalBoolean(extractor(expression), defValue);
};
// --- Extract an optional size value
extractor.asSize = (expression?: any) => {
const value = extractor(expression);
if (value === undefined || value === null) return undefined;
try {
const parser = new StyleParser(value);
const size = parser.parseSize();
if (size?.themeId) {
return toCssVar(size.themeId);
}
return size ? `${size.value}${size.unit ?? "px"}` : undefined;
} catch {
return undefined;
}
};
// --- Done.
return extractor as ValueExtractor;
}
```
--------------------------------------------------------------------------------
/xmlui/src/components/Carousel/Carousel.md:
--------------------------------------------------------------------------------
```markdown
%-DESC-START
`Carousel` displays a slideshow by cycling through elements (images, text, or custom slides) in a carousel format. It provides an interactive way to present multiple content items in a single interface area with smooth transitions and navigation controls.
**Key features:**
- **Multiple orientations**: Supports both horizontal and vertical scrolling
- **Navigation controls**: Built-in previous/next buttons with customizable icons
- **Indicators**: Visual dots showing current position and allowing direct navigation
- **Autoplay functionality**: Automatic slide progression with configurable intervals
- **Loop support**: Continuous cycling through slides
- **Keyboard navigation**: Arrow key support for accessibility
- **Exposed methods**: Programmatic control via `scrollTo()`, `scrollNext()`, `scrollPrev()`, `canScrollNext()`, `canScrollPrev()`
%-DESC-END
%-PROP-START autoplay
This property indicates whether the carousel automatically scrolls through slides.
```xmlui-pg copy display name="Example: autoplay"
<App>
<Carousel autoplay autoplayInterval="2000" height="120px" loop>
<CarouselItem>
<Card title="Slide 1" />
</CarouselItem>
<CarouselItem>
<Card title="Slide 2" />
</CarouselItem>
<CarouselItem>
<Card title="Slide 3" />
</CarouselItem>
</Carousel>
</App>
```
%-PROP-END
%-PROP-START controls
This property indicates whether the carousel displays navigation controls (previous/next buttons).
```xmlui-pg copy display name="Example: controls"
<App>
<Carousel controls="true" height="120px">
<CarouselItem>Slide 1 with controls</CarouselItem>
<CarouselItem>Slide 2 with controls</CarouselItem>
</Carousel>
</App>
```
%-PROP-END
%-PROP-START indicators
This property indicates whether the carousel displays position indicators.
```xmlui-pg copy display name="Example: indicators"
<App>
<Carousel indicators="true" height="120px">
<CarouselItem>Slide 1 with indicators</CarouselItem>
<CarouselItem>Slide 2 with indicators</CarouselItem>
<CarouselItem>Slide 3 with indicators</CarouselItem>
</Carousel>
</App>
```
%-PROP-END
%-PROP-START loop
This property indicates whether the carousel loops continuously from the last slide back to the first.
```xmlui-pg copy display name="Example: loop"
<App>
<Carousel loop="true" height="120px">
<CarouselItem>
<Card title="First Slide" />
</CarouselItem>
<CarouselItem>
<Card title="Second Slide" />
</CarouselItem>
<CarouselItem>
<Card title="Third Slide" />
</CarouselItem>
</Carousel>
</App>
```
%-PROP-END
%-PROP-START orientation
This property indicates the orientation of the carousel. The `horizontal` value indicates that the carousel moves horizontally, and the `vertical` value indicates that the carousel moves vertically.
Available values:
| Value | Description |
| --- | --- |
| `horizontal` | The carousel moves horizontally **(default)** |
| `vertical` | The carousel moves vertically |
```xmlui-pg copy display name="Example: orientation"
<App>
<Carousel orientation="horizontal" height="120px">
<CarouselItem>Horizontal Slide 1</CarouselItem>
<CarouselItem>Horizontal Slide 2</CarouselItem>
</Carousel>
</App>
```
%-PROP-END
%-PROP-START startIndex
This property indicates the index of the first slide to display when the carousel initializes.
```xmlui-pg copy display name="Example: startIndex"
<App>
<Carousel startIndex="2" height="120px">
<CarouselItem>Slide 1</CarouselItem>
<CarouselItem>Slide 2</CarouselItem>
<CarouselItem>Slide 3 (starts here)</CarouselItem>
<CarouselItem>Slide 4</CarouselItem>
</Carousel>
</App>
```
%-PROP-END
%-PROP-START nextIcon
This property specifies the icon to display for the next control button.
```xmlui-pg copy display name="Example: custom icons"
<App>
<Carousel nextIcon="chevronright" prevIcon="chevronleft" height="120px">
<CarouselItem>Slide 1</CarouselItem>
<CarouselItem>Slide 2</CarouselItem>
<CarouselItem>Slide 3</CarouselItem>
</Carousel>
</App>
```
%-PROP-END
%-PROP-START prevIcon
This property specifies the icon to display for the previous control button.
%-PROP-END
%-PROP-START autoplayInterval
This property specifies the interval between autoplay transitions in milliseconds.
%-PROP-END
%-PROP-START stopAutoplayOnInteraction
This property indicates whether autoplay stops when the user interacts with the carousel (clicking controls, indicators, or using keyboard navigation).
%-PROP-END
%-PROP-START transitionDuration
This property indicates the duration of the transition between slides in milliseconds.
%-PROP-END
%-EVENT-START displayDidChange
This event is triggered when the active slide changes.
The event handler receives the active slide index as a parameter.
```xmlui-pg copy display name="Example: displayDidChange"
<App var.currentSlide="0">
<Carousel onDisplayDidChange="(index) => currentSlide = index" height="120px">
<CarouselItem>Slide 1</CarouselItem>
<CarouselItem>Slide 2</CarouselItem>
<CarouselItem>Slide 3</CarouselItem>
</Carousel>
<Text>Current slide: {currentSlide + 1}</Text>
</App>
```
%-EVENT-END
%-STYLE-START
### Theme Variables
| Variable | Default Value (Light) | Default Value (Dark) |
| --- | --- | --- |
| [backgroundColor](../styles-and-themes/common-units/#color)-control-Carousel | $color-primary | $color-primary |
| [backgroundColor](../styles-and-themes/common-units/#color)-control-active-Carousel | $color-primary | $color-primary |
| [backgroundColor](../styles-and-themes/common-units/#color)-control-disabled-Carousel | $color-surface-200 | $color-surface-200 |
| [backgroundColor](../styles-and-themes/common-units/#color)-control-hover-Carousel | $color-primary | $color-primary |
| [backgroundColor](../styles-and-themes/common-units/#color)-indicator-Carousel | $color-surface-200 | $color-surface-200 |
| [backgroundColor](../styles-and-themes/common-units/#color)-indicator-active-Carousel | $color-primary | $color-primary |
| [backgroundColor](../styles-and-themes/common-units/#color)-indicator-hover-Carousel | $color-surface-200 | $color-surface-200 |
| [borderRadius](../styles-and-themes/common-units/#border-rounding)-control-Carousel | 50% | 50% |
| [height](../styles-and-themes/common-units/#size)-Carousel | 100% | 100% |
| [height](../styles-and-themes/common-units/#size)-control-Carousel | 36px | 36px |
| [height](../styles-and-themes/common-units/#size)-indicator-Carousel | 6px | 6px |
| [textColor](../styles-and-themes/common-units/#color)-control-Carousel | $textColor | $textColor |
| [textColor](../styles-and-themes/common-units/#color)-control-active-Carousel | $color-primary | $color-primary |
| [textColor](../styles-and-themes/common-units/#color)-control-disabled-Carousel | $textColor-disabled | $textColor-disabled |
| [textColor](../styles-and-themes/common-units/#color)-control-hover-Carousel | $textColor | $textColor |
| [textColor](../styles-and-themes/common-units/#color)-indicator-Carousel | $color-primary | $color-primary |
| [textColor](../styles-and-themes/common-units/#color)-indicator-active-Carousel | $color-primary | $color-primary |
| [textColor](../styles-and-themes/common-units/#color)-indicator-hover-Carousel | $color-primary | $color-primary |
| [width](../styles-and-themes/common-units/#size)-Carousel | 100% | 100% |
| [width](../styles-and-themes/common-units/#size)-control-Carousel | 36px | 36px |
| [width](../styles-and-themes/common-units/#size)-indicator-Carousel | 25px | 25px |
### Navigation Controls
The carousel provides built-in navigation controls that can be customized through theme variables:
```xmlui-pg copy name="Example: Custom control styling"
<App>
<Theme
backgroundColor-control-Carousel="red"
textColor-control-Carousel="white"
borderRadius-control-Carousel="4px">
<Carousel height="120px">
<CarouselItem>Slide 1</CarouselItem>
<CarouselItem>Slide 2</CarouselItem>
<CarouselItem>Slide 3</CarouselItem>
</Carousel>
</Theme>
</App>
```
%-STYLE-END
```
--------------------------------------------------------------------------------
/xmlui/src/components/_conventions.md:
--------------------------------------------------------------------------------
```markdown
# XMLUI Component Conventions
This document outlines the conventions and patterns used in the XMLUI component system, based on analysis of components like Avatar, Button, and Card.
## Component Structure
### Dual-File Pattern
Components are structured using a dual-file pattern:
- **Native Component** (`*Native.tsx`)
- Pure React implementation
- Uses `forwardRef` pattern
- Contains the actual rendering logic
- Defines prop types and default props
- **Renderer Component** (`.tsx`)
- Provides metadata using `createMetadata`
- Registers component with `createComponentRenderer`
- Defines theme variables
- Maps XMLUI props to native component props
Example relationship:
```typescript
// Avatar.tsx (Renderer)
export const avatarComponentRenderer = createComponentRenderer(
"Avatar",
AvatarMd,
({ node, extractValue, lookupEventHandler, layoutCss, extractResourceUrl }) => {
return (
<Avatar
size={node.props?.size}
url={extractResourceUrl(node.props.url)}
name={extractValue(node.props.name)}
style={layoutCss}
onClick={lookupEventHandler("click")}
/>
);
},
);
// AvatarNative.tsx (Implementation)
export const Avatar = forwardRef(function Avatar(
{ size = defaultProps.size, url, name, style, onClick, ...rest }: Props,
ref: Ref<any>,
) {
// Implementation details...
});
```
### Single-File Pattern Variation
Some XMLUI components are implemented using a single-file pattern:
- **Combined Component** (`.tsx`)
- Contains both the React implementation and XMLUI renderer in a single file
- Defines metadata using `createMetadata`
- Registers component with `createComponentRenderer`
- Does not separate the implementation into a `*Native.tsx` file
- Often used for simpler components or those that primarily compose other native components
Example: `ToneChangerButton.tsx` combines the implementation and renderer in one file:
```typescript
export function ToneChangerButton({
lightToDarkIcon = defaultProps.lightToDarkIcon,
darkToLightIcon = defaultProps.darkToLightIcon,
}) {
// Implementation...
}
export const toneChangerButtonComponentRenderer = createComponentRenderer(
COMP,
ToneChangerButtonMd,
({ node, extractValue }) => {
return (
<ToneChangerButton
lightToDarkIcon={extractValue.asOptionalString(node.props.lightToDarkIcon)}
darkToLightIcon={extractValue.asOptionalString(node.props.darkToLightIcon)}
/>
);
},
);
```
This variation is still a fully functional XMLUI component and is registered in the ComponentProvider, making it available in XMLUI markup.
## Component API and State
### API Registration
Components expose methods through a standardized API registration pattern:
```typescript
useEffect(() => {
registerComponentApi?.({
setValue,
focus,
});
}, [registerComponentApi, setValue, focus]);
```
### State Synchronization
Components keep their internal state synchronized with XMLUI:
```typescript
// Update component state and notify XMLUI
const setValue = useEvent((newValue) => {
setInternalState(newValue);
updateState?.({ value: newValue });
});
// Sync state on initial render or changes
useEffect(() => {
updateState?.({ value: currentState });
}, [updateState, currentState]);
```
## Event Handling
Events are:
1. Declared in component metadata
2. Implemented in the native component
3. Connected via `lookupEventHandler` in the renderer
Common events:
- `click` / `onClick`
- `didChange` (value changes)
- `gotFocus` / `lostFocus`
- Component-specific events (e.g., `onReset`)
## Styling and Theming
### SCSS Module Pattern
- Each component has its own SCSS module (e.g., `Avatar.module.scss`)
- Theme variables follow naming convention: `propertyName-ComponentName`
- Variables are exported and parsed with `parseScssVar`
- CSS classes are composed with the `classnames` library
### Theme Variables
Theme variables are defined in the component metadata:
```typescript
defaultThemeVars: {
[`borderRadius-${COMP}`]: "4px",
[`boxShadow-${COMP}`]: "inset 0 0 0 1px rgba(4,32,69,0.1)",
[`textColor-${COMP}`]: "$textColor-secondary",
// ...other variables
}
```
Components can be styled using the theme system or through direct props.
## Component Registration
Components are registered with the `ComponentRegistry` during framework initialization:
```typescript
if (process.env.VITE_USED_COMPONENTS_Avatar !== "false") {
this.registerCoreComponent(avatarComponentRenderer);
}
```
This allows conditional inclusion of components and provides centralized component management.
## Testing
Components use a driver pattern for testing:
```typescript
// Each component has a dedicated driver class
export class AvatarDriver extends ComponentDriver {
// Driver-specific methods
}
// Used in tests
test("can render 2 initials", async ({ initTestBed, createAvatarDriver }) => {
await initTestBed(`<Avatar name="Tim Smith"/>`);
await expect((await createAvatarDriver()).component).toContainText("TS");
});
```
## Reusable Component System
XMLUI supports user-defined components using the `<Component>` tag:
```xml
<Component name="InfoCard">
<Card width="{$props.width}" borderRadius="8px" boxShadow="$boxShadow-spread">
<Text>{$props.title}</Text>
<Text fontWeight="$fontWeight-extra-bold" fontSize="larger">
{ $props.currency === 'true' ? '$' + $props.value : $props.value }
</Text>
</Card>
</Component>
```
Components can access properties via `$props` context variable, and expose methods through the API registration mechanism.
## Layout Integration
Components behave differently depending on their parent layout containers:
- In `Stack` layouts, component layout properties might be ignored
- In `FlowLayout`, components get wrapped in container elements
- Explicit layout containers within components help control arrangement
## Native-Only React Components
The following components in the XMLUI codebase are implemented as native React components only. These components don't have corresponding XMLUI renderers registered in the ComponentProvider and are not directly available in XMLUI markup. Instead, they serve as internal implementation details, UI utilities, or components used by other XMLUI components.
- `InspectButton`: A button component used for turning inspection mode on/off in XMLUI development environment
- `ProfileMenu`: A profile menu component used within other components but not exposed directly to XMLUI
- `Toggle`: A toggle component that provides base functionality that may be used by other components
- `VisuallyHidden`: A utility component that wraps Radix UI's VisuallyHidden for accessibility
- `SlotItem`: An internal component for implementing the slot mechanism
- `IconProvider`: Provides icon context for the icon system
- `IconRegistryContext`: Manages icon registry context
### Component Structure Observations
1. **Pure Native Components** tend to:
- Be located in a single file (no paired renderer)
- Have a `.tsx` extension (sometimes with a `.module.scss` file)
- Not include `createComponentRenderer` or `createMetadata` calls
- Often be used internally by other XMLUI components
2. **Helper Components**:
- Typically simple with a focused utility purpose
- May be wrappers around third-party components
- Often have minimal props and internal state
These native-only components demonstrate the architectural separation in XMLUI, where not every React component needs to be exposed to the XMLUI markup language. This separation enables internal implementation flexibility while maintaining a clean API surface for XMLUI users.
## Best Practices
1. Keep native component implementations pure and focused
2. Define clear metadata and documentation for components
3. Support proper theming through theme variables
4. Implement consistent API and event patterns
5. Ensure proper testing with component drivers
6. Support accessibility through ARIA attributes and keyboard navigation
7. Ensure proper reference forwarding through `forwardRef`
8. Provide sensible defaults for all props
```
--------------------------------------------------------------------------------
/blog/public/mockServiceWorker.js:
--------------------------------------------------------------------------------
```javascript
/* eslint-disable */
/* tslint:disable */
/**
* Mock Service Worker.
* @see https://github.com/mswjs/msw
* - Please do NOT modify this file.
* - Please do NOT serve this file on production.
*/
const PACKAGE_VERSION = '2.8.4'
const INTEGRITY_CHECKSUM = '00729d72e3b82faf54ca8b9621dbb96f'
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
const activeClientIds = new Set()
self.addEventListener('install', function () {
self.skipWaiting()
})
self.addEventListener('activate', function (event) {
event.waitUntil(self.clients.claim())
})
self.addEventListener('message', async function (event) {
const clientId = event.source.id
if (!clientId || !self.clients) {
return
}
const client = await self.clients.get(clientId)
if (!client) {
return
}
const allClients = await self.clients.matchAll({
type: 'window',
})
switch (event.data) {
case 'KEEPALIVE_REQUEST': {
sendToClient(client, {
type: 'KEEPALIVE_RESPONSE',
})
break
}
case 'INTEGRITY_CHECK_REQUEST': {
sendToClient(client, {
type: 'INTEGRITY_CHECK_RESPONSE',
payload: {
packageVersion: PACKAGE_VERSION,
checksum: INTEGRITY_CHECKSUM,
},
})
break
}
case 'MOCK_ACTIVATE': {
activeClientIds.add(clientId)
sendToClient(client, {
type: 'MOCKING_ENABLED',
payload: {
client: {
id: client.id,
frameType: client.frameType,
},
},
})
break
}
case 'MOCK_DEACTIVATE': {
activeClientIds.delete(clientId)
break
}
case 'CLIENT_CLOSED': {
activeClientIds.delete(clientId)
const remainingClients = allClients.filter((client) => {
return client.id !== clientId
})
// Unregister itself when there are no more clients
if (remainingClients.length === 0) {
self.registration.unregister()
}
break
}
}
})
self.addEventListener('fetch', function (event) {
const { request } = event
// Bypass navigation requests.
if (request.mode === 'navigate') {
return
}
// Opening the DevTools triggers the "only-if-cached" request
// that cannot be handled by the worker. Bypass such requests.
if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') {
return
}
// Bypass all requests when there are no active clients.
// Prevents the self-unregistered worked from handling requests
// after it's been deleted (still remains active until the next reload).
if (activeClientIds.size === 0) {
return
}
// Generate unique request ID.
const requestId = crypto.randomUUID()
event.respondWith(handleRequest(event, requestId))
})
async function handleRequest(event, requestId) {
const client = await resolveMainClient(event)
const response = await getResponse(event, client, requestId)
// Send back the response clone for the "response:*" life-cycle events.
// Ensure MSW is active and ready to handle the message, otherwise
// this message will pend indefinitely.
if (client && activeClientIds.has(client.id)) {
;(async function () {
const responseClone = response.clone()
sendToClient(
client,
{
type: 'RESPONSE',
payload: {
requestId,
isMockedResponse: IS_MOCKED_RESPONSE in response,
type: responseClone.type,
status: responseClone.status,
statusText: responseClone.statusText,
body: responseClone.body,
headers: Object.fromEntries(responseClone.headers.entries()),
},
},
[responseClone.body],
)
})()
}
return response
}
// Resolve the main client for the given event.
// Client that issues a request doesn't necessarily equal the client
// that registered the worker. It's with the latter the worker should
// communicate with during the response resolving phase.
async function resolveMainClient(event) {
const client = await self.clients.get(event.clientId)
if (activeClientIds.has(event.clientId)) {
return client
}
if (client?.frameType === 'top-level') {
return client
}
const allClients = await self.clients.matchAll({
type: 'window',
})
return allClients
.filter((client) => {
// Get only those clients that are currently visible.
return client.visibilityState === 'visible'
})
.find((client) => {
// Find the client ID that's recorded in the
// set of clients that have registered the worker.
return activeClientIds.has(client.id)
})
}
async function getResponse(event, client, requestId) {
const { request } = event
// Clone the request because it might've been already used
// (i.e. its body has been read and sent to the client).
const requestClone = request.clone()
function passthrough() {
// Cast the request headers to a new Headers instance
// so the headers can be manipulated with.
const headers = new Headers(requestClone.headers)
// Remove the "accept" header value that marked this request as passthrough.
// This prevents request alteration and also keeps it compliant with the
// user-defined CORS policies.
const acceptHeader = headers.get('accept')
if (acceptHeader) {
const values = acceptHeader.split(',').map((value) => value.trim())
const filteredValues = values.filter(
(value) => value !== 'msw/passthrough',
)
if (filteredValues.length > 0) {
headers.set('accept', filteredValues.join(', '))
} else {
headers.delete('accept')
}
}
return fetch(requestClone, { headers })
}
// Bypass mocking when the client is not active.
if (!client) {
return passthrough()
}
// Bypass initial page load requests (i.e. static assets).
// The absence of the immediate/parent client in the map of the active clients
// means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
// and is not ready to handle requests.
if (!activeClientIds.has(client.id)) {
return passthrough()
}
// Notify the client that a request has been intercepted.
const requestBuffer = await request.arrayBuffer()
const clientMessage = await sendToClient(
client,
{
type: 'REQUEST',
payload: {
id: requestId,
url: request.url,
mode: request.mode,
method: request.method,
headers: Object.fromEntries(request.headers.entries()),
cache: request.cache,
credentials: request.credentials,
destination: request.destination,
integrity: request.integrity,
redirect: request.redirect,
referrer: request.referrer,
referrerPolicy: request.referrerPolicy,
body: requestBuffer,
keepalive: request.keepalive,
},
},
[requestBuffer],
)
switch (clientMessage.type) {
case 'MOCK_RESPONSE': {
return respondWithMock(clientMessage.data)
}
case 'PASSTHROUGH': {
return passthrough()
}
}
return passthrough()
}
function sendToClient(client, message, transferrables = []) {
return new Promise((resolve, reject) => {
const channel = new MessageChannel()
channel.port1.onmessage = (event) => {
if (event.data && event.data.error) {
return reject(event.data.error)
}
resolve(event.data)
}
client.postMessage(
message,
[channel.port2].concat(transferrables.filter(Boolean)),
)
})
}
async function respondWithMock(response) {
// Setting response status code to 0 is a no-op.
// However, when responding with a "Response.error()", the produced Response
// instance will have status code set to 0. Since it's not possible to create
// a Response instance with status code 0, handle that use-case separately.
if (response.status === 0) {
return Response.error()
}
const mockedResponse = new Response(response.body, response)
Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, {
value: true,
enumerable: true,
})
return mockedResponse
}
```
--------------------------------------------------------------------------------
/docs/public/mockServiceWorker.js:
--------------------------------------------------------------------------------
```javascript
/* eslint-disable */
/* tslint:disable */
/**
* Mock Service Worker.
* @see https://github.com/mswjs/msw
* - Please do NOT modify this file.
* - Please do NOT serve this file on production.
*/
const PACKAGE_VERSION = '2.8.4'
const INTEGRITY_CHECKSUM = '00729d72e3b82faf54ca8b9621dbb96f'
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
const activeClientIds = new Set()
self.addEventListener('install', function () {
self.skipWaiting()
})
self.addEventListener('activate', function (event) {
event.waitUntil(self.clients.claim())
})
self.addEventListener('message', async function (event) {
const clientId = event.source.id
if (!clientId || !self.clients) {
return
}
const client = await self.clients.get(clientId)
if (!client) {
return
}
const allClients = await self.clients.matchAll({
type: 'window',
})
switch (event.data) {
case 'KEEPALIVE_REQUEST': {
sendToClient(client, {
type: 'KEEPALIVE_RESPONSE',
})
break
}
case 'INTEGRITY_CHECK_REQUEST': {
sendToClient(client, {
type: 'INTEGRITY_CHECK_RESPONSE',
payload: {
packageVersion: PACKAGE_VERSION,
checksum: INTEGRITY_CHECKSUM,
},
})
break
}
case 'MOCK_ACTIVATE': {
activeClientIds.add(clientId)
sendToClient(client, {
type: 'MOCKING_ENABLED',
payload: {
client: {
id: client.id,
frameType: client.frameType,
},
},
})
break
}
case 'MOCK_DEACTIVATE': {
activeClientIds.delete(clientId)
break
}
case 'CLIENT_CLOSED': {
activeClientIds.delete(clientId)
const remainingClients = allClients.filter((client) => {
return client.id !== clientId
})
// Unregister itself when there are no more clients
if (remainingClients.length === 0) {
self.registration.unregister()
}
break
}
}
})
self.addEventListener('fetch', function (event) {
const { request } = event
// Bypass navigation requests.
if (request.mode === 'navigate') {
return
}
// Opening the DevTools triggers the "only-if-cached" request
// that cannot be handled by the worker. Bypass such requests.
if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') {
return
}
// Bypass all requests when there are no active clients.
// Prevents the self-unregistered worked from handling requests
// after it's been deleted (still remains active until the next reload).
if (activeClientIds.size === 0) {
return
}
// Generate unique request ID.
const requestId = crypto.randomUUID()
event.respondWith(handleRequest(event, requestId))
})
async function handleRequest(event, requestId) {
const client = await resolveMainClient(event)
const response = await getResponse(event, client, requestId)
// Send back the response clone for the "response:*" life-cycle events.
// Ensure MSW is active and ready to handle the message, otherwise
// this message will pend indefinitely.
if (client && activeClientIds.has(client.id)) {
;(async function () {
const responseClone = response.clone()
sendToClient(
client,
{
type: 'RESPONSE',
payload: {
requestId,
isMockedResponse: IS_MOCKED_RESPONSE in response,
type: responseClone.type,
status: responseClone.status,
statusText: responseClone.statusText,
body: responseClone.body,
headers: Object.fromEntries(responseClone.headers.entries()),
},
},
[responseClone.body],
)
})()
}
return response
}
// Resolve the main client for the given event.
// Client that issues a request doesn't necessarily equal the client
// that registered the worker. It's with the latter the worker should
// communicate with during the response resolving phase.
async function resolveMainClient(event) {
const client = await self.clients.get(event.clientId)
if (activeClientIds.has(event.clientId)) {
return client
}
if (client?.frameType === 'top-level') {
return client
}
const allClients = await self.clients.matchAll({
type: 'window',
})
return allClients
.filter((client) => {
// Get only those clients that are currently visible.
return client.visibilityState === 'visible'
})
.find((client) => {
// Find the client ID that's recorded in the
// set of clients that have registered the worker.
return activeClientIds.has(client.id)
})
}
async function getResponse(event, client, requestId) {
const { request } = event
// Clone the request because it might've been already used
// (i.e. its body has been read and sent to the client).
const requestClone = request.clone()
function passthrough() {
// Cast the request headers to a new Headers instance
// so the headers can be manipulated with.
const headers = new Headers(requestClone.headers)
// Remove the "accept" header value that marked this request as passthrough.
// This prevents request alteration and also keeps it compliant with the
// user-defined CORS policies.
const acceptHeader = headers.get('accept')
if (acceptHeader) {
const values = acceptHeader.split(',').map((value) => value.trim())
const filteredValues = values.filter(
(value) => value !== 'msw/passthrough',
)
if (filteredValues.length > 0) {
headers.set('accept', filteredValues.join(', '))
} else {
headers.delete('accept')
}
}
return fetch(requestClone, { headers })
}
// Bypass mocking when the client is not active.
if (!client) {
return passthrough()
}
// Bypass initial page load requests (i.e. static assets).
// The absence of the immediate/parent client in the map of the active clients
// means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
// and is not ready to handle requests.
if (!activeClientIds.has(client.id)) {
return passthrough()
}
// Notify the client that a request has been intercepted.
const requestBuffer = await request.arrayBuffer()
const clientMessage = await sendToClient(
client,
{
type: 'REQUEST',
payload: {
id: requestId,
url: request.url,
mode: request.mode,
method: request.method,
headers: Object.fromEntries(request.headers.entries()),
cache: request.cache,
credentials: request.credentials,
destination: request.destination,
integrity: request.integrity,
redirect: request.redirect,
referrer: request.referrer,
referrerPolicy: request.referrerPolicy,
body: requestBuffer,
keepalive: request.keepalive,
},
},
[requestBuffer],
)
switch (clientMessage.type) {
case 'MOCK_RESPONSE': {
return respondWithMock(clientMessage.data)
}
case 'PASSTHROUGH': {
return passthrough()
}
}
return passthrough()
}
function sendToClient(client, message, transferrables = []) {
return new Promise((resolve, reject) => {
const channel = new MessageChannel()
channel.port1.onmessage = (event) => {
if (event.data && event.data.error) {
return reject(event.data.error)
}
resolve(event.data)
}
client.postMessage(
message,
[channel.port2].concat(transferrables.filter(Boolean)),
)
})
}
async function respondWithMock(response) {
// Setting response status code to 0 is a no-op.
// However, when responding with a "Response.error()", the produced Response
// instance will have status code set to 0. Since it's not possible to create
// a Response instance with status code 0, handle that use-case separately.
if (response.status === 0) {
return Response.error()
}
const mockedResponse = new Response(response.body, response)
Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, {
value: true,
enumerable: true,
})
return mockedResponse
}
```
--------------------------------------------------------------------------------
/tools/create-app/templates/default/ts/public/mockServiceWorker.js:
--------------------------------------------------------------------------------
```javascript
/* eslint-disable */
/* tslint:disable */
/**
* Mock Service Worker.
* @see https://github.com/mswjs/msw
* - Please do NOT modify this file.
* - Please do NOT serve this file on production.
*/
const PACKAGE_VERSION = '2.8.4'
const INTEGRITY_CHECKSUM = '00729d72e3b82faf54ca8b9621dbb96f'
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
const activeClientIds = new Set()
self.addEventListener('install', function () {
self.skipWaiting()
})
self.addEventListener('activate', function (event) {
event.waitUntil(self.clients.claim())
})
self.addEventListener('message', async function (event) {
const clientId = event.source.id
if (!clientId || !self.clients) {
return
}
const client = await self.clients.get(clientId)
if (!client) {
return
}
const allClients = await self.clients.matchAll({
type: 'window',
})
switch (event.data) {
case 'KEEPALIVE_REQUEST': {
sendToClient(client, {
type: 'KEEPALIVE_RESPONSE',
})
break
}
case 'INTEGRITY_CHECK_REQUEST': {
sendToClient(client, {
type: 'INTEGRITY_CHECK_RESPONSE',
payload: {
packageVersion: PACKAGE_VERSION,
checksum: INTEGRITY_CHECKSUM,
},
})
break
}
case 'MOCK_ACTIVATE': {
activeClientIds.add(clientId)
sendToClient(client, {
type: 'MOCKING_ENABLED',
payload: {
client: {
id: client.id,
frameType: client.frameType,
},
},
})
break
}
case 'MOCK_DEACTIVATE': {
activeClientIds.delete(clientId)
break
}
case 'CLIENT_CLOSED': {
activeClientIds.delete(clientId)
const remainingClients = allClients.filter((client) => {
return client.id !== clientId
})
// Unregister itself when there are no more clients
if (remainingClients.length === 0) {
self.registration.unregister()
}
break
}
}
})
self.addEventListener('fetch', function (event) {
const { request } = event
// Bypass navigation requests.
if (request.mode === 'navigate') {
return
}
// Opening the DevTools triggers the "only-if-cached" request
// that cannot be handled by the worker. Bypass such requests.
if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') {
return
}
// Bypass all requests when there are no active clients.
// Prevents the self-unregistered worked from handling requests
// after it's been deleted (still remains active until the next reload).
if (activeClientIds.size === 0) {
return
}
// Generate unique request ID.
const requestId = crypto.randomUUID()
event.respondWith(handleRequest(event, requestId))
})
async function handleRequest(event, requestId) {
const client = await resolveMainClient(event)
const response = await getResponse(event, client, requestId)
// Send back the response clone for the "response:*" life-cycle events.
// Ensure MSW is active and ready to handle the message, otherwise
// this message will pend indefinitely.
if (client && activeClientIds.has(client.id)) {
;(async function () {
const responseClone = response.clone()
sendToClient(
client,
{
type: 'RESPONSE',
payload: {
requestId,
isMockedResponse: IS_MOCKED_RESPONSE in response,
type: responseClone.type,
status: responseClone.status,
statusText: responseClone.statusText,
body: responseClone.body,
headers: Object.fromEntries(responseClone.headers.entries()),
},
},
[responseClone.body],
)
})()
}
return response
}
// Resolve the main client for the given event.
// Client that issues a request doesn't necessarily equal the client
// that registered the worker. It's with the latter the worker should
// communicate with during the response resolving phase.
async function resolveMainClient(event) {
const client = await self.clients.get(event.clientId)
if (activeClientIds.has(event.clientId)) {
return client
}
if (client?.frameType === 'top-level') {
return client
}
const allClients = await self.clients.matchAll({
type: 'window',
})
return allClients
.filter((client) => {
// Get only those clients that are currently visible.
return client.visibilityState === 'visible'
})
.find((client) => {
// Find the client ID that's recorded in the
// set of clients that have registered the worker.
return activeClientIds.has(client.id)
})
}
async function getResponse(event, client, requestId) {
const { request } = event
// Clone the request because it might've been already used
// (i.e. its body has been read and sent to the client).
const requestClone = request.clone()
function passthrough() {
// Cast the request headers to a new Headers instance
// so the headers can be manipulated with.
const headers = new Headers(requestClone.headers)
// Remove the "accept" header value that marked this request as passthrough.
// This prevents request alteration and also keeps it compliant with the
// user-defined CORS policies.
const acceptHeader = headers.get('accept')
if (acceptHeader) {
const values = acceptHeader.split(',').map((value) => value.trim())
const filteredValues = values.filter(
(value) => value !== 'msw/passthrough',
)
if (filteredValues.length > 0) {
headers.set('accept', filteredValues.join(', '))
} else {
headers.delete('accept')
}
}
return fetch(requestClone, { headers })
}
// Bypass mocking when the client is not active.
if (!client) {
return passthrough()
}
// Bypass initial page load requests (i.e. static assets).
// The absence of the immediate/parent client in the map of the active clients
// means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
// and is not ready to handle requests.
if (!activeClientIds.has(client.id)) {
return passthrough()
}
// Notify the client that a request has been intercepted.
const requestBuffer = await request.arrayBuffer()
const clientMessage = await sendToClient(
client,
{
type: 'REQUEST',
payload: {
id: requestId,
url: request.url,
mode: request.mode,
method: request.method,
headers: Object.fromEntries(request.headers.entries()),
cache: request.cache,
credentials: request.credentials,
destination: request.destination,
integrity: request.integrity,
redirect: request.redirect,
referrer: request.referrer,
referrerPolicy: request.referrerPolicy,
body: requestBuffer,
keepalive: request.keepalive,
},
},
[requestBuffer],
)
switch (clientMessage.type) {
case 'MOCK_RESPONSE': {
return respondWithMock(clientMessage.data)
}
case 'PASSTHROUGH': {
return passthrough()
}
}
return passthrough()
}
function sendToClient(client, message, transferrables = []) {
return new Promise((resolve, reject) => {
const channel = new MessageChannel()
channel.port1.onmessage = (event) => {
if (event.data && event.data.error) {
return reject(event.data.error)
}
resolve(event.data)
}
client.postMessage(
message,
[channel.port2].concat(transferrables.filter(Boolean)),
)
})
}
async function respondWithMock(response) {
// Setting response status code to 0 is a no-op.
// However, when responding with a "Response.error()", the produced Response
// instance will have status code set to 0. Since it's not possible to create
// a Response instance with status code 0, handle that use-case separately.
if (response.status === 0) {
return Response.error()
}
const mockedResponse = new Response(response.body, response)
Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, {
value: true,
enumerable: true,
})
return mockedResponse
}
```
--------------------------------------------------------------------------------
/xmlui/src/testing/infrastructure/public/mockServiceWorker.js:
--------------------------------------------------------------------------------
```javascript
/* eslint-disable */
/* tslint:disable */
/**
* Mock Service Worker.
* @see https://github.com/mswjs/msw
* - Please do NOT modify this file.
* - Please do NOT serve this file on production.
*/
const PACKAGE_VERSION = '2.8.4'
const INTEGRITY_CHECKSUM = '00729d72e3b82faf54ca8b9621dbb96f'
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
const activeClientIds = new Set()
self.addEventListener('install', function () {
self.skipWaiting()
})
self.addEventListener('activate', function (event) {
event.waitUntil(self.clients.claim())
})
self.addEventListener('message', async function (event) {
const clientId = event.source.id
if (!clientId || !self.clients) {
return
}
const client = await self.clients.get(clientId)
if (!client) {
return
}
const allClients = await self.clients.matchAll({
type: 'window',
})
switch (event.data) {
case 'KEEPALIVE_REQUEST': {
sendToClient(client, {
type: 'KEEPALIVE_RESPONSE',
})
break
}
case 'INTEGRITY_CHECK_REQUEST': {
sendToClient(client, {
type: 'INTEGRITY_CHECK_RESPONSE',
payload: {
packageVersion: PACKAGE_VERSION,
checksum: INTEGRITY_CHECKSUM,
},
})
break
}
case 'MOCK_ACTIVATE': {
activeClientIds.add(clientId)
sendToClient(client, {
type: 'MOCKING_ENABLED',
payload: {
client: {
id: client.id,
frameType: client.frameType,
},
},
})
break
}
case 'MOCK_DEACTIVATE': {
activeClientIds.delete(clientId)
break
}
case 'CLIENT_CLOSED': {
activeClientIds.delete(clientId)
const remainingClients = allClients.filter((client) => {
return client.id !== clientId
})
// Unregister itself when there are no more clients
if (remainingClients.length === 0) {
self.registration.unregister()
}
break
}
}
})
self.addEventListener('fetch', function (event) {
const { request } = event
// Bypass navigation requests.
if (request.mode === 'navigate') {
return
}
// Opening the DevTools triggers the "only-if-cached" request
// that cannot be handled by the worker. Bypass such requests.
if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') {
return
}
// Bypass all requests when there are no active clients.
// Prevents the self-unregistered worked from handling requests
// after it's been deleted (still remains active until the next reload).
if (activeClientIds.size === 0) {
return
}
// Generate unique request ID.
const requestId = crypto.randomUUID()
event.respondWith(handleRequest(event, requestId))
})
async function handleRequest(event, requestId) {
const client = await resolveMainClient(event)
const response = await getResponse(event, client, requestId)
// Send back the response clone for the "response:*" life-cycle events.
// Ensure MSW is active and ready to handle the message, otherwise
// this message will pend indefinitely.
if (client && activeClientIds.has(client.id)) {
;(async function () {
const responseClone = response.clone()
sendToClient(
client,
{
type: 'RESPONSE',
payload: {
requestId,
isMockedResponse: IS_MOCKED_RESPONSE in response,
type: responseClone.type,
status: responseClone.status,
statusText: responseClone.statusText,
body: responseClone.body,
headers: Object.fromEntries(responseClone.headers.entries()),
},
},
[responseClone.body],
)
})()
}
return response
}
// Resolve the main client for the given event.
// Client that issues a request doesn't necessarily equal the client
// that registered the worker. It's with the latter the worker should
// communicate with during the response resolving phase.
async function resolveMainClient(event) {
const client = await self.clients.get(event.clientId)
if (activeClientIds.has(event.clientId)) {
return client
}
if (client?.frameType === 'top-level') {
return client
}
const allClients = await self.clients.matchAll({
type: 'window',
})
return allClients
.filter((client) => {
// Get only those clients that are currently visible.
return client.visibilityState === 'visible'
})
.find((client) => {
// Find the client ID that's recorded in the
// set of clients that have registered the worker.
return activeClientIds.has(client.id)
})
}
async function getResponse(event, client, requestId) {
const { request } = event
// Clone the request because it might've been already used
// (i.e. its body has been read and sent to the client).
const requestClone = request.clone()
function passthrough() {
// Cast the request headers to a new Headers instance
// so the headers can be manipulated with.
const headers = new Headers(requestClone.headers)
// Remove the "accept" header value that marked this request as passthrough.
// This prevents request alteration and also keeps it compliant with the
// user-defined CORS policies.
const acceptHeader = headers.get('accept')
if (acceptHeader) {
const values = acceptHeader.split(',').map((value) => value.trim())
const filteredValues = values.filter(
(value) => value !== 'msw/passthrough',
)
if (filteredValues.length > 0) {
headers.set('accept', filteredValues.join(', '))
} else {
headers.delete('accept')
}
}
return fetch(requestClone, { headers })
}
// Bypass mocking when the client is not active.
if (!client) {
return passthrough()
}
// Bypass initial page load requests (i.e. static assets).
// The absence of the immediate/parent client in the map of the active clients
// means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
// and is not ready to handle requests.
if (!activeClientIds.has(client.id)) {
return passthrough()
}
// Notify the client that a request has been intercepted.
const requestBuffer = await request.arrayBuffer()
const clientMessage = await sendToClient(
client,
{
type: 'REQUEST',
payload: {
id: requestId,
url: request.url,
mode: request.mode,
method: request.method,
headers: Object.fromEntries(request.headers.entries()),
cache: request.cache,
credentials: request.credentials,
destination: request.destination,
integrity: request.integrity,
redirect: request.redirect,
referrer: request.referrer,
referrerPolicy: request.referrerPolicy,
body: requestBuffer,
keepalive: request.keepalive,
},
},
[requestBuffer],
)
switch (clientMessage.type) {
case 'MOCK_RESPONSE': {
return respondWithMock(clientMessage.data)
}
case 'PASSTHROUGH': {
return passthrough()
}
}
return passthrough()
}
function sendToClient(client, message, transferrables = []) {
return new Promise((resolve, reject) => {
const channel = new MessageChannel()
channel.port1.onmessage = (event) => {
if (event.data && event.data.error) {
return reject(event.data.error)
}
resolve(event.data)
}
client.postMessage(
message,
[channel.port2].concat(transferrables.filter(Boolean)),
)
})
}
async function respondWithMock(response) {
// Setting response status code to 0 is a no-op.
// However, when responding with a "Response.error()", the produced Response
// instance will have status code set to 0. Since it's not possible to create
// a Response instance with status code 0, handle that use-case separately.
if (response.status === 0) {
return Response.error()
}
const mockedResponse = new Response(response.body, response)
Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, {
value: true,
enumerable: true,
})
return mockedResponse
}
```
--------------------------------------------------------------------------------
/xmlui/src/components/Tree/Tree-icons.spec.ts:
--------------------------------------------------------------------------------
```typescript
import { expect, test } from "../../testing/fixtures";
import { flatTreeData, flatTreeDataWithIcons1, flatTreeDataWithIcons2, flatTreeDataWithIconsAndAlias1 } from "./testData";
test("default collapsed icon appears", async ({ initTestBed, createTreeDriver }) => {
await initTestBed(`
<VStack height="400px">
<Tree testId="tree" data='{${JSON.stringify(flatTreeData)}}' />
</VStack>
`);
const tree = await createTreeDriver("tree");
await expect(tree.getIconByName("chevronright")).toBeVisible();
await expect(tree.getIconByName("chevrondown")).not.toBeVisible();
});
test("custom iconCollapsed appears", async ({ initTestBed, createTreeDriver }) => {
await initTestBed(`
<VStack height="400px">
<Tree
testId="tree"
data='{${JSON.stringify(flatTreeData)}}'
iconCollapsed="phone"
iconExpanded="email"
/>
</VStack>
`);
const tree = await createTreeDriver("tree");
await expect(tree.getIconByName("phone")).toBeVisible();
await expect(tree.getIconByName("email")).not.toBeVisible();
});
test("default expanded icon appears", async ({ initTestBed, createTreeDriver }) => {
await initTestBed(`
<VStack height="400px">
<Tree
testId="tree"
defaultExpanded="all"
data='{${JSON.stringify(flatTreeData)}}'
/>
</VStack>
`);
const tree = await createTreeDriver("tree");
await expect(tree.getIconByName("chevrondown")).toBeVisible();
});
test("custom iconExpanded appears", async ({ initTestBed, createTreeDriver }) => {
await initTestBed(`
<VStack height="400px">
<Tree
testId="tree"
defaultExpanded="all"
data='{${JSON.stringify(flatTreeData)}}'
iconCollapsed="phone"
iconExpanded="email"
/>
</VStack>
`);
const tree = await createTreeDriver("tree");
await expect(tree.getIconByName("phone")).not.toBeVisible();
await expect(tree.getIconsByName("email")).toHaveCount(2);
});
test("both custom icons work together", async ({ initTestBed, createTreeDriver, page }) => {
await initTestBed(`
<VStack height="400px">
<Tree
id="tree"
data='{${JSON.stringify(flatTreeData)}}'
iconCollapsed="phone"
iconExpanded="email"
>
<property name="itemTemplate">
<HStack testId="{$item.id}" verticalAlignment="center">
<Icon name="folder" />
<Text value="{$item.name}" />
</HStack>
</property>
</Tree>
<Button testId="expand1" onClick="tree.expandNode(1)">Expand Node 1</Button>
<Button testId="expand2" onClick="tree.collapseNode(1)">Collapse Node 2</Button>
</VStack>
`);
const tree = await createTreeDriver("tree");
await expect(tree.component).toBeVisible();
// Initially should show collapsed icon
await expect(tree.getIconsByName("phone")).toBeVisible();
await expect(tree.getIconsByName("email")).not.toBeVisible();
// Expand the first node
await page.getByTestId("expand1").click();
// Should now show expanded icon and hide collapsed icon
await expect(tree.getIconsByName("email")).toBeVisible();
await expect(tree.getIconsByName("phone")).toBeVisible();
// Collapse again
await page.getByTestId("expand2").click();
// Should show collapsed icon again
await expect(tree.getIconsByName("phone")).toBeVisible();
await expect(tree.getIconsByName("email")).not.toBeVisible();
});
test("iconExpanded fields are used", async ({ initTestBed, page, createTreeDriver }) => {
await initTestBed(`
<VStack height="400px">
<Tree
testId="tree"
id="tree"
defaultExpanded="first-level"
data='{${JSON.stringify(flatTreeDataWithIcons1)}}' />
<Button testId="expand1" onClick="tree.expandNode(2)">Expand Node 2</Button>
</VStack>
`);
const tree = await createTreeDriver("tree");
// --- Initila state
await expect(tree.getIconByName("phone")).toBeVisible();
await expect(tree.getIconByName("email")).not.toBeVisible();
await expect(tree.getIconsByName("chevronright")).toHaveCount(1);
await page.getByTestId("expand1").click();
// --- After expanding node 2
await expect(tree.getIconByName("phone")).toBeVisible();
await expect(tree.getIconByName("email")).toBeVisible();
await expect(tree.getIconsByName("chevronright")).toHaveCount(0);
});
test("iconExpanded fields (with alias) are used", async ({
initTestBed,
page,
createTreeDriver,
}) => {
await initTestBed(`
<VStack height="400px">
<Tree
testId="tree"
id="tree"
iconExpandedField="iconExp"
defaultExpanded="first-level"
data='{${JSON.stringify(flatTreeDataWithIconsAndAlias1)}}' />
<Button testId="expand1" onClick="tree.expandNode(2)">Expand Node 2</Button>
</VStack>
`);
const tree = await createTreeDriver("tree");
// --- Initila state
await expect(tree.getIconByName("phone")).toBeVisible();
await expect(tree.getIconByName("email")).not.toBeVisible();
await expect(tree.getIconsByName("chevronright")).toHaveCount(1);
await page.getByTestId("expand1").click();
// --- After expanding node 2
await expect(tree.getIconByName("phone")).toBeVisible();
await expect(tree.getIconByName("email")).toBeVisible();
await expect(tree.getIconsByName("chevronright")).toHaveCount(0);
});
test("iconCollapsed fields are used", async ({ initTestBed, page, createTreeDriver }) => {
await initTestBed(`
<VStack height="400px">
<Tree
testId="tree"
id="tree"
defaultExpanded="none"
data='{${JSON.stringify(flatTreeDataWithIcons2)}}' />
<Button testId="expand1" onClick="tree.expandNode(1)">Expand Node 1</Button>
<Button testId="expand2" onClick="tree.expandNode(2)">Expand Node 2</Button>
</VStack>
`);
const tree = await createTreeDriver("tree");
// --- Initila state
await expect(tree.getIconByName("phone")).toBeVisible();
await expect(tree.getIconByName("email")).not.toBeVisible();
await expect(tree.getIconsByName("chevronright")).toHaveCount(0);
await page.getByTestId("expand1").click();
// --- After expanding node 1
await expect(tree.getIconByName("phone")).not.toBeVisible();
await expect(tree.getIconByName("email")).toBeVisible();
await expect(tree.getIconsByName("chevrondown")).toHaveCount(1);
await page.getByTestId("expand2").click();
// --- After expanding node 2
await expect(tree.getIconByName("phone")).not.toBeVisible();
await expect(tree.getIconByName("email")).not.toBeVisible();
await expect(tree.getIconsByName("chevrondown")).toHaveCount(2);
});
test("iconCollapsed fields (with alias) are used", async ({ initTestBed, page, createTreeDriver }) => {
await initTestBed(`
<VStack height="400px">
<Tree
testId="tree"
id="tree"
iconCollapsedField="iconColl"
defaultExpanded="none"
data='{${JSON.stringify(flatTreeDataWithIcons2)}}' />
<Button testId="expand1" onClick="tree.expandNode(1)">Expand Node 1</Button>
<Button testId="expand2" onClick="tree.expandNode(2)">Expand Node 2</Button>
</VStack>
`);
const tree = await createTreeDriver("tree");
// --- Initila state
await expect(tree.getIconByName("phone")).toBeVisible();
await expect(tree.getIconByName("email")).not.toBeVisible();
await expect(tree.getIconsByName("chevronright")).toHaveCount(0);
await page.getByTestId("expand1").click();
// --- After expanding node 1
await expect(tree.getIconByName("phone")).not.toBeVisible();
await expect(tree.getIconByName("email")).toBeVisible();
await expect(tree.getIconsByName("chevrondown")).toHaveCount(1);
await page.getByTestId("expand2").click();
// --- After expanding node 2
await expect(tree.getIconByName("phone")).not.toBeVisible();
await expect(tree.getIconByName("email")).not.toBeVisible();
await expect(tree.getIconsByName("chevrondown")).toHaveCount(2);
});
```
--------------------------------------------------------------------------------
/xmlui/src/components/Stack/Stack.tsx:
--------------------------------------------------------------------------------
```typescript
import type React from "react";
import styles from "./Stack.module.scss";
import type { ComponentDef, ComponentPropertyMetadata } from "../../abstractions/ComponentDefs";
import type { RenderChildFn } from "../../abstractions/RendererDefs";
import type { AsyncFunction } from "../../abstractions/FunctionDefs";
import type { ValueExtractor } from "../../abstractions/RendererDefs";
import { createComponentRenderer } from "../../components-core/renderers";
import { isComponentDefChildren } from "../../components-core/utils/misc";
import { NotAComponentDefError } from "../../components-core/EngineError";
import { parseScssVar } from "../../components-core/theming/themeVars";
import { createMetadata, dClick, dInternal } from "../metadata-helpers";
import { DEFAULT_ORIENTATION, Stack, defaultProps } from "./StackNative";
import { alignmentOptionValues } from "../abstractions";
const COMP = "Stack";
const HORIZONTAL_ALIGNMENT: ComponentPropertyMetadata = {
description: "Manages the horizontal content alignment for each child element in the Stack.",
availableValues: alignmentOptionValues,
valueType: "string",
defaultValue: "start",
};
const VERTICAL_ALIGNMENT: ComponentPropertyMetadata = {
description: "Manages the vertical content alignment for each child element in the Stack.",
availableValues: alignmentOptionValues,
valueType: "string",
defaultValue: "start",
};
const stackMd = createMetadata({
status: "stable",
description:
"`Stack` is the fundamental layout container that organizes child elements in " +
"configurable horizontal or vertical arrangements. As the most versatile building " +
"block in XMLUI's layout system, it provides comprehensive alignment, spacing, " +
"and flow control options that serve as the foundation for all specialized stack variants.",
props: {
gap: {
description: "Optional size value indicating the gap between child elements.",
valueType: "string",
defaultValue: "$gap-normal",
},
reverse: {
description: "Optional boolean property to reverse the order of child elements.",
valueType: "boolean",
defaultValue: defaultProps.reverse,
},
wrapContent: {
description:
"Optional boolean which wraps the content if set to true and the available " +
"space is not big enough. Works only with horizontal orientations.",
valueType: "boolean",
defaultValue: false,
},
orientation: {
description:
"An optional property that governs the Stack's orientation (whether " +
"the Stack lays out its children in a row or a column).",
availableValues: ["horizontal", "vertical"],
valueType: "string",
defaultValue: defaultProps.orientation,
},
horizontalAlignment: HORIZONTAL_ALIGNMENT,
verticalAlignment: VERTICAL_ALIGNMENT,
hoverContainer: {
...dInternal("Reserved for future use"),
defaultValue: defaultProps.hoverContainer,
},
visibleOnHover: {
...dInternal("Reserved for future use"),
defaultValue: defaultProps.visibleOnHover,
},
},
events: {
click: dClick(COMP),
mounted: dInternal("Reserved for future use"),
},
themeVars: parseScssVar(styles.themeVars),
});
export const StackMd = {
...stackMd,
props: {
...stackMd.props,
},
};
type StackComponentDef = ComponentDef<typeof StackMd>;
export const VStackMd = {
...StackMd,
specializedFrom: COMP,
description: `This component represents a stack rendering its contents vertically.`,
props: {
...stackMd.props,
},
};
type VStackComponentDef = ComponentDef<typeof VStackMd>;
export const HStackMd = {
...StackMd,
specializedFrom: COMP,
description: `This component represents a stack rendering its contents horizontally.`,
props: {
...stackMd.props,
},
};
type HStackComponentDef = ComponentDef<typeof HStackMd>;
export const CVStackMd = {
...StackMd,
specializedFrom: COMP,
description:
`This component represents a stack that renders its contents vertically ` +
`and aligns that in the center along both axes.`,
};
type CVStackComponentDef = ComponentDef<typeof CVStackMd>;
export const CHStackMd = {
...StackMd,
specializedFrom: COMP,
description:
`This component represents a stack that renders its contents horizontally ` +
`and aligns that in the center along both axes.`,
};
type CHStackComponentDef = ComponentDef<typeof CHStackMd>;
type RenderStackPars = {
node:
| StackComponentDef
| VStackComponentDef
| HStackComponentDef
| CVStackComponentDef
| CHStackComponentDef;
extractValue: ValueExtractor;
className?: string;
lookupEventHandler: (
eventName: keyof NonNullable<StackComponentDef["events"]>,
) => AsyncFunction | undefined;
renderChild: RenderChildFn;
orientation: string;
horizontalAlignment: string;
verticalAlignment: string;
};
function renderStack({
node,
extractValue,
className,
orientation,
horizontalAlignment,
verticalAlignment,
lookupEventHandler,
renderChild,
}: RenderStackPars) {
if (!isComponentDefChildren(node.children)) {
throw new NotAComponentDefError();
}
return (
<Stack
orientation={orientation}
horizontalAlignment={horizontalAlignment}
verticalAlignment={verticalAlignment}
reverse={extractValue(node.props?.reverse)}
hoverContainer={extractValue(node.props?.hoverContainer)}
visibleOnHover={extractValue(node.props?.visibleOnHover)}
className={className}
onMount={lookupEventHandler("mounted")}
>
{renderChild(node.children, {
type: "Stack",
orientation,
})}
</Stack>
);
}
export const stackComponentRenderer = createComponentRenderer(
COMP,
StackMd,
({ node, extractValue, renderChild, className, lookupEventHandler }) => {
const orientation = extractValue(node.props?.orientation) || DEFAULT_ORIENTATION;
const horizontalAlignment = extractValue(node.props?.horizontalAlignment);
const verticalAlignment = extractValue(node.props?.verticalAlignment);
return renderStack({
node,
extractValue,
className,
orientation,
horizontalAlignment,
verticalAlignment,
lookupEventHandler,
renderChild,
});
},
);
export const vStackComponentRenderer = createComponentRenderer(
"VStack",
VStackMd,
({ node, extractValue, renderChild, className, lookupEventHandler }) => {
const horizontalAlignment = extractValue(node.props?.horizontalAlignment);
const verticalAlignment = extractValue(node.props?.verticalAlignment);
return renderStack({
node,
extractValue,
className,
lookupEventHandler,
renderChild,
orientation: "vertical",
horizontalAlignment,
verticalAlignment,
});
},
);
export const hStackComponentRenderer = createComponentRenderer(
"HStack",
HStackMd,
({ node, extractValue, renderChild, className, lookupEventHandler }) => {
const horizontalAlignment = extractValue(node.props?.horizontalAlignment);
const verticalAlignment = extractValue(node.props?.verticalAlignment);
return renderStack({
node,
extractValue,
className,
lookupEventHandler,
renderChild,
orientation: "horizontal",
horizontalAlignment,
verticalAlignment,
});
},
);
export const cvStackComponentRenderer = createComponentRenderer(
"CVStack",
CVStackMd,
({ node, extractValue, renderChild, className, lookupEventHandler }) => {
return renderStack({
node,
extractValue,
className,
lookupEventHandler,
renderChild,
orientation: "vertical",
horizontalAlignment: "center",
verticalAlignment: "center",
});
},
);
export const chStackComponentRenderer = createComponentRenderer(
"CHStack",
CHStackMd,
({ node, extractValue, renderChild, className, lookupEventHandler }) => {
return renderStack({
node,
extractValue,
className,
lookupEventHandler,
renderChild,
orientation: "horizontal",
horizontalAlignment: "center",
verticalAlignment: "center",
});
},
);
```
--------------------------------------------------------------------------------
/packages/xmlui-website-blocks/src/FancyButton/FancyButton.tsx:
--------------------------------------------------------------------------------
```typescript
import { createUserDefinedComponentRenderer, dClick, dGotFocus, dLostFocus } from "xmlui";
import { createMetadata } from "xmlui";
import componentSource from "./FancyButton.xmlui";
import { PropertyValueDescription } from "xmlui/src/abstractions/ComponentDefs";
export const alignmentOptionMd: PropertyValueDescription[] = [
{ value: "center", description: "Place the content in the middle" },
{
value: "start",
description: "Justify the content to the left (to the right if in right-to-left)",
},
{
value: "end",
description: "Justify the content to the right (to the left if in right-to-left)",
},
];
export const sizeMd: PropertyValueDescription[] = [
{ value: "xs", description: "Extra small" },
{ value: "sm", description: "Small" },
{ value: "md", description: "Medium" },
{ value: "lg", description: "Large" },
{ value: "xl", description: "Extra large" },
];
export const iconPositionMd: PropertyValueDescription[] = [
{
value: "start",
description:
"The icon will appear at the start (left side when the left-to-right direction is set)",
},
{
value: "end",
description:
"The icon will appear at the end (right side when the left-to-right direction is set)",
},
];
export const buttonTypesMd: PropertyValueDescription[] = [
{
value: "button",
description: "Regular behavior that only executes logic if explicitly determined.",
},
{
value: "submit",
description:
"The button submits the form data to the server. This is the default for buttons in a Form or NativeForm component.",
},
{
value: "reset",
description:
"Resets all the controls to their initial values. Using it is ill advised for UX reasons.",
},
];
const COMP = "FancyButton";
const fancyButtonVariantMd = [
{ value: "rounded", description: "Rounded variant with soft corners" },
{ value: "square", description: "Square variant with sharp corners" },
{ value: "pill", description: "Pill variant with fully rounded edges" },
{ value: "outlinedPill", description: "Outlined pill variant with fully rounded edges" },
];
export const FancyButtonMd = createMetadata({
status: "experimental",
description:
"`FancyButton` is an enhanced interactive component for triggering actions with " +
"advanced styling options. It provides rounded and square variants for different " +
"design aesthetics while maintaining all standard button functionality.",
props: {
autoFocus: {
description: "Indicates if the button should receive focus when the page loads.",
isRequired: false,
type: "boolean",
defaultValue: false,
},
variant: {
description: "The button variant determines the visual style and corner treatment.",
isRequired: false,
type: "string",
availableValues: fancyButtonVariantMd,
defaultValue: "rounded",
},
size: {
description: "Sets the size of the button.",
isRequired: false,
type: "string",
availableValues: sizeMd,
defaultValue: "md",
},
label: {
description:
`This property is an optional string to set a label for the ${COMP}. If no label is ` +
`specified and an icon is set, the ${COMP} will modify its styling to look like a ` +
`small icon button. When the ${COMP} has nested children, it will display them and ` +
`ignore the value of the \`label\` prop.`,
type: "string",
},
type: {
description:
`This optional string describes how the ${COMP} appears in an HTML context. You ` +
`rarely need to set this property explicitly.`,
availableValues: buttonTypesMd,
valueType: "string",
defaultValue: "button",
},
enabled: {
description:
`The value of this property indicates whether the button accepts actions (\`true\`) ` +
`or does not react to them (\`false\`).`,
type: "boolean",
defaultValue: true,
},
icon: {
description:
`This string value denotes an icon name. The framework will render an icon if XMLUI ` +
`recognizes the icon by its name. If no label is specified and an icon is set, the ${COMP} ` +
`displays only that icon.`,
type: "string",
},
iconPosition: {
description: `This optional string determines the location of the icon in the ${COMP}.`,
availableValues: iconPositionMd,
type: "string",
defaultValue: "start",
},
contentPosition: {
description:
`This optional value determines how the label and icon (or nested children) should be placed` +
`inside the ${COMP} component.`,
availableValues: alignmentOptionMd,
type: "string",
defaultValue: "center",
},
contextualLabel: {
description: `This optional value is used to provide an accessible name for the ${COMP} in the context of its usage.`,
type: "string",
},
},
events: {
click: dClick(COMP),
gotFocus: dGotFocus(COMP),
lostFocus: dLostFocus(COMP),
},
defaultThemeVars: {
[`fontSize-${COMP}`]: "$fontSize-sm",
[`fontWeight-${COMP}`]: "$fontWeight-medium",
[`gap-${COMP}`]: "$space-2",
[`backgroundColor-${COMP}--disabled`]: "$backgroundColor--disabled",
[`borderColor-${COMP}--disabled`]: "$borderColor--disabled",
[`borderStyle-${COMP}`]: "solid",
[`textColor-${COMP}--disabled`]: "$textColor--disabled",
[`outlineColor-${COMP}--focus`]: "$outlineColor--focus",
[`borderWidth-${COMP}`]: "1px",
[`outlineWidth-${COMP}--focus`]: "$outlineWidth--focus",
[`outlineStyle-${COMP}--focus`]: "$outlineStyle--focus",
[`outlineOffset-${COMP}--focus`]: "$outlineOffset--focus",
// Size variant theme variables
[`paddingHorizontal-${COMP}-xs`]: "$space-3",
[`paddingVertical-${COMP}-xs`]: "$space-1_5",
[`fontSize-${COMP}-xs`]: "$fontSize-xs",
[`gap-${COMP}-xs`]: "$space-2",
[`paddingHorizontal-${COMP}-sm`]: "$space-4",
[`paddingVertical-${COMP}-sm`]: "$space-2",
[`fontSize-${COMP}-sm`]: "$fontSize-sm",
[`gap-${COMP}-sm`]: "$space-2_5",
[`paddingHorizontal-${COMP}-md`]: "$space-5",
[`paddingVertical-${COMP}-md`]: "$space-2_5",
[`fontSize-${COMP}-md`]: "$fontSize-xl",
[`gap-${COMP}-md`]: "$space-3",
[`paddingHorizontal-${COMP}-lg`]: "$space-8",
[`paddingVertical-${COMP}-lg`]: "$space-3",
[`fontSize-${COMP}-lg`]: "$fontSize-2xl",
[`gap-${COMP}-lg`]: "$space-4",
[`paddingHorizontal-${COMP}-xl`]: "$space-10",
[`paddingVertical-${COMP}-xl`]: "$space-4",
[`fontSize-${COMP}-xl`]: "$fontSize-4xl",
[`gap-${COMP}-xl`]: "$space-5",
// Common colors
[`backgroundColor-${COMP}`]: "$color-primary-500",
[`backgroundColor-${COMP}--hover`]: "$color-primary-400",
[`backgroundColor-${COMP}--active`]: "$color-primary-600",
[`textColor-${COMP}`]: "$const-color-surface-50",
[`borderColor-${COMP}`]: "$color-primary-500",
[`borderColor-${COMP}--hover`]: "$color-primary-400",
// Rounded variant theme variables
[`borderRadius-${COMP}-rounded-xs`]: "6px",
[`borderRadius-${COMP}-rounded-sm`]: "8px",
[`borderRadius-${COMP}-rounded-md`]: "12px",
[`borderRadius-${COMP}-rounded-lg`]: "16px",
[`borderRadius-${COMP}-rounded-xl`]: "24px",
// Square variant theme variables
[`borderRadius-${COMP}-square`]: "$space-0",
// Pill variant theme variables
[`borderRadius-${COMP}-pill`]: "9999px",
// Outlined pill variant theme variables
[`backgroundColor-${COMP}-outlinedPill`]: "transparent",
[`backgroundColor-${COMP}-outlinedPill--hover`]: "transparent",
[`borderRadius-${COMP}-outlinedPill`]: "9999px",
[`borderColor-${COMP}-outlinedPill--hover`]: "red",
[`textColor-${COMP}-outlinedPill`]: "$textColor-primary",
[`borderWidth-${COMP}-outlinedPill-xs`]: "1.5px",
[`borderWidth-${COMP}-outlinedPill-sm`]: "2px",
[`borderWidth-${COMP}-outlinedPill-md`]: "2.5px",
[`borderWidth-${COMP}-outlinedPill-lg`]: "4px",
[`borderWidth-${COMP}-outlinedPill-xl`]: "5px",
},
});
export const fancyButtonRenderer = createUserDefinedComponentRenderer(
FancyButtonMd,
componentSource,
);
```
--------------------------------------------------------------------------------
/xmlui/src/components/SpaceFiller/SpaceFiller.spec.ts:
--------------------------------------------------------------------------------
```typescript
import { test, expect } from "../../testing/fixtures";
// =============================================================================
// BASIC FUNCTIONALITY TESTS
// =============================================================================
test.describe("Basic Functionality", () => {
test("renders without props", async ({ initTestBed, page }) => {
await initTestBed(`<SpaceFiller/>`);
const spaceFiller = page.getByTestId("test-id-component");
// SpaceFiller exists in DOM but may not be "visible" due to zero dimensions
await expect(spaceFiller).toBeAttached();
await expect(spaceFiller).toHaveCSS("flex", "1 1 0px");
await expect(spaceFiller).toHaveCSS("place-self", "stretch");
});
test("renders in HStack and pushes subsequent elements to the end", async ({ initTestBed, page }) => {
await initTestBed(`
<HStack>
<Stack width="36px" height="36px" backgroundColor="red" testId="red-box"/>
<SpaceFiller testId="spacer"/>
<Stack width="36px" height="36px" backgroundColor="blue" testId="blue-box"/>
</HStack>
`);
const redBox = page.getByTestId("red-box");
const blueBox = page.getByTestId("blue-box");
const spaceFiller = page.getByTestId("spacer");
await expect(spaceFiller).toBeAttached();
await expect(redBox).toBeVisible();
await expect(blueBox).toBeVisible();
// Verify the spaceFiller has flex properties
await expect(spaceFiller).toHaveCSS("flex", "1 1 0px");
await expect(spaceFiller).toHaveCSS("place-self", "stretch");
});
test("renders in VStack and pushes subsequent elements to the end", async ({ initTestBed, page }) => {
await initTestBed(`
<VStack>
<Stack width="36px" height="36px" backgroundColor="red" testId="red-box"/>
<SpaceFiller testId="spacer"/>
<Stack width="36px" height="36px" backgroundColor="blue" testId="blue-box"/>
</VStack>
`);
const redBox = page.getByTestId("red-box");
const blueBox = page.getByTestId("blue-box");
const spaceFiller = page.getByTestId("spacer");
await expect(spaceFiller).toBeAttached();
await expect(redBox).toBeVisible();
await expect(blueBox).toBeVisible();
// Verify the spaceFiller has flex properties
await expect(spaceFiller).toHaveCSS("flex", "1 1 0px");
await expect(spaceFiller).toHaveCSS("place-self", "stretch");
});
test("acts as line break in FlowLayout", async ({ initTestBed, page }) => {
await initTestBed(`
<FlowLayout>
<Stack width="20%" height="36px" backgroundColor="red" testId="red-box"/>
<SpaceFiller/>
<Stack width="20%" height="36px" backgroundColor="green" testId="green-box"/>
<Stack width="20%" height="36px" backgroundColor="blue" testId="blue-box"/>
</FlowLayout>
`);
const redBox = page.getByTestId("red-box");
const greenBox = page.getByTestId("green-box");
const blueBox = page.getByTestId("blue-box");
await expect(redBox).toBeVisible();
await expect(greenBox).toBeVisible();
await expect(blueBox).toBeVisible();
// Get bounding rectangles to verify line break behavior
const redRect = await redBox.boundingBox();
const greenRect = await greenBox.boundingBox();
const blueRect = await blueBox.boundingBox();
// Red box should be on the first line
// Green and blue boxes should be on the second line (lower y position)
// The SpaceFiller should cause a line break between red and green/blue
expect(redRect).not.toBeNull();
expect(greenRect).not.toBeNull();
expect(blueRect).not.toBeNull();
// Green and blue boxes should be on a different (lower) line than red
expect(greenRect!.y).toBeGreaterThan(redRect!.y);
expect(blueRect!.y).toBeGreaterThan(redRect!.y);
// Green and blue should be on the same line (similar y coordinates)
expect(Math.abs(greenRect!.y - blueRect!.y)).toBeLessThan(5);
});
test("multiple SpaceFillers distribute space evenly", async ({ initTestBed, page }) => {
await initTestBed(`
<HStack>
<Stack width="36px" height="36px" backgroundColor="red" testId="red-box"/>
<SpaceFiller testId="spacer1"/>
<Stack width="36px" height="36px" backgroundColor="green" testId="green-box"/>
<SpaceFiller testId="spacer2"/>
<Stack width="36px" height="36px" backgroundColor="blue" testId="blue-box"/>
</HStack>
`);
const redBox = page.getByTestId("red-box");
const greenBox = page.getByTestId("green-box");
const blueBox = page.getByTestId("blue-box");
const spaceFiller1 = page.getByTestId("spacer1");
const spaceFiller2 = page.getByTestId("spacer2");
await expect(spaceFiller1).toBeVisible();
await expect(spaceFiller2).toBeVisible();
await expect(redBox).toBeVisible();
await expect(greenBox).toBeVisible();
await expect(blueBox).toBeVisible();
// Both SpaceFillers should have the same flex properties
await expect(spaceFiller1).toHaveCSS("flex", "1 1 0px");
await expect(spaceFiller2).toHaveCSS("flex", "1 1 0px");
});
test("ignores layout properties", async ({ initTestBed, page }) => {
await initTestBed(`
<HStack>
<SpaceFiller testId="spacer" width="100px" height="100px" backgroundColor="red" padding="20px"/>
</HStack>
`);
const spaceFiller = page.getByTestId("spacer");
await expect(spaceFiller).toBeAttached();
// SpaceFiller should not apply any styling properties
await expect(spaceFiller).not.toHaveCSS("width", "100px");
await expect(spaceFiller).not.toHaveCSS("height", "100px");
await expect(spaceFiller).not.toHaveCSS("background-color", "rgb(255, 0, 0)");
await expect(spaceFiller).not.toHaveCSS("padding", "20px");
// But it should still have its core flex properties
await expect(spaceFiller).toHaveCSS("flex", "1 1 0px");
});
});
// =============================================================================
// OTHER EDGE CASE TESTS
// =============================================================================
test.describe("Other Edge Cases", () => {
test("renders correctly when nested in multiple containers", async ({ initTestBed, page }) => {
await initTestBed(`
<VStack>
<HStack>
<Stack width="36px" height="36px" backgroundColor="red"/>
<SpaceFiller testId="spacer1"/>
<Stack width="36px" height="36px" backgroundColor="blue"/>
</HStack>
<SpaceFiller testId="spacer2"/>
<HStack>
<Stack width="36px" height="36px" backgroundColor="green"/>
</HStack>
</VStack>
`);
const spaceFiller1 = page.getByTestId("spacer1");
const spaceFiller2 = page.getByTestId("spacer2");
await expect(spaceFiller1).toBeAttached();
await expect(spaceFiller2).toBeAttached();
// Both should have the correct CSS properties
await expect(spaceFiller1).toHaveCSS("flex", "1 1 0px");
await expect(spaceFiller2).toHaveCSS("flex", "1 1 0px");
});
test("works in containers with no other children", async ({ initTestBed, page }) => {
await initTestBed(`
<HStack>
<SpaceFiller testId="spacer"/>
</HStack>
`);
const spaceFiller = page.getByTestId("spacer");
await expect(spaceFiller).toBeAttached();
await expect(spaceFiller).toHaveCSS("flex", "1 1 0px");
await expect(spaceFiller).toHaveCSS("place-self", "stretch");
});
test("handles container with different alignment settings", async ({ initTestBed, page }) => {
await initTestBed(`
<HStack justifyContent="center" alignItems="flex-end">
<Stack width="36px" height="36px" backgroundColor="red"/>
<SpaceFiller testId="spacer"/>
<Stack width="36px" height="36px" backgroundColor="blue"/>
</HStack>
`);
const spaceFiller = page.getByTestId("spacer");
await expect(spaceFiller).toBeAttached();
// SpaceFiller should maintain its flex properties regardless of parent alignment
await expect(spaceFiller).toHaveCSS("flex", "1 1 0px");
await expect(spaceFiller).toHaveCSS("place-self", "stretch");
});
});
```
--------------------------------------------------------------------------------
/xmlui/src/components/TimeInput/utils.ts:
--------------------------------------------------------------------------------
```typescript
// Merged utility functions for TimeInput component
import getUserLocale from 'get-user-locale';
// ============================================================================
// Types (from types.ts)
// ============================================================================
export type Range<T> = [T, T];
export type AmPmType = 'am' | 'pm';
export type ClassName = string | null | undefined | (string | null | undefined)[];
export type Detail = 'hour' | 'minute' | 'second';
export type LooseValuePiece = string | Date | null;
export type LooseValue = LooseValuePiece | Range<LooseValuePiece>;
export type Value = string | null;
// ============================================================================
// Date Formatter utilities (from dateFormatter.ts)
// ============================================================================
const formatterCache = new Map();
export function getFormatter(
options: Intl.DateTimeFormatOptions,
): (locale: string | undefined, date: Date) => string {
return function formatter(locale: string | undefined, date: Date): string {
const localeWithDefault = locale || getUserLocale();
if (!formatterCache.has(localeWithDefault)) {
formatterCache.set(localeWithDefault, new Map());
}
const formatterCacheLocale = formatterCache.get(localeWithDefault);
if (!formatterCacheLocale.has(options)) {
formatterCacheLocale.set(
options,
new Intl.DateTimeFormat(localeWithDefault || undefined, options).format,
);
}
return formatterCacheLocale.get(options)(date);
};
}
const numberFormatterCache = new Map();
export function getNumberFormatter(
options: Intl.NumberFormatOptions,
): (locale: string | undefined, number: number) => string {
return (locale: string | undefined, number: number): string => {
const localeWithDefault = locale || getUserLocale();
if (!numberFormatterCache.has(localeWithDefault)) {
numberFormatterCache.set(localeWithDefault, new Map());
}
const numberFormatterCacheLocale = numberFormatterCache.get(localeWithDefault);
if (!numberFormatterCacheLocale.has(options)) {
numberFormatterCacheLocale.set(
options,
new Intl.NumberFormat(localeWithDefault || undefined, options).format,
);
}
return numberFormatterCacheLocale.get(options)(number);
};
}
// ============================================================================
// Date utilities (from dateUtils.ts)
// ============================================================================
export function getHours(dateOrTimeString: string | Date | null | undefined): number {
if (!dateOrTimeString) return 0;
if (dateOrTimeString instanceof Date) {
return dateOrTimeString.getHours();
}
// Handle time string format like "14:30:45" or "14:30"
const timeString = String(dateOrTimeString);
const timeParts = timeString.split(':');
if (timeParts.length >= 1) {
const hours = parseInt(timeParts[0], 10);
return isNaN(hours) ? 0 : hours;
}
return 0;
}
export function getMinutes(dateOrTimeString: string | Date | null | undefined): number {
if (!dateOrTimeString) return 0;
if (dateOrTimeString instanceof Date) {
return dateOrTimeString.getMinutes();
}
// Handle time string format like "14:30:45" or "14:30"
const timeString = String(dateOrTimeString);
const timeParts = timeString.split(':');
if (timeParts.length >= 2) {
const minutes = parseInt(timeParts[1], 10);
return isNaN(minutes) ? 0 : minutes;
}
return 0;
}
export function getSeconds(dateOrTimeString: string | Date | null | undefined): number {
if (!dateOrTimeString) return 0;
if (dateOrTimeString instanceof Date) {
return dateOrTimeString.getSeconds();
}
// Handle time string format like "14:30:45"
const timeString = String(dateOrTimeString);
const timeParts = timeString.split(':');
if (timeParts.length >= 3) {
const seconds = parseInt(timeParts[2], 10);
return isNaN(seconds) ? 0 : seconds;
}
return 0;
}
export function getHoursMinutes(dateOrTimeString: string | Date | null | undefined): string {
if (!dateOrTimeString) return '';
if (dateOrTimeString instanceof Date) {
const hours = dateOrTimeString.getHours().toString().padStart(2, '0');
const minutes = dateOrTimeString.getMinutes().toString().padStart(2, '0');
return `${hours}:${minutes}`;
}
// Handle time string - return first two components
const timeString = String(dateOrTimeString);
const timeParts = timeString.split(':');
if (timeParts.length >= 2) {
const hours = parseInt(timeParts[0], 10);
const minutes = parseInt(timeParts[1], 10);
if (!isNaN(hours) && !isNaN(minutes)) {
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
}
}
return timeString;
}
export function getHoursMinutesSeconds(dateOrTimeString: string | Date | null | undefined): string {
if (!dateOrTimeString) return '';
if (dateOrTimeString instanceof Date) {
const hours = dateOrTimeString.getHours().toString().padStart(2, '0');
const minutes = dateOrTimeString.getMinutes().toString().padStart(2, '0');
const seconds = dateOrTimeString.getSeconds().toString().padStart(2, '0');
return `${hours}:${minutes}:${seconds}`;
}
// Handle time string - ensure it has three components
const timeString = String(dateOrTimeString);
const timeParts = timeString.split(':');
if (timeParts.length >= 3) {
const hours = parseInt(timeParts[0], 10);
const minutes = parseInt(timeParts[1], 10);
const seconds = parseInt(timeParts[2], 10);
if (!isNaN(hours) && !isNaN(minutes) && !isNaN(seconds)) {
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
}
} else if (timeParts.length === 2) {
// Add seconds if missing
const hours = parseInt(timeParts[0], 10);
const minutes = parseInt(timeParts[1], 10);
if (!isNaN(hours) && !isNaN(minutes)) {
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:00`;
}
}
return timeString;
}
// ============================================================================
// Date conversion utilities (from dates.ts)
// ============================================================================
export function convert12to24(hour12: string | number, amPm: AmPmType): number {
let hour24 = Number(hour12);
if (amPm === 'am' && hour24 === 12) {
hour24 = 0;
} else if (amPm === 'pm' && hour24 < 12) {
hour24 += 12;
}
return hour24;
}
export function convert24to12(hour24: string | number): [number, AmPmType] {
const hour12 = Number(hour24) % 12 || 12;
return [hour12, Number(hour24) < 12 ? 'am' : 'pm'];
}
// ============================================================================
// General utilities (from utils.ts)
// ============================================================================
const nines = ['9', '٩'];
const ninesRegExp = new RegExp(`[${nines.join('')}]`);
const amPmFormatter = getFormatter({ hour: 'numeric' });
export function getAmPmLabels(locale: string | undefined): [string, string] {
const amString = amPmFormatter(locale, new Date(2017, 0, 1, 9));
const pmString = amPmFormatter(locale, new Date(2017, 0, 1, 21));
const [am1, am2] = amString.split(ninesRegExp) as [string, string];
const [pm1, pm2] = pmString.split(ninesRegExp) as [string, string];
if (pm2 !== undefined) {
// If pm2 is undefined, nine was not found in pmString - this locale is not using 12-hour time
if (am1 !== pm1) {
return [am1, pm1].map((el) => el.trim()) as [string, string];
}
if (am2 !== pm2) {
return [am2, pm2].map((el) => el.trim()) as [string, string];
}
}
// Fallback
return ['AM', 'PM'];
}
function isValidNumber(num: unknown): num is number {
return num !== null && num !== false && !Number.isNaN(Number(num));
}
export function safeMin(...args: unknown[]): number {
return Math.min(...args.filter(isValidNumber));
}
export function safeMax(...args: unknown[]): number {
return Math.max(...args.filter(isValidNumber));
}
```