This is page 44 of 186. Use http://codebase.md/xmlui-org/xmlui/xmlui/mockApiDef.js?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .changeset
│ ├── config.json
│ └── cyan-tools-design.md
├── .eslintrc.cjs
├── .github
│ ├── build-checklist.png
│ ├── ISSUE_TEMPLATE
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ └── workflows
│ ├── deploy-blog-optimized.yml
│ ├── deploy-blog-swa.yml
│ ├── deploy-blog.yml
│ ├── deploy-docs-optimized.yml
│ ├── deploy-docs-swa.yml
│ ├── deploy-docs.yml
│ ├── prepare-versions.yml
│ ├── release-packages.yml
│ ├── run-all-tests.yml
│ └── run-smoke-tests.yml
├── .gitignore
├── .prettierrc.js
├── .vscode
│ ├── launch.json
│ └── settings.json
├── blog
│ ├── .gitignore
│ ├── .gitkeep
│ ├── CHANGELOG.md
│ ├── extensions.ts
│ ├── index.html
│ ├── index.ts
│ ├── package.json
│ ├── public
│ │ ├── blog
│ │ │ ├── images
│ │ │ │ ├── an-advanced-codefence.gif
│ │ │ │ ├── an-advanced-codefence.mp4
│ │ │ │ ├── blog-page-component.png
│ │ │ │ ├── blog-scrabble.png
│ │ │ │ ├── codefence-runner.png
│ │ │ │ ├── integrated-blog-search.png
│ │ │ │ ├── lorem-ipsum.png
│ │ │ │ ├── playground-checkbox-source.png
│ │ │ │ ├── playground.png
│ │ │ │ ├── use-xmlui-mcp-to-find-a-howto.png
│ │ │ │ └── xmlui-demo-gallery.png
│ │ │ ├── introducing-xmlui.md
│ │ │ ├── lorem-ipsum.md
│ │ │ ├── newest-post.md
│ │ │ ├── older-post.md
│ │ │ ├── xmlui-playground.md
│ │ │ └── xmlui-powered-blog.md
│ │ ├── mockServiceWorker.js
│ │ ├── resources
│ │ │ ├── favicon.ico
│ │ │ ├── files
│ │ │ │ └── for-download
│ │ │ │ └── xmlui
│ │ │ │ └── xmlui-standalone.umd.js
│ │ │ ├── github.svg
│ │ │ ├── llms.txt
│ │ │ ├── logo-dark.svg
│ │ │ ├── logo.svg
│ │ │ ├── pg-popout.svg
│ │ │ ├── rss.svg
│ │ │ └── xmlui-logo.svg
│ │ ├── serve.json
│ │ ├── staticwebapp.config.json
│ │ └── web.config
│ ├── scripts
│ │ ├── download-latest-xmlui.js
│ │ ├── generate-rss.js
│ │ ├── get-releases.js
│ │ └── utils.js
│ ├── src
│ │ ├── components
│ │ │ ├── BlogOverview.xmlui
│ │ │ ├── BlogPage.xmlui
│ │ │ └── PageNotFound.xmlui
│ │ ├── config.ts
│ │ ├── Main.xmlui
│ │ └── themes
│ │ └── blog-theme.ts
│ └── tsconfig.json
├── CONTRIBUTING.md
├── docs
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── ComponentRefLinks.txt
│ ├── content
│ │ ├── _meta.json
│ │ ├── components
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── APICall.md
│ │ │ ├── App.md
│ │ │ ├── AppHeader.md
│ │ │ ├── AppState.md
│ │ │ ├── AutoComplete.md
│ │ │ ├── Avatar.md
│ │ │ ├── Backdrop.md
│ │ │ ├── Badge.md
│ │ │ ├── BarChart.md
│ │ │ ├── Bookmark.md
│ │ │ ├── Breakout.md
│ │ │ ├── Button.md
│ │ │ ├── Card.md
│ │ │ ├── Carousel.md
│ │ │ ├── ChangeListener.md
│ │ │ ├── Checkbox.md
│ │ │ ├── CHStack.md
│ │ │ ├── ColorPicker.md
│ │ │ ├── Column.md
│ │ │ ├── ContentSeparator.md
│ │ │ ├── CVStack.md
│ │ │ ├── DataSource.md
│ │ │ ├── DateInput.md
│ │ │ ├── DatePicker.md
│ │ │ ├── DonutChart.md
│ │ │ ├── DropdownMenu.md
│ │ │ ├── EmojiSelector.md
│ │ │ ├── ExpandableItem.md
│ │ │ ├── FileInput.md
│ │ │ ├── FileUploadDropZone.md
│ │ │ ├── FlowLayout.md
│ │ │ ├── Footer.md
│ │ │ ├── Form.md
│ │ │ ├── FormItem.md
│ │ │ ├── FormSection.md
│ │ │ ├── Fragment.md
│ │ │ ├── H1.md
│ │ │ ├── H2.md
│ │ │ ├── H3.md
│ │ │ ├── H4.md
│ │ │ ├── H5.md
│ │ │ ├── H6.md
│ │ │ ├── Heading.md
│ │ │ ├── HSplitter.md
│ │ │ ├── HStack.md
│ │ │ ├── Icon.md
│ │ │ ├── IFrame.md
│ │ │ ├── Image.md
│ │ │ ├── Items.md
│ │ │ ├── LabelList.md
│ │ │ ├── Legend.md
│ │ │ ├── LineChart.md
│ │ │ ├── Link.md
│ │ │ ├── List.md
│ │ │ ├── Logo.md
│ │ │ ├── Markdown.md
│ │ │ ├── MenuItem.md
│ │ │ ├── MenuSeparator.md
│ │ │ ├── ModalDialog.md
│ │ │ ├── NavGroup.md
│ │ │ ├── NavLink.md
│ │ │ ├── NavPanel.md
│ │ │ ├── NoResult.md
│ │ │ ├── NumberBox.md
│ │ │ ├── Option.md
│ │ │ ├── Page.md
│ │ │ ├── PageMetaTitle.md
│ │ │ ├── Pages.md
│ │ │ ├── Pagination.md
│ │ │ ├── PasswordInput.md
│ │ │ ├── PieChart.md
│ │ │ ├── ProgressBar.md
│ │ │ ├── Queue.md
│ │ │ ├── RadioGroup.md
│ │ │ ├── RealTimeAdapter.md
│ │ │ ├── Redirect.md
│ │ │ ├── Select.md
│ │ │ ├── Slider.md
│ │ │ ├── Slot.md
│ │ │ ├── SpaceFiller.md
│ │ │ ├── Spinner.md
│ │ │ ├── Splitter.md
│ │ │ ├── Stack.md
│ │ │ ├── StickyBox.md
│ │ │ ├── SubMenuItem.md
│ │ │ ├── Switch.md
│ │ │ ├── TabItem.md
│ │ │ ├── Table.md
│ │ │ ├── TableOfContents.md
│ │ │ ├── Tabs.md
│ │ │ ├── Text.md
│ │ │ ├── TextArea.md
│ │ │ ├── TextBox.md
│ │ │ ├── Theme.md
│ │ │ ├── TimeInput.md
│ │ │ ├── Timer.md
│ │ │ ├── ToneChangerButton.md
│ │ │ ├── ToneSwitch.md
│ │ │ ├── Tooltip.md
│ │ │ ├── Tree.md
│ │ │ ├── VSplitter.md
│ │ │ ├── VStack.md
│ │ │ ├── xmlui-animations
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── Animation.md
│ │ │ │ ├── FadeAnimation.md
│ │ │ │ ├── FadeInAnimation.md
│ │ │ │ ├── FadeOutAnimation.md
│ │ │ │ ├── ScaleAnimation.md
│ │ │ │ └── SlideInAnimation.md
│ │ │ ├── xmlui-pdf
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Pdf.md
│ │ │ ├── xmlui-spreadsheet
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Spreadsheet.md
│ │ │ └── xmlui-website-blocks
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── Carousel.md
│ │ │ ├── HelloMd.md
│ │ │ ├── HeroSection.md
│ │ │ └── ScrollToTop.md
│ │ └── extensions
│ │ ├── _meta.json
│ │ ├── xmlui-animations
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── Animation.md
│ │ │ ├── FadeAnimation.md
│ │ │ ├── FadeInAnimation.md
│ │ │ ├── FadeOutAnimation.md
│ │ │ ├── ScaleAnimation.md
│ │ │ └── SlideInAnimation.md
│ │ └── xmlui-website-blocks
│ │ ├── _meta.json
│ │ ├── _overview.md
│ │ ├── Carousel.md
│ │ ├── FancyButton.md
│ │ ├── HeroSection.md
│ │ └── ScrollToTop.md
│ ├── extensions.ts
│ ├── index.html
│ ├── index.ts
│ ├── package.json
│ ├── public
│ │ ├── feed.rss
│ │ ├── mockServiceWorker.js
│ │ ├── pages
│ │ │ ├── _meta.json
│ │ │ ├── app-structure.md
│ │ │ ├── build-editor-component.md
│ │ │ ├── build-hello-world-component.md
│ │ │ ├── components-intro.md
│ │ │ ├── context-variables.md
│ │ │ ├── forms.md
│ │ │ ├── globals.md
│ │ │ ├── glossary.md
│ │ │ ├── helper-tags.md
│ │ │ ├── hosted-deployment.md
│ │ │ ├── howto
│ │ │ │ ├── assign-a-complex-json-literal-to-a-component-variable.md
│ │ │ │ ├── chain-a-refetch.md
│ │ │ │ ├── control-cache-invalidation.md
│ │ │ │ ├── debounce-user-input-for-api-calls.md
│ │ │ │ ├── debounce-with-changelistener.md
│ │ │ │ ├── debug-a-component.md
│ │ │ │ ├── delay-a-datasource-until-another-datasource-is-ready.md
│ │ │ │ ├── delegate-a-method.md
│ │ │ │ ├── do-custom-form-validation.md
│ │ │ │ ├── expose-a-method-from-a-component.md
│ │ │ │ ├── filter-and-transform-data-from-an-api.md
│ │ │ │ ├── group-items-in-list-by-a-property.md
│ │ │ │ ├── handle-background-operations.md
│ │ │ │ ├── hide-an-element-until-its-datasource-is-ready.md
│ │ │ │ ├── make-a-set-of-equal-width-cards.md
│ │ │ │ ├── make-a-table-responsive.md
│ │ │ │ ├── make-navpanel-width-responsive.md
│ │ │ │ ├── modify-a-value-reported-in-a-column.md
│ │ │ │ ├── paginate-a-list.md
│ │ │ │ ├── pass-data-to-a-modal-dialog.md
│ │ │ │ ├── react-to-button-click-not-keystrokes.md
│ │ │ │ ├── set-the-initial-value-of-a-select-from-fetched-data.md
│ │ │ │ ├── share-a-modaldialog-across-components.md
│ │ │ │ ├── sync-selections-between-table-and-list-views.md
│ │ │ │ ├── update-ui-optimistically.md
│ │ │ │ ├── use-built-in-form-validation.md
│ │ │ │ └── use-the-same-modaldialog-to-add-or-edit.md
│ │ │ ├── howto.md
│ │ │ ├── intro.md
│ │ │ ├── layout.md
│ │ │ ├── markup.md
│ │ │ ├── mcp.md
│ │ │ ├── modal-dialogs.md
│ │ │ ├── news-and-reviews.md
│ │ │ ├── reactive-intro.md
│ │ │ ├── refactoring.md
│ │ │ ├── routing-and-links.md
│ │ │ ├── samples
│ │ │ │ ├── color-palette.xmlui
│ │ │ │ ├── color-values.xmlui
│ │ │ │ ├── shadow-sizes.xmlui
│ │ │ │ ├── spacing-sizes.xmlui
│ │ │ │ ├── swatch.xmlui
│ │ │ │ ├── theme-gallery-brief.xmlui
│ │ │ │ └── theme-gallery.xmlui
│ │ │ ├── scoping.md
│ │ │ ├── scripting.md
│ │ │ ├── styles-and-themes
│ │ │ │ ├── common-units.md
│ │ │ │ ├── layout-props.md
│ │ │ │ ├── theme-variable-defaults.md
│ │ │ │ ├── theme-variables.md
│ │ │ │ └── themes.md
│ │ │ ├── template-properties.md
│ │ │ ├── test.md
│ │ │ ├── tutorial-01.md
│ │ │ ├── tutorial-02.md
│ │ │ ├── tutorial-03.md
│ │ │ ├── tutorial-04.md
│ │ │ ├── tutorial-05.md
│ │ │ ├── tutorial-06.md
│ │ │ ├── tutorial-07.md
│ │ │ ├── tutorial-08.md
│ │ │ ├── tutorial-09.md
│ │ │ ├── tutorial-10.md
│ │ │ ├── tutorial-11.md
│ │ │ ├── tutorial-12.md
│ │ │ ├── universal-properties.md
│ │ │ ├── user-defined-components.md
│ │ │ ├── vscode.md
│ │ │ ├── working-with-markdown.md
│ │ │ ├── working-with-text.md
│ │ │ ├── xmlui-animations
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── Animation.md
│ │ │ │ ├── FadeAnimation.md
│ │ │ │ ├── FadeInAnimation.md
│ │ │ │ ├── FadeOutAnimation.md
│ │ │ │ ├── ScaleAnimation.md
│ │ │ │ └── SlideInAnimation.md
│ │ │ ├── xmlui-charts
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── BarChart.md
│ │ │ │ ├── DonutChart.md
│ │ │ │ ├── LabelList.md
│ │ │ │ ├── Legend.md
│ │ │ │ ├── LineChart.md
│ │ │ │ └── PieChart.md
│ │ │ ├── xmlui-pdf
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Pdf.md
│ │ │ └── xmlui-spreadsheet
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ └── Spreadsheet.md
│ │ ├── resources
│ │ │ ├── devdocs
│ │ │ │ ├── debug-proxy-object-2.png
│ │ │ │ ├── debug-proxy-object.png
│ │ │ │ ├── table_editor_01.png
│ │ │ │ ├── table_editor_02.png
│ │ │ │ ├── table_editor_03.png
│ │ │ │ ├── table_editor_04.png
│ │ │ │ ├── table_editor_05.png
│ │ │ │ ├── table_editor_06.png
│ │ │ │ ├── table_editor_07.png
│ │ │ │ ├── table_editor_08.png
│ │ │ │ ├── table_editor_09.png
│ │ │ │ ├── table_editor_10.png
│ │ │ │ ├── table_editor_11.png
│ │ │ │ ├── table-editor-01.png
│ │ │ │ ├── table-editor-02.png
│ │ │ │ ├── table-editor-03.png
│ │ │ │ ├── table-editor-04.png
│ │ │ │ ├── table-editor-06.png
│ │ │ │ ├── table-editor-07.png
│ │ │ │ ├── table-editor-08.png
│ │ │ │ ├── table-editor-09.png
│ │ │ │ └── xmlui-rendering-of-tiptap-markdown.png
│ │ │ ├── favicon.ico
│ │ │ ├── files
│ │ │ │ ├── clients.json
│ │ │ │ ├── daily-revenue.json
│ │ │ │ ├── dashboard-stats.json
│ │ │ │ ├── demo.xmlui
│ │ │ │ ├── demo.xmlui.xs
│ │ │ │ ├── downloads
│ │ │ │ │ └── downloads.json
│ │ │ │ ├── for-download
│ │ │ │ │ ├── index-with-api.html
│ │ │ │ │ ├── index.html
│ │ │ │ │ ├── mockApi.js
│ │ │ │ │ ├── start-darwin.sh
│ │ │ │ │ ├── start-linux.sh
│ │ │ │ │ ├── start.bat
│ │ │ │ │ └── xmlui
│ │ │ │ │ └── xmlui-standalone.umd.js
│ │ │ │ ├── getting-started
│ │ │ │ │ ├── cl-tutorial-final.zip
│ │ │ │ │ ├── cl-tutorial.zip
│ │ │ │ │ ├── cl-tutorial2.zip
│ │ │ │ │ ├── cl-tutorial3.zip
│ │ │ │ │ ├── cl-tutorial4.zip
│ │ │ │ │ ├── cl-tutorial5.zip
│ │ │ │ │ ├── cl-tutorial6.zip
│ │ │ │ │ ├── getting-started.zip
│ │ │ │ │ ├── hello-xmlui.zip
│ │ │ │ │ ├── xmlui-empty.zip
│ │ │ │ │ └── xmlui-starter.zip
│ │ │ │ ├── howto
│ │ │ │ │ └── component-icons
│ │ │ │ │ └── up-arrow.svg
│ │ │ │ ├── invoices.json
│ │ │ │ ├── monthly-status.json
│ │ │ │ ├── news-and-reviews.json
│ │ │ │ ├── products.json
│ │ │ │ ├── releases.json
│ │ │ │ ├── tutorials
│ │ │ │ │ ├── datasource
│ │ │ │ │ │ └── api.ts
│ │ │ │ │ └── p2do
│ │ │ │ │ ├── api.ts
│ │ │ │ │ └── todo-logo.svg
│ │ │ │ └── xmlui.json
│ │ │ ├── github.svg
│ │ │ ├── images
│ │ │ │ ├── apiaction-tutorial
│ │ │ │ │ ├── add-success.png
│ │ │ │ │ ├── apiaction-param.png
│ │ │ │ │ ├── change-completed.png
│ │ │ │ │ ├── change-in-progress.png
│ │ │ │ │ ├── confirm-delete.png
│ │ │ │ │ ├── data-error.png
│ │ │ │ │ ├── data-progress.png
│ │ │ │ │ ├── data-success.png
│ │ │ │ │ ├── display-1.png
│ │ │ │ │ ├── item-deleted.png
│ │ │ │ │ ├── item-updated.png
│ │ │ │ │ ├── missing-api-key.png
│ │ │ │ │ ├── new-item-added.png
│ │ │ │ │ └── test-message.png
│ │ │ │ ├── chat-api
│ │ │ │ │ └── domain-model.svg
│ │ │ │ ├── components
│ │ │ │ │ ├── image
│ │ │ │ │ │ └── breakfast.jpg
│ │ │ │ │ ├── markdown
│ │ │ │ │ │ └── colors.png
│ │ │ │ │ └── modal
│ │ │ │ │ ├── deep_link_dialog_1.jpg
│ │ │ │ │ └── deep_link_dialog_2.jpg
│ │ │ │ ├── create-apps
│ │ │ │ │ ├── collapsed-vertical.png
│ │ │ │ │ ├── using-forms-warning-dialog.png
│ │ │ │ │ └── using-forms.png
│ │ │ │ ├── datasource-tutorial
│ │ │ │ │ ├── data-with-header.png
│ │ │ │ │ ├── filtered-data.png
│ │ │ │ │ ├── filtered-items.png
│ │ │ │ │ ├── initial-page-items.png
│ │ │ │ │ ├── list-items.png
│ │ │ │ │ ├── next-page-items.png
│ │ │ │ │ ├── no-data.png
│ │ │ │ │ ├── pagination-1.jpg
│ │ │ │ │ ├── pagination-1.png
│ │ │ │ │ ├── polling-1.png
│ │ │ │ │ ├── refetch-data.png
│ │ │ │ │ ├── slow-loading.png
│ │ │ │ │ ├── test-message.png
│ │ │ │ │ ├── Thumbs.db
│ │ │ │ │ ├── unconventional-data.png
│ │ │ │ │ └── unfiltered-items.png
│ │ │ │ ├── flower.jpg
│ │ │ │ ├── get-started
│ │ │ │ │ ├── add-new-contact.png
│ │ │ │ │ ├── app-modified.png
│ │ │ │ │ ├── app-start.png
│ │ │ │ │ ├── app-with-boxes.png
│ │ │ │ │ ├── app-with-toast.png
│ │ │ │ │ ├── boilerplate-structure.png
│ │ │ │ │ ├── cl-initial.png
│ │ │ │ │ ├── cl-start.png
│ │ │ │ │ ├── contact-counts.png
│ │ │ │ │ ├── contact-dialog-title.png
│ │ │ │ │ ├── contact-dialog.png
│ │ │ │ │ ├── contact-menus.png
│ │ │ │ │ ├── contact-predicates.png
│ │ │ │ │ ├── context-menu.png
│ │ │ │ │ ├── dashboard-numbers.png
│ │ │ │ │ ├── default-contact-list.png
│ │ │ │ │ ├── delete-contact.png
│ │ │ │ │ ├── delete-task.png
│ │ │ │ │ ├── detailed-template.png
│ │ │ │ │ ├── edit-contact-details.png
│ │ │ │ │ ├── edited-contact-saved.png
│ │ │ │ │ ├── empty-sections.png
│ │ │ │ │ ├── filter-completed.png
│ │ │ │ │ ├── fullwidth-desktop.png
│ │ │ │ │ ├── fullwidth-mobile.png
│ │ │ │ │ ├── initial-table.png
│ │ │ │ │ ├── items-and-badges.png
│ │ │ │ │ ├── loading-message.png
│ │ │ │ │ ├── new-contact-button.png
│ │ │ │ │ ├── new-contact-saved.png
│ │ │ │ │ ├── no-empty-sections.png
│ │ │ │ │ ├── personal-todo-initial.png
│ │ │ │ │ ├── piechart.png
│ │ │ │ │ ├── review-today.png
│ │ │ │ │ ├── rudimentary-dashboard.png
│ │ │ │ │ ├── section-collapsed.png
│ │ │ │ │ ├── sectioned-items.png
│ │ │ │ │ ├── sections-ordered.png
│ │ │ │ │ ├── spacex-list-with-links.png
│ │ │ │ │ ├── spacex-list.png
│ │ │ │ │ ├── start-personal-todo-1.png
│ │ │ │ │ ├── submit-new-contact.png
│ │ │ │ │ ├── submit-new-task.png
│ │ │ │ │ ├── syntax-highlighting.png
│ │ │ │ │ ├── table-with-badge.png
│ │ │ │ │ ├── template-with-card.png
│ │ │ │ │ ├── test-emulated-api.png
│ │ │ │ │ ├── Thumbs.db
│ │ │ │ │ ├── todo-logo.png
│ │ │ │ │ └── xmlui-tools.png
│ │ │ │ ├── HelloApp.png
│ │ │ │ ├── HelloApp2.png
│ │ │ │ ├── logos
│ │ │ │ │ ├── xmlui1.svg
│ │ │ │ │ ├── xmlui2.svg
│ │ │ │ │ ├── xmlui3.svg
│ │ │ │ │ ├── xmlui4.svg
│ │ │ │ │ ├── xmlui5.svg
│ │ │ │ │ ├── xmlui6.svg
│ │ │ │ │ └── xmlui7.svg
│ │ │ │ ├── pdf
│ │ │ │ │ └── dummy-pdf.jpg
│ │ │ │ ├── rendering-engine
│ │ │ │ │ ├── AppEngine-flow.svg
│ │ │ │ │ ├── Component.svg
│ │ │ │ │ ├── CompoundComponent.svg
│ │ │ │ │ ├── RootComponent.svg
│ │ │ │ │ └── tree-with-containers.svg
│ │ │ │ ├── reviewers-guide
│ │ │ │ │ ├── AppEngine-flow.svg
│ │ │ │ │ └── incbutton-in-action.png
│ │ │ │ ├── tools
│ │ │ │ │ └── boilerplate-structure.png
│ │ │ │ ├── try.svg
│ │ │ │ ├── tutorial
│ │ │ │ │ ├── app-chat-history.png
│ │ │ │ │ ├── app-content-placeholder.png
│ │ │ │ │ ├── app-header-and-content.png
│ │ │ │ │ ├── app-links-channel-selected.png
│ │ │ │ │ ├── app-links-click.png
│ │ │ │ │ ├── app-navigation.png
│ │ │ │ │ ├── finished-ex01.png
│ │ │ │ │ ├── finished-ex02.png
│ │ │ │ │ ├── hello.png
│ │ │ │ │ ├── splash-screen-advanced.png
│ │ │ │ │ ├── splash-screen-after-click.png
│ │ │ │ │ ├── splash-screen-centered.png
│ │ │ │ │ ├── splash-screen-events.png
│ │ │ │ │ ├── splash-screen-expression.png
│ │ │ │ │ ├── splash-screen-reuse-after.png
│ │ │ │ │ ├── splash-screen-reuse-before.png
│ │ │ │ │ └── splash-screen.png
│ │ │ │ └── tutorial-01.png
│ │ │ ├── llms.txt
│ │ │ ├── logo-dark.svg
│ │ │ ├── logo.svg
│ │ │ ├── pg-popout.svg
│ │ │ └── xmlui-logo.svg
│ │ ├── serve.json
│ │ └── web.config
│ ├── scripts
│ │ ├── download-latest-xmlui.js
│ │ ├── generate-rss.js
│ │ ├── get-releases.js
│ │ └── utils.js
│ ├── src
│ │ ├── components
│ │ │ ├── BlogOverview.xmlui
│ │ │ ├── BlogPage.xmlui
│ │ │ ├── Boxes.xmlui
│ │ │ ├── Breadcrumb.xmlui
│ │ │ ├── ChangeLog.xmlui
│ │ │ ├── ColorPalette.xmlui
│ │ │ ├── DocumentLinks.xmlui
│ │ │ ├── DocumentPage.xmlui
│ │ │ ├── DocumentPageNoTOC.xmlui
│ │ │ ├── Icons.xmlui
│ │ │ ├── IncButton.xmlui
│ │ │ ├── IncButton2.xmlui
│ │ │ ├── NameValue.xmlui
│ │ │ ├── PageNotFound.xmlui
│ │ │ ├── PaletteItem.xmlui
│ │ │ ├── Palettes.xmlui
│ │ │ ├── SectionHeader.xmlui
│ │ │ ├── TBD.xmlui
│ │ │ ├── Test.xmlui
│ │ │ ├── ThemesIntro.xmlui
│ │ │ ├── ThousandThemes.xmlui
│ │ │ ├── TubeStops.xmlui
│ │ │ ├── TubeStops.xmlui.xs
│ │ │ └── TwoColumnCode.xmlui
│ │ ├── config.ts
│ │ ├── Main.xmlui
│ │ └── themes
│ │ ├── docs-theme.ts
│ │ ├── earthtone.ts
│ │ ├── xmlui-gray-on-default.ts
│ │ ├── xmlui-green-on-default.ts
│ │ └── xmlui-orange-on-default.ts
│ └── tsconfig.json
├── LICENSE
├── package-lock.json
├── package.json
├── packages
│ ├── tsconfig.json
│ ├── xmlui-animations
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── Animation.tsx
│ │ ├── AnimationNative.tsx
│ │ ├── FadeAnimation.tsx
│ │ ├── FadeInAnimation.tsx
│ │ ├── FadeOutAnimation.tsx
│ │ ├── index.tsx
│ │ ├── ScaleAnimation.tsx
│ │ └── SlideInAnimation.tsx
│ ├── xmlui-devtools
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ ├── src
│ │ │ ├── devtools
│ │ │ │ ├── DevTools.tsx
│ │ │ │ ├── DevToolsNative.module.scss
│ │ │ │ ├── DevToolsNative.tsx
│ │ │ │ ├── ModalDialog.module.scss
│ │ │ │ ├── ModalDialog.tsx
│ │ │ │ ├── ModalVisibilityContext.tsx
│ │ │ │ ├── Tooltip.module.scss
│ │ │ │ ├── Tooltip.tsx
│ │ │ │ └── utils.ts
│ │ │ ├── editor
│ │ │ │ └── Editor.tsx
│ │ │ └── index.tsx
│ │ └── vite.config-overrides.ts
│ ├── xmlui-hello-world
│ │ ├── .gitignore
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── HelloWorld.module.scss
│ │ ├── HelloWorld.tsx
│ │ ├── HelloWorldNative.tsx
│ │ └── index.tsx
│ ├── xmlui-os-frames
│ │ ├── .gitignore
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── index.tsx
│ │ ├── IPhoneFrame.module.scss
│ │ ├── IPhoneFrame.tsx
│ │ ├── MacOSAppFrame.module.scss
│ │ ├── MacOSAppFrame.tsx
│ │ ├── WindowsAppFrame.module.scss
│ │ └── WindowsAppFrame.tsx
│ ├── xmlui-pdf
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ ├── components
│ │ │ │ └── Pdf.xmlui
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── index.tsx
│ │ ├── LazyPdfNative.tsx
│ │ ├── Pdf.module.scss
│ │ └── Pdf.tsx
│ ├── xmlui-playground
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── hooks
│ │ │ ├── usePlayground.ts
│ │ │ └── useToast.ts
│ │ ├── index.tsx
│ │ ├── playground
│ │ │ ├── Box.module.scss
│ │ │ ├── Box.tsx
│ │ │ ├── CodeSelector.module.scss
│ │ │ ├── CodeSelector.tsx
│ │ │ ├── ConfirmationDialog.module.scss
│ │ │ ├── ConfirmationDialog.tsx
│ │ │ ├── Editor.tsx
│ │ │ ├── Header.module.scss
│ │ │ ├── Header.tsx
│ │ │ ├── Playground.tsx
│ │ │ ├── PlaygroundContent.module.scss
│ │ │ ├── PlaygroundContent.tsx
│ │ │ ├── PlaygroundNative.module.scss
│ │ │ ├── PlaygroundNative.tsx
│ │ │ ├── Preview.tsx
│ │ │ ├── StandalonePlayground.tsx
│ │ │ ├── StandalonePlaygroundNative.module.scss
│ │ │ ├── StandalonePlaygroundNative.tsx
│ │ │ ├── ThemeSwitcher.module.scss
│ │ │ ├── ThemeSwitcher.tsx
│ │ │ └── utils.ts
│ │ ├── providers
│ │ │ ├── Toast.module.scss
│ │ │ └── ToastProvider.tsx
│ │ ├── state
│ │ │ └── store.ts
│ │ ├── themes
│ │ │ └── theme.ts
│ │ └── utils
│ │ └── helpers.ts
│ ├── xmlui-search
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── index.tsx
│ │ ├── Search.module.scss
│ │ └── Search.tsx
│ ├── xmlui-spreadsheet
│ │ ├── .gitignore
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── index.tsx
│ │ ├── Spreadsheet.tsx
│ │ └── SpreadsheetNative.tsx
│ └── xmlui-website-blocks
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── demo
│ │ ├── components
│ │ │ ├── HeroBackgroundBreakoutPage.xmlui
│ │ │ ├── HeroBackgroundsPage.xmlui
│ │ │ ├── HeroContentsPage.xmlui
│ │ │ ├── HeroTextAlignPage.xmlui
│ │ │ ├── HeroTextPage.xmlui
│ │ │ └── HeroTonesPage.xmlui
│ │ ├── Main.xmlui
│ │ └── themes
│ │ └── default.ts
│ ├── index.html
│ ├── index.ts
│ ├── meta
│ │ └── componentsMetadata.ts
│ ├── package.json
│ ├── public
│ │ └── resources
│ │ ├── building.jpg
│ │ └── xmlui-logo.svg
│ └── src
│ ├── Carousel
│ │ ├── Carousel.module.scss
│ │ ├── Carousel.tsx
│ │ ├── CarouselContext.tsx
│ │ └── CarouselNative.tsx
│ ├── FancyButton
│ │ ├── FancyButton.module.scss
│ │ ├── FancyButton.tsx
│ │ └── FancyButton.xmlui
│ ├── Hello
│ │ ├── Hello.tsx
│ │ ├── Hello.xmlui
│ │ └── Hello.xmlui.xs
│ ├── HeroSection
│ │ ├── HeroSection.module.scss
│ │ ├── HeroSection.spec.ts
│ │ ├── HeroSection.tsx
│ │ └── HeroSectionNative.tsx
│ ├── index.tsx
│ ├── ScrollToTop
│ │ ├── ScrollToTop.module.scss
│ │ ├── ScrollToTop.tsx
│ │ └── ScrollToTopNative.tsx
│ └── vite-env.d.ts
├── playwright.config.ts
├── README.md
├── tools
│ ├── codefence
│ │ └── xmlui-code-fence-docs.md
│ ├── create-app
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── create-app.ts
│ │ ├── helpers
│ │ │ ├── copy.ts
│ │ │ ├── get-pkg-manager.ts
│ │ │ ├── git.ts
│ │ │ ├── install.ts
│ │ │ ├── is-folder-empty.ts
│ │ │ ├── is-writeable.ts
│ │ │ ├── make-dir.ts
│ │ │ └── validate-pkg.ts
│ │ ├── index.ts
│ │ ├── package.json
│ │ ├── templates
│ │ │ ├── default
│ │ │ │ └── ts
│ │ │ │ ├── gitignore
│ │ │ │ ├── index.html
│ │ │ │ ├── index.ts
│ │ │ │ ├── public
│ │ │ │ │ ├── mockServiceWorker.js
│ │ │ │ │ ├── resources
│ │ │ │ │ │ ├── favicon.ico
│ │ │ │ │ │ └── xmlui-logo.svg
│ │ │ │ │ └── serve.json
│ │ │ │ └── src
│ │ │ │ ├── components
│ │ │ │ │ ├── ApiAware.xmlui
│ │ │ │ │ ├── Home.xmlui
│ │ │ │ │ ├── IncButton.xmlui
│ │ │ │ │ └── PagePanel.xmlui
│ │ │ │ ├── config.ts
│ │ │ │ └── Main.xmlui
│ │ │ ├── index.ts
│ │ │ └── types.ts
│ │ └── tsconfig.json
│ ├── create-xmlui-hello-world
│ │ ├── index.js
│ │ └── package.json
│ └── vscode
│ ├── .gitignore
│ ├── .vscode
│ │ ├── launch.json
│ │ └── tasks.json
│ ├── .vscodeignore
│ ├── build.sh
│ ├── CHANGELOG.md
│ ├── esbuild.js
│ ├── eslint.config.mjs
│ ├── formatter-docs.md
│ ├── generate-test-sample.sh
│ ├── LICENSE.md
│ ├── package-lock.json
│ ├── package.json
│ ├── README.md
│ ├── resources
│ │ ├── xmlui-logo.png
│ │ └── xmlui-markup-syntax-highlighting.png
│ ├── src
│ │ ├── extension.ts
│ │ └── server.ts
│ ├── syntaxes
│ │ └── xmlui.tmLanguage.json
│ ├── test-samples
│ │ └── sample.xmlui
│ ├── tsconfig.json
│ └── tsconfig.tsbuildinfo
├── turbo.json
└── xmlui
├── .gitignore
├── bin
│ ├── bootstrap.cjs
│ ├── bootstrap.js
│ ├── build-lib.ts
│ ├── build.ts
│ ├── index.ts
│ ├── preview.ts
│ ├── start.ts
│ ├── vite-xmlui-plugin.ts
│ └── viteConfig.ts
├── CHANGELOG.md
├── conventions
│ ├── component-qa-checklist.md
│ ├── copilot-conventions.md
│ ├── create-xmlui-components.md
│ ├── mermaid.md
│ ├── testing-conventions.md
│ └── xmlui-in-a-nutshell.md
├── dev-docs
│ ├── accessibility.md
│ ├── build-system.md
│ ├── build-xmlui.md
│ ├── component-behaviors.md
│ ├── component-metadata.md
│ ├── components-with-options.md
│ ├── containers.md
│ ├── data-operations.md
│ ├── glossary.md
│ ├── index.md
│ ├── next
│ │ ├── component-dev-guide.md
│ │ ├── configuration-management-enhancement-summary.md
│ │ ├── documentation-scripts-refactoring-complete-summary.md
│ │ ├── documentation-scripts-refactoring-plan.md
│ │ ├── duplicate-pattern-extraction-summary.md
│ │ ├── error-handling-standardization-summary.md
│ │ ├── generating-component-reference.md
│ │ ├── index.md
│ │ ├── logging-consistency-implementation-summary.md
│ │ ├── project-build.md
│ │ ├── project-structure.md
│ │ ├── theme-context.md
│ │ ├── tiptap-design-considerations.md
│ │ ├── working-with-code.md
│ │ ├── xmlui-runtime-architecture
│ │ └── xmlui-wcag-accessibility-report.md
│ ├── react-fundamentals.md
│ ├── release-method.md
│ ├── standalone-app.md
│ ├── theme-variables-refactoring.md
│ ├── ud-components.md
│ └── xmlui-repo.md
├── package.json
├── scripts
│ ├── coverage-only.js
│ ├── e2e-test-summary.js
│ ├── extract-component-metadata.js
│ ├── generate-docs
│ │ ├── build-downloads-map.mjs
│ │ ├── build-pages-map.mjs
│ │ ├── components-config.json
│ │ ├── configuration-management.mjs
│ │ ├── constants.mjs
│ │ ├── create-theme-files.mjs
│ │ ├── DocsGenerator.mjs
│ │ ├── error-handling.mjs
│ │ ├── extensions-config.json
│ │ ├── folders.mjs
│ │ ├── generate-summary-files.mjs
│ │ ├── get-docs.mjs
│ │ ├── input-handler.mjs
│ │ ├── logger.mjs
│ │ ├── logging-standards.mjs
│ │ ├── MetadataProcessor.mjs
│ │ ├── pattern-utilities.mjs
│ │ └── utils.mjs
│ ├── generate-metadata-markdown.js
│ ├── get-langserver-metadata.js
│ ├── inline-links.mjs
│ └── README-e2e-summary.md
├── src
│ ├── abstractions
│ │ ├── _conventions.md
│ │ ├── ActionDefs.ts
│ │ ├── AppContextDefs.ts
│ │ ├── ComponentDefs.ts
│ │ ├── ContainerDefs.ts
│ │ ├── ExtensionDefs.ts
│ │ ├── FunctionDefs.ts
│ │ ├── RendererDefs.ts
│ │ ├── scripting
│ │ │ ├── BlockScope.ts
│ │ │ ├── Compilation.ts
│ │ │ ├── LogicalThread.ts
│ │ │ ├── LoopScope.ts
│ │ │ ├── modules.ts
│ │ │ ├── ScriptParserError.ts
│ │ │ ├── Token.ts
│ │ │ ├── TryScope.ts
│ │ │ └── TryScopeExp.ts
│ │ └── ThemingDefs.ts
│ ├── components
│ │ ├── _conventions.md
│ │ ├── abstractions.ts
│ │ ├── Accordion
│ │ │ ├── Accordion.md
│ │ │ ├── Accordion.module.scss
│ │ │ ├── Accordion.spec.ts
│ │ │ ├── Accordion.tsx
│ │ │ ├── AccordionContext.tsx
│ │ │ ├── AccordionItem.tsx
│ │ │ ├── AccordionItemNative.tsx
│ │ │ └── AccordionNative.tsx
│ │ ├── Animation
│ │ │ └── AnimationNative.tsx
│ │ ├── APICall
│ │ │ ├── APICall.md
│ │ │ ├── APICall.spec.ts
│ │ │ ├── APICall.tsx
│ │ │ └── APICallNative.tsx
│ │ ├── App
│ │ │ ├── App.md
│ │ │ ├── App.module.scss
│ │ │ ├── App.spec.ts
│ │ │ ├── App.tsx
│ │ │ ├── AppLayoutContext.ts
│ │ │ ├── AppNative.tsx
│ │ │ ├── AppStateContext.ts
│ │ │ ├── doc-resources
│ │ │ │ ├── condensed-sticky.xmlui
│ │ │ │ ├── condensed.xmlui
│ │ │ │ ├── horizontal-sticky.xmlui
│ │ │ │ ├── horizontal.xmlui
│ │ │ │ ├── vertical-full-header.xmlui
│ │ │ │ ├── vertical-sticky.xmlui
│ │ │ │ └── vertical.xmlui
│ │ │ ├── IndexerContext.ts
│ │ │ ├── LinkInfoContext.ts
│ │ │ ├── SearchContext.tsx
│ │ │ ├── Sheet.module.scss
│ │ │ └── Sheet.tsx
│ │ ├── AppHeader
│ │ │ ├── AppHeader.md
│ │ │ ├── AppHeader.module.scss
│ │ │ ├── AppHeader.spec.ts
│ │ │ ├── AppHeader.tsx
│ │ │ └── AppHeaderNative.tsx
│ │ ├── AppState
│ │ │ ├── AppState.md
│ │ │ ├── AppState.spec.ts
│ │ │ ├── AppState.tsx
│ │ │ └── AppStateNative.tsx
│ │ ├── AutoComplete
│ │ │ ├── AutoComplete.md
│ │ │ ├── AutoComplete.module.scss
│ │ │ ├── AutoComplete.spec.ts
│ │ │ ├── AutoComplete.tsx
│ │ │ ├── AutoCompleteContext.tsx
│ │ │ └── AutoCompleteNative.tsx
│ │ ├── Avatar
│ │ │ ├── Avatar.md
│ │ │ ├── Avatar.module.scss
│ │ │ ├── Avatar.spec.ts
│ │ │ ├── Avatar.tsx
│ │ │ └── AvatarNative.tsx
│ │ ├── Backdrop
│ │ │ ├── Backdrop.md
│ │ │ ├── Backdrop.module.scss
│ │ │ ├── Backdrop.spec.ts
│ │ │ ├── Backdrop.tsx
│ │ │ └── BackdropNative.tsx
│ │ ├── Badge
│ │ │ ├── Badge.md
│ │ │ ├── Badge.module.scss
│ │ │ ├── Badge.spec.ts
│ │ │ ├── Badge.tsx
│ │ │ └── BadgeNative.tsx
│ │ ├── Bookmark
│ │ │ ├── Bookmark.md
│ │ │ ├── Bookmark.module.scss
│ │ │ ├── Bookmark.spec.ts
│ │ │ ├── Bookmark.tsx
│ │ │ └── BookmarkNative.tsx
│ │ ├── Breakout
│ │ │ ├── Breakout.module.scss
│ │ │ ├── Breakout.spec.ts
│ │ │ ├── Breakout.tsx
│ │ │ └── BreakoutNative.tsx
│ │ ├── Button
│ │ │ ├── Button-style.spec.ts
│ │ │ ├── Button.md
│ │ │ ├── Button.module.scss
│ │ │ ├── Button.spec.ts
│ │ │ ├── Button.tsx
│ │ │ └── ButtonNative.tsx
│ │ ├── Card
│ │ │ ├── Card.md
│ │ │ ├── Card.module.scss
│ │ │ ├── Card.spec.ts
│ │ │ ├── Card.tsx
│ │ │ └── CardNative.tsx
│ │ ├── Carousel
│ │ │ ├── Carousel.md
│ │ │ ├── Carousel.module.scss
│ │ │ ├── Carousel.spec.ts
│ │ │ ├── Carousel.tsx
│ │ │ ├── CarouselContext.tsx
│ │ │ ├── CarouselItem.tsx
│ │ │ ├── CarouselItemNative.tsx
│ │ │ └── CarouselNative.tsx
│ │ ├── ChangeListener
│ │ │ ├── ChangeListener.md
│ │ │ ├── ChangeListener.spec.ts
│ │ │ ├── ChangeListener.tsx
│ │ │ └── ChangeListenerNative.tsx
│ │ ├── chart-color-schemes.ts
│ │ ├── Charts
│ │ │ ├── AreaChart
│ │ │ │ ├── AreaChart.md
│ │ │ │ ├── AreaChart.spec.ts
│ │ │ │ ├── AreaChart.tsx
│ │ │ │ └── AreaChartNative.tsx
│ │ │ ├── BarChart
│ │ │ │ ├── BarChart.md
│ │ │ │ ├── BarChart.module.scss
│ │ │ │ ├── BarChart.spec.ts
│ │ │ │ ├── BarChart.tsx
│ │ │ │ └── BarChartNative.tsx
│ │ │ ├── DonutChart
│ │ │ │ ├── DonutChart.spec.ts
│ │ │ │ └── DonutChart.tsx
│ │ │ ├── LabelList
│ │ │ │ ├── LabelList.module.scss
│ │ │ │ ├── LabelList.spec.ts
│ │ │ │ ├── LabelList.tsx
│ │ │ │ └── LabelListNative.tsx
│ │ │ ├── Legend
│ │ │ │ ├── Legend.spec.ts
│ │ │ │ ├── Legend.tsx
│ │ │ │ └── LegendNative.tsx
│ │ │ ├── LineChart
│ │ │ │ ├── LineChart.md
│ │ │ │ ├── LineChart.module.scss
│ │ │ │ ├── LineChart.spec.ts
│ │ │ │ ├── LineChart.tsx
│ │ │ │ └── LineChartNative.tsx
│ │ │ ├── PieChart
│ │ │ │ ├── PieChart.md
│ │ │ │ ├── PieChart.spec.ts
│ │ │ │ ├── PieChart.tsx
│ │ │ │ ├── PieChartNative.module.scss
│ │ │ │ └── PieChartNative.tsx
│ │ │ ├── RadarChart
│ │ │ │ ├── RadarChart.md
│ │ │ │ ├── RadarChart.spec.ts
│ │ │ │ ├── RadarChart.tsx
│ │ │ │ └── RadarChartNative.tsx
│ │ │ ├── Tooltip
│ │ │ │ ├── TooltipContent.module.scss
│ │ │ │ ├── TooltipContent.spec.ts
│ │ │ │ └── TooltipContent.tsx
│ │ │ └── utils
│ │ │ ├── abstractions.ts
│ │ │ └── ChartProvider.tsx
│ │ ├── Checkbox
│ │ │ ├── Checkbox.md
│ │ │ ├── Checkbox.spec.ts
│ │ │ └── Checkbox.tsx
│ │ ├── CodeBlock
│ │ │ ├── CodeBlock.module.scss
│ │ │ ├── CodeBlock.spec.ts
│ │ │ ├── CodeBlock.tsx
│ │ │ ├── CodeBlockNative.tsx
│ │ │ └── highlight-code.ts
│ │ ├── collectedComponentMetadata.ts
│ │ ├── ColorPicker
│ │ │ ├── ColorPicker.md
│ │ │ ├── ColorPicker.module.scss
│ │ │ ├── ColorPicker.spec.ts
│ │ │ ├── ColorPicker.tsx
│ │ │ └── ColorPickerNative.tsx
│ │ ├── Column
│ │ │ ├── Column.md
│ │ │ ├── Column.tsx
│ │ │ ├── ColumnNative.tsx
│ │ │ ├── doc-resources
│ │ │ │ └── list-component-data.js
│ │ │ └── TableContext.tsx
│ │ ├── component-utils.ts
│ │ ├── ComponentProvider.tsx
│ │ ├── ComponentRegistryContext.tsx
│ │ ├── container-helpers.tsx
│ │ ├── ContentSeparator
│ │ │ ├── ContentSeparator.md
│ │ │ ├── ContentSeparator.module.scss
│ │ │ ├── ContentSeparator.spec.ts
│ │ │ ├── ContentSeparator.tsx
│ │ │ └── ContentSeparatorNative.tsx
│ │ ├── DataSource
│ │ │ ├── DataSource.md
│ │ │ └── DataSource.tsx
│ │ ├── DateInput
│ │ │ ├── DateInput.md
│ │ │ ├── DateInput.module.scss
│ │ │ ├── DateInput.spec.ts
│ │ │ ├── DateInput.tsx
│ │ │ └── DateInputNative.tsx
│ │ ├── DatePicker
│ │ │ ├── DatePicker.md
│ │ │ ├── DatePicker.module.scss
│ │ │ ├── DatePicker.spec.ts
│ │ │ ├── DatePicker.tsx
│ │ │ └── DatePickerNative.tsx
│ │ ├── DropdownMenu
│ │ │ ├── DropdownMenu.md
│ │ │ ├── DropdownMenu.module.scss
│ │ │ ├── DropdownMenu.spec.ts
│ │ │ ├── DropdownMenu.tsx
│ │ │ ├── DropdownMenuNative.tsx
│ │ │ ├── MenuItem.md
│ │ │ └── SubMenuItem.md
│ │ ├── EmojiSelector
│ │ │ ├── EmojiSelector.md
│ │ │ ├── EmojiSelector.spec.ts
│ │ │ ├── EmojiSelector.tsx
│ │ │ └── EmojiSelectorNative.tsx
│ │ ├── ExpandableItem
│ │ │ ├── ExpandableItem.module.scss
│ │ │ ├── ExpandableItem.spec.ts
│ │ │ ├── ExpandableItem.tsx
│ │ │ └── ExpandableItemNative.tsx
│ │ ├── FileInput
│ │ │ ├── FileInput.md
│ │ │ ├── FileInput.module.scss
│ │ │ ├── FileInput.spec.ts
│ │ │ ├── FileInput.tsx
│ │ │ └── FileInputNative.tsx
│ │ ├── FileUploadDropZone
│ │ │ ├── FileUploadDropZone.md
│ │ │ ├── FileUploadDropZone.module.scss
│ │ │ ├── FileUploadDropZone.spec.ts
│ │ │ ├── FileUploadDropZone.tsx
│ │ │ └── FileUploadDropZoneNative.tsx
│ │ ├── FlowLayout
│ │ │ ├── FlowLayout.md
│ │ │ ├── FlowLayout.module.scss
│ │ │ ├── FlowLayout.spec.ts
│ │ │ ├── FlowLayout.spec.ts-snapshots
│ │ │ │ └── Edge-cases-boxShadow-is-not-clipped-1-non-smoke-darwin.png
│ │ │ ├── FlowLayout.tsx
│ │ │ └── FlowLayoutNative.tsx
│ │ ├── Footer
│ │ │ ├── Footer.md
│ │ │ ├── Footer.module.scss
│ │ │ ├── Footer.spec.ts
│ │ │ ├── Footer.tsx
│ │ │ └── FooterNative.tsx
│ │ ├── Form
│ │ │ ├── Form.md
│ │ │ ├── Form.module.scss
│ │ │ ├── Form.spec.ts
│ │ │ ├── Form.tsx
│ │ │ ├── formActions.ts
│ │ │ ├── FormContext.ts
│ │ │ └── FormNative.tsx
│ │ ├── FormItem
│ │ │ ├── FormItem.md
│ │ │ ├── FormItem.module.scss
│ │ │ ├── FormItem.spec.ts
│ │ │ ├── FormItem.tsx
│ │ │ ├── FormItemNative.tsx
│ │ │ ├── HelperText.module.scss
│ │ │ ├── HelperText.tsx
│ │ │ ├── ItemWithLabel.tsx
│ │ │ └── Validations.ts
│ │ ├── FormSection
│ │ │ ├── FormSection.md
│ │ │ ├── FormSection.ts
│ │ │ └── FormSection.xmlui
│ │ ├── Fragment
│ │ │ ├── Fragment.spec.ts
│ │ │ └── Fragment.tsx
│ │ ├── Heading
│ │ │ ├── abstractions.ts
│ │ │ ├── H1.md
│ │ │ ├── H1.spec.ts
│ │ │ ├── H2.md
│ │ │ ├── H2.spec.ts
│ │ │ ├── H3.md
│ │ │ ├── H3.spec.ts
│ │ │ ├── H4.md
│ │ │ ├── H4.spec.ts
│ │ │ ├── H5.md
│ │ │ ├── H5.spec.ts
│ │ │ ├── H6.md
│ │ │ ├── H6.spec.ts
│ │ │ ├── Heading.md
│ │ │ ├── Heading.module.scss
│ │ │ ├── Heading.spec.ts
│ │ │ ├── Heading.tsx
│ │ │ └── HeadingNative.tsx
│ │ ├── HoverCard
│ │ │ ├── HoverCard.tsx
│ │ │ └── HovercardNative.tsx
│ │ ├── HtmlTags
│ │ │ ├── HtmlTags.module.scss
│ │ │ ├── HtmlTags.spec.ts
│ │ │ └── HtmlTags.tsx
│ │ ├── Icon
│ │ │ ├── AdmonitionDanger.tsx
│ │ │ ├── AdmonitionInfo.tsx
│ │ │ ├── AdmonitionNote.tsx
│ │ │ ├── AdmonitionTip.tsx
│ │ │ ├── AdmonitionWarning.tsx
│ │ │ ├── ApiIcon.tsx
│ │ │ ├── ArrowDropDown.module.scss
│ │ │ ├── ArrowDropDown.tsx
│ │ │ ├── ArrowDropUp.module.scss
│ │ │ ├── ArrowDropUp.tsx
│ │ │ ├── ArrowLeft.module.scss
│ │ │ ├── ArrowLeft.tsx
│ │ │ ├── ArrowRight.module.scss
│ │ │ ├── ArrowRight.tsx
│ │ │ ├── Attach.tsx
│ │ │ ├── Binding.module.scss
│ │ │ ├── Binding.tsx
│ │ │ ├── BoardIcon.tsx
│ │ │ ├── BoxIcon.tsx
│ │ │ ├── CheckIcon.tsx
│ │ │ ├── ChevronDownIcon.tsx
│ │ │ ├── ChevronLeft.tsx
│ │ │ ├── ChevronRight.tsx
│ │ │ ├── ChevronUpIcon.tsx
│ │ │ ├── CodeFileIcon.tsx
│ │ │ ├── CodeSandbox.tsx
│ │ │ ├── CompactListIcon.tsx
│ │ │ ├── ContentCopyIcon.tsx
│ │ │ ├── DarkToLightIcon.tsx
│ │ │ ├── DatabaseIcon.module.scss
│ │ │ ├── DatabaseIcon.tsx
│ │ │ ├── DocFileIcon.tsx
│ │ │ ├── DocIcon.tsx
│ │ │ ├── DotMenuHorizontalIcon.tsx
│ │ │ ├── DotMenuIcon.tsx
│ │ │ ├── EmailIcon.tsx
│ │ │ ├── EmptyFolderIcon.tsx
│ │ │ ├── ErrorIcon.tsx
│ │ │ ├── ExpressionIcon.tsx
│ │ │ ├── FillPlusCricleIcon.tsx
│ │ │ ├── FilterIcon.tsx
│ │ │ ├── FolderIcon.tsx
│ │ │ ├── GlobeIcon.tsx
│ │ │ ├── HomeIcon.tsx
│ │ │ ├── HyperLinkIcon.tsx
│ │ │ ├── Icon.md
│ │ │ ├── Icon.module.scss
│ │ │ ├── Icon.spec.ts
│ │ │ ├── Icon.tsx
│ │ │ ├── IconNative.tsx
│ │ │ ├── ImageFileIcon.tsx
│ │ │ ├── Inspect.tsx
│ │ │ ├── LightToDark.tsx
│ │ │ ├── LinkIcon.tsx
│ │ │ ├── ListIcon.tsx
│ │ │ ├── LooseListIcon.tsx
│ │ │ ├── MoonIcon.tsx
│ │ │ ├── MoreOptionsIcon.tsx
│ │ │ ├── NoSortIcon.tsx
│ │ │ ├── PDFIcon.tsx
│ │ │ ├── PenIcon.tsx
│ │ │ ├── PhoneIcon.tsx
│ │ │ ├── PhotoIcon.tsx
│ │ │ ├── PlusIcon.tsx
│ │ │ ├── SearchIcon.tsx
│ │ │ ├── ShareIcon.tsx
│ │ │ ├── SortAscendingIcon.tsx
│ │ │ ├── SortDescendingIcon.tsx
│ │ │ ├── StarsIcon.tsx
│ │ │ ├── SunIcon.tsx
│ │ │ ├── svg
│ │ │ │ ├── admonition_danger.svg
│ │ │ │ ├── admonition_info.svg
│ │ │ │ ├── admonition_note.svg
│ │ │ │ ├── admonition_tip.svg
│ │ │ │ ├── admonition_warning.svg
│ │ │ │ ├── api.svg
│ │ │ │ ├── arrow-dropdown.svg
│ │ │ │ ├── arrow-left.svg
│ │ │ │ ├── arrow-right.svg
│ │ │ │ ├── arrow-up.svg
│ │ │ │ ├── attach.svg
│ │ │ │ ├── binding.svg
│ │ │ │ ├── box.svg
│ │ │ │ ├── bulb.svg
│ │ │ │ ├── code-file.svg
│ │ │ │ ├── code-sandbox.svg
│ │ │ │ ├── dark_to_light.svg
│ │ │ │ ├── database.svg
│ │ │ │ ├── doc.svg
│ │ │ │ ├── empty-folder.svg
│ │ │ │ ├── expression.svg
│ │ │ │ ├── eye-closed.svg
│ │ │ │ ├── eye-dark.svg
│ │ │ │ ├── eye.svg
│ │ │ │ ├── file-text.svg
│ │ │ │ ├── filter.svg
│ │ │ │ ├── folder.svg
│ │ │ │ ├── img.svg
│ │ │ │ ├── inspect.svg
│ │ │ │ ├── light_to_dark.svg
│ │ │ │ ├── moon.svg
│ │ │ │ ├── pdf.svg
│ │ │ │ ├── photo.svg
│ │ │ │ ├── share.svg
│ │ │ │ ├── stars.svg
│ │ │ │ ├── sun.svg
│ │ │ │ ├── trending-down.svg
│ │ │ │ ├── trending-level.svg
│ │ │ │ ├── trending-up.svg
│ │ │ │ ├── txt.svg
│ │ │ │ ├── unknown-file.svg
│ │ │ │ ├── unlink.svg
│ │ │ │ └── xls.svg
│ │ │ ├── TableDeleteColumnIcon.tsx
│ │ │ ├── TableDeleteRowIcon.tsx
│ │ │ ├── TableInsertColumnIcon.tsx
│ │ │ ├── TableInsertRowIcon.tsx
│ │ │ ├── TrashIcon.tsx
│ │ │ ├── TrendingDownIcon.tsx
│ │ │ ├── TrendingLevelIcon.tsx
│ │ │ ├── TrendingUpIcon.tsx
│ │ │ ├── TxtIcon.tsx
│ │ │ ├── UnknownFileIcon.tsx
│ │ │ ├── UnlinkIcon.tsx
│ │ │ ├── UserIcon.tsx
│ │ │ ├── WarningIcon.tsx
│ │ │ └── XlsIcon.tsx
│ │ ├── IconProvider.tsx
│ │ ├── IconRegistryContext.tsx
│ │ ├── IFrame
│ │ │ ├── IFrame.md
│ │ │ ├── IFrame.module.scss
│ │ │ ├── IFrame.spec.ts
│ │ │ ├── IFrame.tsx
│ │ │ └── IFrameNative.tsx
│ │ ├── Image
│ │ │ ├── Image.md
│ │ │ ├── Image.module.scss
│ │ │ ├── Image.spec.ts
│ │ │ ├── Image.tsx
│ │ │ └── ImageNative.tsx
│ │ ├── Input
│ │ │ ├── index.ts
│ │ │ ├── InputAdornment.module.scss
│ │ │ ├── InputAdornment.tsx
│ │ │ ├── InputDivider.module.scss
│ │ │ ├── InputDivider.tsx
│ │ │ ├── InputLabel.module.scss
│ │ │ ├── InputLabel.tsx
│ │ │ ├── PartialInput.module.scss
│ │ │ └── PartialInput.tsx
│ │ ├── InspectButton
│ │ │ ├── InspectButton.module.scss
│ │ │ └── InspectButton.tsx
│ │ ├── Items
│ │ │ ├── Items.md
│ │ │ ├── Items.spec.ts
│ │ │ ├── Items.tsx
│ │ │ └── ItemsNative.tsx
│ │ ├── Link
│ │ │ ├── Link.md
│ │ │ ├── Link.module.scss
│ │ │ ├── Link.spec.ts
│ │ │ ├── Link.tsx
│ │ │ └── LinkNative.tsx
│ │ ├── List
│ │ │ ├── doc-resources
│ │ │ │ └── list-component-data.js
│ │ │ ├── List.md
│ │ │ ├── List.module.scss
│ │ │ ├── List.spec.ts
│ │ │ ├── List.tsx
│ │ │ └── ListNative.tsx
│ │ ├── Logo
│ │ │ ├── doc-resources
│ │ │ │ └── xmlui-logo.svg
│ │ │ ├── Logo.md
│ │ │ ├── Logo.tsx
│ │ │ └── LogoNative.tsx
│ │ ├── Markdown
│ │ │ ├── CodeText.module.scss
│ │ │ ├── CodeText.tsx
│ │ │ ├── Markdown.md
│ │ │ ├── Markdown.module.scss
│ │ │ ├── Markdown.spec.ts
│ │ │ ├── Markdown.tsx
│ │ │ ├── MarkdownNative.tsx
│ │ │ ├── parse-binding-expr.ts
│ │ │ └── utils.ts
│ │ ├── metadata-helpers.ts
│ │ ├── ModalDialog
│ │ │ ├── ConfirmationModalContextProvider.tsx
│ │ │ ├── Dialog.module.scss
│ │ │ ├── Dialog.tsx
│ │ │ ├── ModalDialog.md
│ │ │ ├── ModalDialog.module.scss
│ │ │ ├── ModalDialog.spec.ts
│ │ │ ├── ModalDialog.tsx
│ │ │ ├── ModalDialogNative.tsx
│ │ │ └── ModalVisibilityContext.tsx
│ │ ├── NavGroup
│ │ │ ├── NavGroup.md
│ │ │ ├── NavGroup.module.scss
│ │ │ ├── NavGroup.spec.ts
│ │ │ ├── NavGroup.tsx
│ │ │ ├── NavGroupContext.ts
│ │ │ └── NavGroupNative.tsx
│ │ ├── NavLink
│ │ │ ├── NavLink.md
│ │ │ ├── NavLink.module.scss
│ │ │ ├── NavLink.spec.ts
│ │ │ ├── NavLink.tsx
│ │ │ └── NavLinkNative.tsx
│ │ ├── NavPanel
│ │ │ ├── NavPanel.md
│ │ │ ├── NavPanel.module.scss
│ │ │ ├── NavPanel.spec.ts
│ │ │ ├── NavPanel.tsx
│ │ │ └── NavPanelNative.tsx
│ │ ├── NestedApp
│ │ │ ├── AppWithCodeView.module.scss
│ │ │ ├── AppWithCodeView.tsx
│ │ │ ├── AppWithCodeViewNative.tsx
│ │ │ ├── defaultProps.tsx
│ │ │ ├── logo.svg
│ │ │ ├── NestedApp.module.scss
│ │ │ ├── NestedApp.tsx
│ │ │ ├── NestedAppNative.tsx
│ │ │ ├── Tooltip.module.scss
│ │ │ ├── Tooltip.tsx
│ │ │ └── utils.ts
│ │ ├── NoResult
│ │ │ ├── NoResult.md
│ │ │ ├── NoResult.module.scss
│ │ │ ├── NoResult.spec.ts
│ │ │ ├── NoResult.tsx
│ │ │ └── NoResultNative.tsx
│ │ ├── NumberBox
│ │ │ ├── numberbox-abstractions.ts
│ │ │ ├── NumberBox.md
│ │ │ ├── NumberBox.module.scss
│ │ │ ├── NumberBox.spec.ts
│ │ │ ├── NumberBox.tsx
│ │ │ └── NumberBoxNative.tsx
│ │ ├── Option
│ │ │ ├── Option.md
│ │ │ ├── Option.spec.ts
│ │ │ ├── Option.tsx
│ │ │ ├── OptionNative.tsx
│ │ │ └── OptionTypeProvider.tsx
│ │ ├── PageMetaTitle
│ │ │ ├── PageMetaTilteNative.tsx
│ │ │ ├── PageMetaTitle.md
│ │ │ ├── PageMetaTitle.spec.ts
│ │ │ └── PageMetaTitle.tsx
│ │ ├── Pages
│ │ │ ├── Page.md
│ │ │ ├── Pages.md
│ │ │ ├── Pages.module.scss
│ │ │ ├── Pages.tsx
│ │ │ └── PagesNative.tsx
│ │ ├── Pagination
│ │ │ ├── Pagination.md
│ │ │ ├── Pagination.module.scss
│ │ │ ├── Pagination.spec.ts
│ │ │ ├── Pagination.tsx
│ │ │ └── PaginationNative.tsx
│ │ ├── PositionedContainer
│ │ │ ├── PositionedContainer.module.scss
│ │ │ ├── PositionedContainer.tsx
│ │ │ └── PositionedContainerNative.tsx
│ │ ├── ProfileMenu
│ │ │ ├── ProfileMenu.module.scss
│ │ │ └── ProfileMenu.tsx
│ │ ├── ProgressBar
│ │ │ ├── ProgressBar.md
│ │ │ ├── ProgressBar.module.scss
│ │ │ ├── ProgressBar.spec.ts
│ │ │ ├── ProgressBar.tsx
│ │ │ └── ProgressBarNative.tsx
│ │ ├── Queue
│ │ │ ├── Queue.md
│ │ │ ├── Queue.spec.ts
│ │ │ ├── Queue.tsx
│ │ │ ├── queueActions.ts
│ │ │ └── QueueNative.tsx
│ │ ├── RadioGroup
│ │ │ ├── RadioGroup.md
│ │ │ ├── RadioGroup.module.scss
│ │ │ ├── RadioGroup.spec.ts
│ │ │ ├── RadioGroup.tsx
│ │ │ ├── RadioGroupNative.tsx
│ │ │ ├── RadioItem.tsx
│ │ │ └── RadioItemNative.tsx
│ │ ├── RealTimeAdapter
│ │ │ ├── RealTimeAdapter.tsx
│ │ │ └── RealTimeAdapterNative.tsx
│ │ ├── Redirect
│ │ │ ├── Redirect.md
│ │ │ ├── Redirect.spec.ts
│ │ │ └── Redirect.tsx
│ │ ├── ResponsiveBar
│ │ │ ├── README.md
│ │ │ ├── ResponsiveBar.md
│ │ │ ├── ResponsiveBar.module.scss
│ │ │ ├── ResponsiveBar.spec.ts
│ │ │ ├── ResponsiveBar.tsx
│ │ │ └── ResponsiveBarNative.tsx
│ │ ├── Select
│ │ │ ├── HiddenOption.tsx
│ │ │ ├── OptionContext.ts
│ │ │ ├── Select.md
│ │ │ ├── Select.module.scss
│ │ │ ├── Select.spec.ts
│ │ │ ├── Select.tsx
│ │ │ ├── SelectContext.tsx
│ │ │ └── SelectNative.tsx
│ │ ├── SelectionStore
│ │ │ ├── SelectionStore.md
│ │ │ ├── SelectionStore.tsx
│ │ │ └── SelectionStoreNative.tsx
│ │ ├── Slider
│ │ │ ├── Slider.md
│ │ │ ├── Slider.module.scss
│ │ │ ├── Slider.spec.ts
│ │ │ ├── Slider.tsx
│ │ │ └── SliderNative.tsx
│ │ ├── Slot
│ │ │ ├── Slot.md
│ │ │ ├── Slot.spec.ts
│ │ │ └── Slot.ts
│ │ ├── SlotItem.tsx
│ │ ├── SpaceFiller
│ │ │ ├── SpaceFiller.md
│ │ │ ├── SpaceFiller.module.scss
│ │ │ ├── SpaceFiller.spec.ts
│ │ │ ├── SpaceFiller.tsx
│ │ │ └── SpaceFillerNative.tsx
│ │ ├── Spinner
│ │ │ ├── Spinner.md
│ │ │ ├── Spinner.module.scss
│ │ │ ├── Spinner.spec.ts
│ │ │ ├── Spinner.tsx
│ │ │ └── SpinnerNative.tsx
│ │ ├── Splitter
│ │ │ ├── HSplitter.md
│ │ │ ├── HSplitter.spec.ts
│ │ │ ├── Splitter.md
│ │ │ ├── Splitter.module.scss
│ │ │ ├── Splitter.spec.ts
│ │ │ ├── Splitter.tsx
│ │ │ ├── SplitterNative.tsx
│ │ │ ├── utils.ts
│ │ │ ├── VSplitter.md
│ │ │ └── VSplitter.spec.ts
│ │ ├── Stack
│ │ │ ├── CHStack.md
│ │ │ ├── CHStack.spec.ts
│ │ │ ├── CVStack.md
│ │ │ ├── CVStack.spec.ts
│ │ │ ├── HStack.md
│ │ │ ├── HStack.spec.ts
│ │ │ ├── Stack.md
│ │ │ ├── Stack.module.scss
│ │ │ ├── Stack.spec.ts
│ │ │ ├── Stack.tsx
│ │ │ ├── StackNative.tsx
│ │ │ ├── VStack.md
│ │ │ └── VStack.spec.ts
│ │ ├── StickyBox
│ │ │ ├── StickyBox.md
│ │ │ ├── StickyBox.module.scss
│ │ │ ├── StickyBox.tsx
│ │ │ └── StickyBoxNative.tsx
│ │ ├── Switch
│ │ │ ├── Switch.md
│ │ │ ├── Switch.spec.ts
│ │ │ └── Switch.tsx
│ │ ├── Table
│ │ │ ├── doc-resources
│ │ │ │ └── list-component-data.js
│ │ │ ├── react-table-config.d.ts
│ │ │ ├── Table.md
│ │ │ ├── Table.module.scss
│ │ │ ├── Table.spec.ts
│ │ │ ├── Table.tsx
│ │ │ ├── TableNative.tsx
│ │ │ └── useRowSelection.tsx
│ │ ├── TableOfContents
│ │ │ ├── TableOfContents.module.scss
│ │ │ ├── TableOfContents.spec.ts
│ │ │ ├── TableOfContents.tsx
│ │ │ └── TableOfContentsNative.tsx
│ │ ├── Tabs
│ │ │ ├── TabContext.tsx
│ │ │ ├── TabItem.md
│ │ │ ├── TabItem.tsx
│ │ │ ├── TabItemNative.tsx
│ │ │ ├── Tabs.md
│ │ │ ├── Tabs.module.scss
│ │ │ ├── Tabs.spec.ts
│ │ │ ├── Tabs.tsx
│ │ │ └── TabsNative.tsx
│ │ ├── Text
│ │ │ ├── Text.md
│ │ │ ├── Text.module.scss
│ │ │ ├── Text.spec.ts
│ │ │ ├── Text.tsx
│ │ │ └── TextNative.tsx
│ │ ├── TextArea
│ │ │ ├── TextArea.md
│ │ │ ├── TextArea.module.scss
│ │ │ ├── TextArea.spec.ts
│ │ │ ├── TextArea.tsx
│ │ │ ├── TextAreaNative.tsx
│ │ │ ├── TextAreaResizable.tsx
│ │ │ └── useComposedRef.ts
│ │ ├── TextBox
│ │ │ ├── TextBox.md
│ │ │ ├── TextBox.module.scss
│ │ │ ├── TextBox.spec.ts
│ │ │ ├── TextBox.tsx
│ │ │ └── TextBoxNative.tsx
│ │ ├── Theme
│ │ │ ├── NotificationToast.tsx
│ │ │ ├── Theme.md
│ │ │ ├── Theme.module.scss
│ │ │ ├── Theme.spec.ts
│ │ │ ├── Theme.tsx
│ │ │ └── ThemeNative.tsx
│ │ ├── TimeInput
│ │ │ ├── TimeInput.md
│ │ │ ├── TimeInput.module.scss
│ │ │ ├── TimeInput.spec.ts
│ │ │ ├── TimeInput.tsx
│ │ │ ├── TimeInputNative.tsx
│ │ │ └── utils.ts
│ │ ├── Timer
│ │ │ ├── Timer.md
│ │ │ ├── Timer.spec.ts
│ │ │ ├── Timer.tsx
│ │ │ └── TimerNative.tsx
│ │ ├── Toggle
│ │ │ ├── Toggle.module.scss
│ │ │ └── Toggle.tsx
│ │ ├── ToneChangerButton
│ │ │ ├── ToneChangerButton.md
│ │ │ ├── ToneChangerButton.spec.ts
│ │ │ └── ToneChangerButton.tsx
│ │ ├── ToneSwitch
│ │ │ ├── ToneSwitch.md
│ │ │ ├── ToneSwitch.module.scss
│ │ │ ├── ToneSwitch.spec.ts
│ │ │ ├── ToneSwitch.tsx
│ │ │ └── ToneSwitchNative.tsx
│ │ ├── Tooltip
│ │ │ ├── Tooltip.md
│ │ │ ├── Tooltip.module.scss
│ │ │ ├── Tooltip.spec.ts
│ │ │ ├── Tooltip.tsx
│ │ │ └── TooltipNative.tsx
│ │ ├── Tree
│ │ │ ├── testData.ts
│ │ │ ├── Tree-dynamic.spec.ts
│ │ │ ├── Tree-icons.spec.ts
│ │ │ ├── Tree.md
│ │ │ ├── Tree.spec.ts
│ │ │ ├── TreeComponent.module.scss
│ │ │ ├── TreeComponent.tsx
│ │ │ └── TreeNative.tsx
│ │ ├── TreeDisplay
│ │ │ ├── TreeDisplay.md
│ │ │ ├── TreeDisplay.module.scss
│ │ │ ├── TreeDisplay.tsx
│ │ │ └── TreeDisplayNative.tsx
│ │ ├── ValidationSummary
│ │ │ ├── ValidationSummary.module.scss
│ │ │ └── ValidationSummary.tsx
│ │ └── VisuallyHidden.tsx
│ ├── components-core
│ │ ├── abstractions
│ │ │ ├── ComponentRenderer.ts
│ │ │ ├── LoaderRenderer.ts
│ │ │ ├── standalone.ts
│ │ │ └── treeAbstractions.ts
│ │ ├── action
│ │ │ ├── actions.ts
│ │ │ ├── APICall.tsx
│ │ │ ├── FileDownloadAction.tsx
│ │ │ ├── FileUploadAction.tsx
│ │ │ ├── NavigateAction.tsx
│ │ │ └── TimedAction.tsx
│ │ ├── ApiBoundComponent.tsx
│ │ ├── appContext
│ │ │ ├── date-functions.ts
│ │ │ ├── math-function.ts
│ │ │ └── misc-utils.ts
│ │ ├── AppContext.tsx
│ │ ├── behaviors
│ │ │ ├── Behavior.tsx
│ │ │ └── CoreBehaviors.tsx
│ │ ├── component-hooks.ts
│ │ ├── ComponentDecorator.tsx
│ │ ├── ComponentViewer.tsx
│ │ ├── CompoundComponent.tsx
│ │ ├── constants.ts
│ │ ├── DebugViewProvider.tsx
│ │ ├── descriptorHelper.ts
│ │ ├── devtools
│ │ │ ├── InspectorDialog.module.scss
│ │ │ ├── InspectorDialog.tsx
│ │ │ └── InspectorDialogVisibilityContext.tsx
│ │ ├── EngineError.ts
│ │ ├── event-handlers.ts
│ │ ├── InspectorButton.module.scss
│ │ ├── InspectorContext.tsx
│ │ ├── interception
│ │ │ ├── abstractions.ts
│ │ │ ├── ApiInterceptor.ts
│ │ │ ├── ApiInterceptorProvider.tsx
│ │ │ ├── apiInterceptorWorker.ts
│ │ │ ├── Backend.ts
│ │ │ ├── Errors.ts
│ │ │ ├── IndexedDb.ts
│ │ │ ├── initMock.ts
│ │ │ ├── InMemoryDb.ts
│ │ │ ├── ReadonlyCollection.ts
│ │ │ └── useApiInterceptorContext.tsx
│ │ ├── loader
│ │ │ ├── ApiLoader.tsx
│ │ │ ├── DataLoader.tsx
│ │ │ ├── ExternalDataLoader.tsx
│ │ │ ├── Loader.tsx
│ │ │ ├── MockLoaderRenderer.tsx
│ │ │ └── PageableLoader.tsx
│ │ ├── LoaderComponent.tsx
│ │ ├── markup-check.ts
│ │ ├── parts.ts
│ │ ├── renderers.ts
│ │ ├── rendering
│ │ │ ├── AppContent.tsx
│ │ │ ├── AppRoot.tsx
│ │ │ ├── AppWrapper.tsx
│ │ │ ├── buildProxy.ts
│ │ │ ├── collectFnVarDeps.ts
│ │ │ ├── ComponentAdapter.tsx
│ │ │ ├── ComponentWrapper.tsx
│ │ │ ├── Container.tsx
│ │ │ ├── containers.ts
│ │ │ ├── ContainerWrapper.tsx
│ │ │ ├── ErrorBoundary.module.scss
│ │ │ ├── ErrorBoundary.tsx
│ │ │ ├── InvalidComponent.module.scss
│ │ │ ├── InvalidComponent.tsx
│ │ │ ├── nodeUtils.ts
│ │ │ ├── reducer.ts
│ │ │ ├── renderChild.tsx
│ │ │ ├── StandaloneComponent.tsx
│ │ │ ├── StateContainer.tsx
│ │ │ ├── UnknownComponent.module.scss
│ │ │ ├── UnknownComponent.tsx
│ │ │ └── valueExtractor.ts
│ │ ├── reportEngineError.ts
│ │ ├── RestApiProxy.ts
│ │ ├── script-runner
│ │ │ ├── asyncProxy.ts
│ │ │ ├── AttributeValueParser.ts
│ │ │ ├── bannedFunctions.ts
│ │ │ ├── BindingTreeEvaluationContext.ts
│ │ │ ├── eval-tree-async.ts
│ │ │ ├── eval-tree-common.ts
│ │ │ ├── eval-tree-sync.ts
│ │ │ ├── ParameterParser.ts
│ │ │ ├── process-statement-async.ts
│ │ │ ├── process-statement-common.ts
│ │ │ ├── process-statement-sync.ts
│ │ │ ├── ScriptingSourceTree.ts
│ │ │ ├── simplify-expression.ts
│ │ │ ├── statement-queue.ts
│ │ │ └── visitors.ts
│ │ ├── StandaloneApp.tsx
│ │ ├── StandaloneExtensionManager.ts
│ │ ├── TableOfContentsContext.tsx
│ │ ├── theming
│ │ │ ├── _themes.scss
│ │ │ ├── component-layout-resolver.ts
│ │ │ ├── extendThemeUtils.ts
│ │ │ ├── hvar.ts
│ │ │ ├── layout-resolver.ts
│ │ │ ├── parse-layout-props.ts
│ │ │ ├── StyleContext.tsx
│ │ │ ├── StyleRegistry.ts
│ │ │ ├── ThemeContext.tsx
│ │ │ ├── ThemeProvider.tsx
│ │ │ ├── themes
│ │ │ │ ├── base-utils.ts
│ │ │ │ ├── palette.ts
│ │ │ │ ├── root.ts
│ │ │ │ ├── solid.ts
│ │ │ │ ├── theme-colors.ts
│ │ │ │ └── xmlui.ts
│ │ │ ├── themeVars.module.scss
│ │ │ ├── themeVars.ts
│ │ │ ├── transformThemeVars.ts
│ │ │ └── utils.ts
│ │ ├── utils
│ │ │ ├── actionUtils.ts
│ │ │ ├── audio-utils.ts
│ │ │ ├── base64-utils.ts
│ │ │ ├── compound-utils.ts
│ │ │ ├── css-utils.ts
│ │ │ ├── DataLoaderQueryKeyGenerator.ts
│ │ │ ├── date-utils.ts
│ │ │ ├── extractParam.ts
│ │ │ ├── hooks.tsx
│ │ │ ├── LruCache.ts
│ │ │ ├── mergeProps.ts
│ │ │ ├── misc.ts
│ │ │ ├── request-params.ts
│ │ │ ├── statementUtils.ts
│ │ │ └── treeUtils.ts
│ │ └── xmlui-parser.ts
│ ├── index-standalone.ts
│ ├── index.scss
│ ├── index.ts
│ ├── language-server
│ │ ├── server-common.ts
│ │ ├── server-web-worker.ts
│ │ ├── server.ts
│ │ ├── services
│ │ │ ├── common
│ │ │ │ ├── docs-generation.ts
│ │ │ │ ├── lsp-utils.ts
│ │ │ │ ├── metadata-utils.ts
│ │ │ │ └── syntax-node-utilities.ts
│ │ │ ├── completion.ts
│ │ │ ├── diagnostic.ts
│ │ │ ├── format.ts
│ │ │ └── hover.ts
│ │ └── xmlui-metadata-generated.js
│ ├── logging
│ │ ├── LoggerContext.tsx
│ │ ├── LoggerInitializer.tsx
│ │ ├── LoggerService.ts
│ │ └── xmlui.ts
│ ├── logo.svg
│ ├── parsers
│ │ ├── common
│ │ │ ├── GenericToken.ts
│ │ │ ├── InputStream.ts
│ │ │ └── utils.ts
│ │ ├── scripting
│ │ │ ├── code-behind-collect.ts
│ │ │ ├── Lexer.ts
│ │ │ ├── modules.ts
│ │ │ ├── Parser.ts
│ │ │ ├── ParserError.ts
│ │ │ ├── ScriptingNodeTypes.ts
│ │ │ ├── TokenTrait.ts
│ │ │ ├── TokenType.ts
│ │ │ └── tree-visitor.ts
│ │ ├── style-parser
│ │ │ ├── errors.ts
│ │ │ ├── source-tree.ts
│ │ │ ├── StyleInputStream.ts
│ │ │ ├── StyleLexer.ts
│ │ │ ├── StyleParser.ts
│ │ │ └── tokens.ts
│ │ └── xmlui-parser
│ │ ├── CharacterCodes.ts
│ │ ├── diagnostics.ts
│ │ ├── fileExtensions.ts
│ │ ├── index.ts
│ │ ├── lint.ts
│ │ ├── parser.ts
│ │ ├── ParserError.ts
│ │ ├── scanner.ts
│ │ ├── syntax-kind.ts
│ │ ├── syntax-node.ts
│ │ ├── transform.ts
│ │ ├── utils.ts
│ │ ├── xmlui-serializer.ts
│ │ └── xmlui-tree.ts
│ ├── react-app-env.d.ts
│ ├── syntax
│ │ ├── monaco
│ │ │ ├── grammar.monacoLanguage.ts
│ │ │ ├── index.ts
│ │ │ ├── xmlui-dark.ts
│ │ │ ├── xmlui-light.ts
│ │ │ └── xmluiscript.monacoLanguage.ts
│ │ └── textMate
│ │ ├── index.ts
│ │ ├── xmlui-dark.json
│ │ ├── xmlui-light.json
│ │ ├── xmlui.json
│ │ └── xmlui.tmLanguage.json
│ ├── testing
│ │ ├── assertions.ts
│ │ ├── component-test-helpers.ts
│ │ ├── ComponentDrivers.ts
│ │ ├── drivers
│ │ │ ├── DateInputDriver.ts
│ │ │ ├── index.ts
│ │ │ ├── ModalDialogDriver.ts
│ │ │ ├── NumberBoxDriver.ts
│ │ │ ├── TextBoxDriver.ts
│ │ │ ├── TimeInputDriver.ts
│ │ │ ├── TimerDriver.ts
│ │ │ └── TreeDriver.ts
│ │ ├── fixtures.ts
│ │ ├── index.ts
│ │ ├── infrastructure
│ │ │ ├── index.html
│ │ │ ├── main.tsx
│ │ │ ├── public
│ │ │ │ ├── mockServiceWorker.js
│ │ │ │ ├── resources
│ │ │ │ │ ├── bell.svg
│ │ │ │ │ ├── box.svg
│ │ │ │ │ ├── doc.svg
│ │ │ │ │ ├── eye.svg
│ │ │ │ │ ├── flower-640x480.jpg
│ │ │ │ │ ├── sun.svg
│ │ │ │ │ ├── test-image-100x100.jpg
│ │ │ │ │ └── txt.svg
│ │ │ │ └── serve.json
│ │ │ └── TestBed.tsx
│ │ └── themed-app-test-helpers.ts
│ └── vite-env.d.ts
├── tests
│ ├── components
│ │ ├── CodeBlock
│ │ │ └── hightlight-code.test.ts
│ │ ├── playground-pattern.test.ts
│ │ └── Tree
│ │ └── Tree-states.test.ts
│ ├── components-core
│ │ ├── abstractions
│ │ │ └── treeAbstractions.test.ts
│ │ ├── container
│ │ │ └── buildProxy.test.ts
│ │ ├── interception
│ │ │ ├── orderBy.test.ts
│ │ │ ├── ReadOnlyCollection.test.ts
│ │ │ └── request-param-converter.test.ts
│ │ ├── scripts-runner
│ │ │ ├── AttributeValueParser.test.ts
│ │ │ ├── eval-tree-arrow-async.test.ts
│ │ │ ├── eval-tree-arrow.test.ts
│ │ │ ├── eval-tree-func-decl-async.test.ts
│ │ │ ├── eval-tree-func-decl.test.ts
│ │ │ ├── eval-tree-pre-post.test.ts
│ │ │ ├── eval-tree-regression.test.ts
│ │ │ ├── eval-tree.test.ts
│ │ │ ├── function-proxy.test.ts
│ │ │ ├── parser-regression.test.ts
│ │ │ ├── process-event.test.ts
│ │ │ ├── process-function.test.ts
│ │ │ ├── process-implicit-context.test.ts
│ │ │ ├── process-statement-asgn.test.ts
│ │ │ ├── process-statement-destruct.test.ts
│ │ │ ├── process-statement-regs.test.ts
│ │ │ ├── process-statement-sync.test.ts
│ │ │ ├── process-statement.test.ts
│ │ │ ├── process-switch-sync.test.ts
│ │ │ ├── process-switch.test.ts
│ │ │ ├── process-try-sync.test.ts
│ │ │ ├── process-try.test.ts
│ │ │ └── test-helpers.ts
│ │ ├── test-metadata-handler.ts
│ │ ├── theming
│ │ │ ├── border-segments.test.ts
│ │ │ ├── component-layout.resolver.test.ts
│ │ │ ├── layout-property-parser.test.ts
│ │ │ ├── layout-resolver.test.ts
│ │ │ ├── layout-resolver2.test.ts
│ │ │ ├── layout-vp-override.test.ts
│ │ │ └── padding-segments.test.ts
│ │ └── utils
│ │ ├── date-utils.test.ts
│ │ ├── format-human-elapsed-time.test.ts
│ │ └── LruCache.test.ts
│ ├── language-server
│ │ ├── completion.test.ts
│ │ ├── format.test.ts
│ │ ├── hover.test.ts
│ │ └── mockData.ts
│ └── parsers
│ ├── common
│ │ └── input-stream.test.ts
│ ├── markdown
│ │ └── parse-binding-expression.test.ts
│ ├── parameter-parser.test.ts
│ ├── paremeter-parser.test.ts
│ ├── scripting
│ │ ├── eval-tree-arrow.test.ts
│ │ ├── eval-tree-pre-post.test.ts
│ │ ├── eval-tree.test.ts
│ │ ├── function-proxy.test.ts
│ │ ├── lexer-literals.test.ts
│ │ ├── lexer-misc.test.ts
│ │ ├── module-parse.test.ts
│ │ ├── parser-arrow.test.ts
│ │ ├── parser-assignments.test.ts
│ │ ├── parser-binary.test.ts
│ │ ├── parser-destructuring.test.ts
│ │ ├── parser-errors.test.ts
│ │ ├── parser-expressions.test.ts
│ │ ├── parser-function.test.ts
│ │ ├── parser-literals.test.ts
│ │ ├── parser-primary.test.ts
│ │ ├── parser-regex.test.ts
│ │ ├── parser-statements.test.ts
│ │ ├── parser-unary.test.ts
│ │ ├── process-event.test.ts
│ │ ├── process-implicit-context.test.ts
│ │ ├── process-statement-asgn.test.ts
│ │ ├── process-statement-destruct.test.ts
│ │ ├── process-statement-regs.test.ts
│ │ ├── process-statement-sync.test.ts
│ │ ├── process-statement.test.ts
│ │ ├── process-switch-sync.test.ts
│ │ ├── process-switch.test.ts
│ │ ├── process-try-sync.test.ts
│ │ ├── process-try.test.ts
│ │ ├── simplify-expression.test.ts
│ │ ├── statement-hooks.test.ts
│ │ └── test-helpers.ts
│ ├── style-parser
│ │ ├── generateHvarChain.test.ts
│ │ ├── parseHVar.test.ts
│ │ ├── parser.test.ts
│ │ └── tokens.test.ts
│ └── xmlui
│ ├── lint.test.ts
│ ├── parser.test.ts
│ ├── scanner.test.ts
│ ├── transform.attr.test.ts
│ ├── transform.circular.test.ts
│ ├── transform.element.test.ts
│ ├── transform.errors.test.ts
│ ├── transform.escape.test.ts
│ ├── transform.regression.test.ts
│ ├── transform.script.test.ts
│ ├── transform.test.ts
│ └── xmlui.ts
├── tests-e2e
│ ├── api-bound-component-regression.spec.ts
│ ├── api-call-as-extracted-component.spec.ts
│ ├── assign-to-object-or-array-regression.spec.ts
│ ├── binding-regression.spec.ts
│ ├── children-as-template-context-vars.spec.ts
│ ├── compound-component.spec.ts
│ ├── context-vars-regression.spec.ts
│ ├── data-bindings.spec.ts
│ ├── datasource-and-api-usage-in-var.spec.ts
│ ├── datasource-direct-binding.spec.ts
│ ├── datasource-onLoaded-regression.spec.ts
│ ├── modify-array-item-regression.spec.ts
│ ├── namespaces.spec.ts
│ ├── push-to-array-regression.spec.ts
│ ├── screen-breakpoints.spec.ts
│ ├── scripting.spec.ts
│ ├── state-scope-in-pages.spec.ts
│ └── state-var-scopes.spec.ts
├── tsconfig.json
├── tsdown.config.ts
├── vite.config.ts
└── vitest.config.ts
```
# Files
--------------------------------------------------------------------------------
/xmlui/tests/components-core/theming/layout-resolver2.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { describe, expect, it } from "vitest";
2 | import { resolveLayoutProps, toCssVar } from "../../../src/components-core/theming/layout-resolver";
3 |
4 | describe("Layout resolver 2", () => {
5 | const THEME_ID = "$some-theme-id_x";
6 | const THEME_ID_VALUE = toCssVar(THEME_ID);
7 | const THEME_ID2 = "$some-theme-id2";
8 | const THEME_ID2_VALUE = toCssVar(THEME_ID2);
9 | const THEME_ID3 = "$some-theme-id3";
10 | const THEME_ID3_VALUE = toCssVar(THEME_ID3);
11 | const THEME_ID4 = "$some-theme-id4";
12 | const THEME_ID4_VALUE = toCssVar(THEME_ID4);
13 |
14 | // --- Borders
15 | it("textDecoration", () => {
16 | const PROP = "textDecoration";
17 | const VALUE = "underline solid red";
18 | const result = resolveLayoutProps({ [PROP]: VALUE });
19 | expect(result.cssProps[PROP]).toBe(VALUE);
20 | expect(result.issues.has(PROP)).toBe(false);
21 | });
22 |
23 | it("textDecorationLine", () => {
24 | const PROP = "textDecorationLine";
25 | const VALUE = "overline";
26 | const result = resolveLayoutProps({ [PROP]: VALUE });
27 | expect(result.cssProps[PROP]).toBe(VALUE);
28 | expect(result.issues.has(PROP)).toBe(false);
29 | });
30 |
31 | it("textDecorationColor", () => {
32 | const PROP = "textDecorationColor";
33 | const VALUE = "blue";
34 | const result = resolveLayoutProps({ [PROP]: VALUE });
35 | expect(result.cssProps[PROP]).toBe(VALUE);
36 | expect(result.issues.has(PROP)).toBe(false);
37 | });
38 |
39 | it("textDecorationStyle", () => {
40 | const PROP = "textDecorationStyle";
41 | const VALUE = "dotted";
42 | const result = resolveLayoutProps({ [PROP]: VALUE });
43 | expect(result.cssProps[PROP]).toBe(VALUE);
44 | expect(result.issues.has(PROP)).toBe(false);
45 | });
46 |
47 | it("textDecorationThickness", () => {
48 | const PROP = "textDecorationThickness";
49 | const VALUE = "8px";
50 | const result = resolveLayoutProps({ [PROP]: VALUE });
51 | expect(result.cssProps[PROP]).toBe(VALUE);
52 | expect(result.issues.has(PROP)).toBe(false);
53 | });
54 |
55 | it("textUnderlineOffset", () => {
56 | const PROP = "textUnderlineOffset";
57 | const VALUE = "8px";
58 | const result = resolveLayoutProps({ [PROP]: VALUE });
59 | expect(result.cssProps[PROP]).toBe(VALUE);
60 | expect(result.issues.has(PROP)).toBe(false);
61 | });
62 |
63 | it("outline", () => {
64 | const PROP = "outline";
65 | const VALUE = "3px solid green";
66 | const result = resolveLayoutProps({ [PROP]: VALUE });
67 | expect(result.cssProps[PROP]).toBe(VALUE);
68 | expect(result.issues.has(PROP)).toBe(false);
69 | });
70 |
71 | it("outlineWidth", () => {
72 | const PROP = "outlineWidth";
73 | const VALUE = "3px";
74 | const result = resolveLayoutProps({ [PROP]: VALUE });
75 | expect(result.cssProps[PROP]).toBe(VALUE);
76 | expect(result.issues.has(PROP)).toBe(false);
77 | });
78 |
79 | it("outlineColor", () => {
80 | const PROP = "outlineColor";
81 | const VALUE = "green";
82 | const result = resolveLayoutProps({ [PROP]: VALUE });
83 | expect(result.cssProps[PROP]).toBe(VALUE);
84 | expect(result.issues.has(PROP)).toBe(false);
85 | });
86 |
87 | it("outlineStyle", () => {
88 | const PROP = "outlineStyle";
89 | const VALUE = "dotted";
90 | const result = resolveLayoutProps({ [PROP]: VALUE });
91 | expect(result.cssProps[PROP]).toBe(VALUE);
92 | expect(result.issues.has(PROP)).toBe(false);
93 | });
94 |
95 | it("outlineOffset", () => {
96 | const PROP = "outlineOffset";
97 | const VALUE = "5px";
98 | const result = resolveLayoutProps({ [PROP]: VALUE });
99 | expect(result.cssProps[PROP]).toBe(VALUE);
100 | expect(result.issues.has(PROP)).toBe(false);
101 | });
102 |
103 | it("borderHorizontal: 1px solid red", () => {
104 | const PROP = "borderHorizontal";
105 | const VALUE = "1px solid red";
106 | const result = resolveLayoutProps({ [PROP]: VALUE });
107 | expect(result.cssProps.borderLeft).toBe(VALUE);
108 | expect(result.cssProps.borderRight).toBe(VALUE);
109 | expect(result.issues.has(PROP)).toBe(false);
110 | });
111 |
112 | it("borderLeft overwrites borderHorizontal", () => {
113 | const PROP = "borderHorizontal";
114 | const VALUE = "1px solid red";
115 | const result = resolveLayoutProps({ [PROP]: VALUE, borderLeft: "2px solid blue" });
116 | expect(result.cssProps.borderLeft).toBe("2px solid blue");
117 | expect(result.cssProps.borderRight).toBe(VALUE);
118 | expect(result.issues.has(PROP)).toBe(false);
119 | });
120 |
121 | it("borderRight overwrites borderHorizontal", () => {
122 | const PROP = "borderHorizontal";
123 | const VALUE = "1px solid red";
124 | const result = resolveLayoutProps({ [PROP]: VALUE, borderRight: "2px solid blue" });
125 | expect(result.cssProps.borderLeft).toBe(VALUE);
126 | expect(result.cssProps.borderRight).toBe("2px solid blue");
127 | expect(result.issues.has(PROP)).toBe(false);
128 | });
129 |
130 | it("borderHorizontal (themeVar) 1", () => {
131 | const PROP = "borderHorizontal";
132 | const VALUE = THEME_ID;
133 | const result = resolveLayoutProps({ [PROP]: VALUE });
134 | expect(result.cssProps.borderLeft).toBe(THEME_ID_VALUE);
135 | expect(result.cssProps.borderRight).toBe(THEME_ID_VALUE);
136 | expect(result.issues.has(PROP)).toBe(false);
137 | });
138 |
139 | it("borderHorizontal (themeVar) 2", () => {
140 | const PROP = "borderHorizontal";
141 | const VALUE = `${THEME_ID} ${THEME_ID2}`;
142 | const result = resolveLayoutProps({ [PROP]: VALUE });
143 | expect(result.cssProps.borderLeft).toBe(`${THEME_ID_VALUE} ${THEME_ID2_VALUE}`);
144 | expect(result.cssProps.borderRight).toBe(`${THEME_ID_VALUE} ${THEME_ID2_VALUE}`);
145 | expect(result.issues.has(PROP)).toBe(false);
146 | });
147 |
148 | it("borderHorizontal (themeVar) 3", () => {
149 | const PROP = "borderHorizontal";
150 | const VALUE = `${THEME_ID} ${THEME_ID2} ${THEME_ID3}`;
151 | const result = resolveLayoutProps({ [PROP]: VALUE });
152 | expect(result.cssProps.borderLeft).toBe(`${THEME_ID_VALUE} ${THEME_ID2_VALUE} ${THEME_ID3_VALUE}`);
153 | expect(result.cssProps.borderRight).toBe(`${THEME_ID_VALUE} ${THEME_ID2_VALUE} ${THEME_ID3_VALUE}`);
154 | expect(result.issues.has(PROP)).toBe(false);
155 | });
156 |
157 | it("borderVertical: 1px solid red", () => {
158 | const PROP = "borderVertical";
159 | const VALUE = "1px solid red";
160 | const result = resolveLayoutProps({ [PROP]: VALUE });
161 | expect(result.cssProps.borderTop).toBe(VALUE);
162 | expect(result.cssProps.borderBottom).toBe(VALUE);
163 | expect(result.issues.has(PROP)).toBe(false);
164 | });
165 |
166 | it("borderVertical (themeVar) 1", () => {
167 | const PROP = "borderVertical";
168 | const VALUE = THEME_ID;
169 | const result = resolveLayoutProps({ [PROP]: VALUE });
170 | expect(result.cssProps.borderTop).toBe(THEME_ID_VALUE);
171 | expect(result.cssProps.borderBottom).toBe(THEME_ID_VALUE);
172 | expect(result.issues.has(PROP)).toBe(false);
173 | });
174 |
175 | it("borderVertical (themeVar) 2", () => {
176 | const PROP = "borderVertical";
177 | const VALUE = `${THEME_ID} ${THEME_ID2}`;
178 | const result = resolveLayoutProps({ [PROP]: VALUE });
179 | expect(result.cssProps.borderTop).toBe(`${THEME_ID_VALUE} ${THEME_ID2_VALUE}`);
180 | expect(result.cssProps.borderBottom).toBe(`${THEME_ID_VALUE} ${THEME_ID2_VALUE}`);
181 | expect(result.issues.has(PROP)).toBe(false);
182 | });
183 |
184 | it("borderVertical (themeVar) 3", () => {
185 | const PROP = "borderVertical";
186 | const VALUE = `${THEME_ID} ${THEME_ID2} ${THEME_ID3}`;
187 | const result = resolveLayoutProps({ [PROP]: VALUE });
188 | expect(result.cssProps.borderTop).toBe(`${THEME_ID_VALUE} ${THEME_ID2_VALUE} ${THEME_ID3_VALUE}`);
189 | expect(result.cssProps.borderBottom).toBe(`${THEME_ID_VALUE} ${THEME_ID2_VALUE} ${THEME_ID3_VALUE}`);
190 | expect(result.issues.has(PROP)).toBe(false);
191 | });
192 |
193 | it("borderTop overwrites borderVertical", () => {
194 | const PROP = "borderVertical";
195 | const VALUE = "1px solid red";
196 | const result = resolveLayoutProps({ [PROP]: VALUE, borderTop: "2px solid blue" });
197 | expect(result.cssProps.borderTop).toBe("2px solid blue");
198 | expect(result.cssProps.borderBottom).toBe(VALUE);
199 | expect(result.issues.has(PROP)).toBe(false);
200 | });
201 |
202 | it("borderBottom overwrites borderVertical", () => {
203 | const PROP = "borderVertical";
204 | const VALUE = "1px solid red";
205 | const result = resolveLayoutProps({ [PROP]: VALUE, borderBottom: "2px solid blue" });
206 | expect(result.cssProps.borderTop).toBe(VALUE);
207 | expect(result.cssProps.borderBottom).toBe("2px solid blue");
208 | expect(result.issues.has(PROP)).toBe(false);
209 | });
210 | });
211 |
```
--------------------------------------------------------------------------------
/xmlui/src/components-core/interception/IndexedDb.ts:
--------------------------------------------------------------------------------
```typescript
1 | import type { Table } from "dexie";
2 | import Dexie from "dexie";
3 |
4 | import { ReadOnlyCollection } from "../interception/ReadonlyCollection";
5 | import type { IDatabase, TableDescriptor } from "./abstractions";
6 |
7 | export class IndexedDb implements IDatabase {
8 | // private repository: Record<string, Array<any>>;
9 | // private maxIdsByCollections: Record<string, number> = {};
10 | private db: Dexie | null = null;
11 |
12 | [key: string]: unknown;
13 |
14 | constructor(
15 | private tables: Array<TableDescriptor> | undefined,
16 | private initialData: Record<string, any[]> | (() => Promise<Record<string, any[]>>) = {},
17 | private config?: any,
18 | ) {}
19 |
20 | private getDb() {
21 | if (this.db === null) {
22 | throw new Error("Db is not initialized yet");
23 | }
24 | return this.db;
25 | }
26 |
27 | public async initialize() {
28 | const resolvedInitialData =
29 | typeof this.initialData === "function" ? await this.initialData() : this.initialData;
30 | const schema: Record<string, string> = {};
31 | const tableNames = new Set<string>();
32 | if (this.tables) {
33 | this.tables.forEach((tableDescriptor) => {
34 | const schemaArray = [];
35 | if (tableDescriptor.pk.length === 1) {
36 | schemaArray.push(tableDescriptor.pk[0]);
37 | } else {
38 | schemaArray.push(`[${tableDescriptor.pk.join("+")}]`); //indexeddb compound index looks like this: [field+anotherfield]
39 | }
40 | if (tableDescriptor.indexes) {
41 | schemaArray.push(...tableDescriptor.indexes);
42 | }
43 | schema[tableDescriptor.name] = schemaArray.join(", ");
44 | tableNames.add(tableDescriptor.name);
45 | });
46 | } else {
47 | Object.entries(resolvedInitialData).forEach(([key]) => {
48 | schema[key] = "++id";
49 | tableNames.add(key);
50 | });
51 | }
52 |
53 | const targetVersion =
54 | this.config?.version !== undefined && typeof this.config?.version === "number"
55 | ? this.config?.version
56 | : 1;
57 |
58 | const shouldInitializeData = await this.dropDbOnVersionChange(targetVersion);
59 |
60 | this.db = this.createDbInstance();
61 | this.db.version(targetVersion).stores(schema);
62 |
63 | if (shouldInitializeData) {
64 | await Promise.all(
65 | Object.entries(resolvedInitialData).map(async ([key, value]) => {
66 | try {
67 | await this.getDb().table(key).bulkAdd(value);
68 | } catch (ignored) {
69 | console.error(ignored);
70 | }
71 | }),
72 | );
73 | }
74 |
75 | tableNames.forEach((key) => {
76 | this[`$${key}`] = createTableWrapper(this.getDb().table(key));
77 | });
78 | }
79 |
80 | private createDbInstance() {
81 | return new Dexie(this.config?.database ?? "defaultIndexDb");
82 | }
83 |
84 | private async dropDbOnVersionChange(targetVersion: number) {
85 | const tempDb = this.createDbInstance();
86 | if (!(await Dexie.exists(tempDb.name))) {
87 | return true;
88 | }
89 | await tempDb.open();
90 | if (tempDb.verno !== targetVersion) {
91 | await tempDb.delete();
92 | return true;
93 | } else {
94 | tempDb.close();
95 | return false;
96 | }
97 | }
98 |
99 | public getItems = (resourceId: string) => this.getDb().table(resourceId).toArray();
100 |
101 | public findItems = async (resourceId: string, predicate: (item: any) => Promise<boolean>) => {
102 | const ret = await this.getItems(resourceId);
103 | const results = await Promise.all(ret.map(predicate));
104 | return ret.filter((_v, index) => results[index]);
105 | };
106 |
107 | public getItem = async (resourceId: string, predicate: (item: any) => Promise<boolean>) => {
108 | const ret = await this.getItems(resourceId);
109 | const results = await Promise.all(ret.map(predicate));
110 | return ret.find((_v, index) => results[index]);
111 | };
112 |
113 | public getItemById = async (resourceId: string, id: any) => {
114 | return await this.getItem(resourceId, (item) => {
115 | return Promise.resolve(item.id + "" === id + "");
116 | });
117 | };
118 |
119 | public deleteItems = async (resourceId: string, predicate: (item: any) => Promise<boolean>) => {
120 | // this.repository[resourceId] = this.repository[resourceId]?.filter((item) => !predicate(item));
121 | };
122 |
123 | public insertItem = async (resourceId: string, item: any) => {
124 | const id = await this.getDb().table(resourceId).add(item);
125 | return this.getItemById(resourceId, id);
126 | };
127 |
128 | public updateItem = async (resourceId: string, item: any) => {
129 | await this.getDb().table(resourceId).update(item.id, item);
130 | return await this.getItemById(resourceId, item.id);
131 | };
132 |
133 | // --- This method signifies that a section of operations is executed in a transaction
134 | async transaction(actions: () => Promise<void>): Promise<void> {
135 | if (!this.db) return;
136 |
137 | const tables = this.db.tables;
138 | await this.db.transaction("rw", tables, actions);
139 | }
140 | }
141 |
142 | // Wraps an indexDb Table into an object that provides helpful methods
143 | function createTableWrapper(table: Table): any {
144 | // --- Function to retrieve the current table data
145 | const getDataFn = () => table.db.table(table.name);
146 |
147 | // --- Helper method to filter the table data
148 | const filteredData = async (predicate?: (item: any) => Promise<boolean>) => {
149 | const dataSnapshot = await table.toArray();
150 | const results = await Promise.all(dataSnapshot.map(predicate ?? (() => Promise.resolve(true))));
151 | return dataSnapshot.filter((_v, index) => results[index]);
152 | };
153 |
154 | return {
155 | native: getDataFn,
156 | insert: async (item: any) => {
157 | const id = await table.add(item);
158 | return getDataFn().get(id);
159 | },
160 | update: async (item: any) => {
161 | await table.update(item.id, item);
162 | return getDataFn().get(item.id);
163 | },
164 | save: async (item: any) => {
165 | const key = await table.put(item);
166 | return table.get(key);
167 | },
168 | deleteById: async (id: any) => {
169 | await table.delete(id);
170 | },
171 | byId: async (id: any) => {
172 | if (id === undefined || id === null) {
173 | return null;
174 | }
175 | let safeId = id;
176 | if (table.schema.primKey.src === "++id") {
177 | //it's an auto incremented id, must be a number
178 | safeId = Number(id);
179 | }
180 | return await table.get(safeId);
181 | },
182 | toArray: async () => await table.toArray(),
183 | single: async (predicate: (item: any) => Promise<boolean>) =>
184 | await new ReadOnlyCollection(await table.toArray()).single(predicate),
185 | singleOrDefault: async (predicate: (item: any) => Promise<boolean>, defValue?: any) => {
186 | return await new ReadOnlyCollection(await table.toArray()).singleOrDefault(
187 | predicate,
188 | defValue,
189 | );
190 | },
191 | where: async (predicate: (item: any) => Promise<boolean>) =>
192 | new ReadOnlyCollection(await filteredData(predicate)),
193 | whereAsArray: async (predicate: (item: any) => Promise<boolean>) =>
194 | await filteredData(predicate),
195 | orderBy: async (...mappers: any[]) =>
196 | await new ReadOnlyCollection(await table.toArray()).orderBy(...mappers),
197 | orderByAsArray: async (...mappers: any[]) =>
198 | await new ReadOnlyCollection(await table.toArray()).orderByAsArray(...mappers),
199 | groupBy: async (groupKey: (item: any) => Promise<any>) =>
200 | await new ReadOnlyCollection(await table.toArray()).groupBy(groupKey),
201 | groupByAsArray: async (groupKey: (item: any) => Promise<any>) =>
202 | await new ReadOnlyCollection(await table.toArray()).groupByAsArray(groupKey),
203 | distinct: async (distinctValue?: (item: any) => Promise<any>) =>
204 | await new ReadOnlyCollection(await table.toArray()).distinct(distinctValue),
205 | distinctAsArray: async (distinctValue?: (item: any) => Promise<any>) =>
206 | await new ReadOnlyCollection(await table.toArray()).distinctAsArray(distinctValue),
207 | maxValue: async (fieldName = "id", predicate?: (item: any) => Promise<boolean>) => {
208 | return await new ReadOnlyCollection(await table.toArray()).maxValue(fieldName, predicate);
209 | },
210 | skip: async (count: number) => {
211 | return await new ReadOnlyCollection(await table.toArray()).skip(count);
212 | },
213 | take: async (count: number) => {
214 | return await new ReadOnlyCollection(await table.toArray()).take(count);
215 | },
216 | skipTake: async (skip: number, take: number) => {
217 | return await new ReadOnlyCollection(await table.toArray()).skipTake(skip, take);
218 | },
219 | };
220 | }
221 |
```
--------------------------------------------------------------------------------
/xmlui/src/components/IconRegistryContext.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import React, { useCallback, useContext } from "react";
2 |
3 | import type { IconRegistry } from "./IconProvider";
4 | import { useIsomorphicLayoutEffect } from "../components-core/utils/hooks";
5 |
6 | export const IconRegistryContext = React.createContext<IconRegistry | null>(null);
7 |
8 | export function useIconRegistry() {
9 | return useContext(IconRegistryContext)!;
10 | }
11 |
12 | //https://github.com/hatashiro/react-attr-converter/tree/master
13 | const svgAttributeMap: Record<string, string> = {
14 | // SVG attributes
15 | accentheight: "accentHeight",
16 | accumulate: "accumulate",
17 | additive: "additive",
18 | alignmentbaseline: "alignmentBaseline",
19 | allowreorder: "allowReorder",
20 | alphabetic: "alphabetic",
21 | amplitude: "amplitude",
22 | arabicform: "arabicForm",
23 | ascent: "ascent",
24 | attributename: "attributeName",
25 | attributetype: "attributeType",
26 | autoreverse: "autoReverse",
27 | azimuth: "azimuth",
28 | basefrequency: "baseFrequency",
29 | baseprofile: "baseProfile",
30 | baselineshift: "baselineShift",
31 | bbox: "bbox",
32 | begin: "begin",
33 | bias: "bias",
34 | by: "by",
35 | calcmode: "calcMode",
36 | capheight: "capHeight",
37 | clip: "clip",
38 | clippath: "clipPath",
39 | clippathunits: "clipPathUnits",
40 | cliprule: "clipRule",
41 | colorinterpolation: "colorInterpolation",
42 | colorinterpolationfilters: "colorInterpolationFilters",
43 | colorprofile: "colorProfile",
44 | colorrendering: "colorRendering",
45 | contentscripttype: "contentScriptType",
46 | contentstyletype: "contentStyleType",
47 | cursor: "cursor",
48 | cx: "cx",
49 | cy: "cy",
50 | d: "d",
51 | decelerate: "decelerate",
52 | descent: "descent",
53 | diffuseconstant: "diffuseConstant",
54 | direction: "direction",
55 | display: "display",
56 | divisor: "divisor",
57 | dominantbaseline: "dominantBaseline",
58 | dur: "dur",
59 | dx: "dx",
60 | dy: "dy",
61 | edgemode: "edgeMode",
62 | elevation: "elevation",
63 | enablebackground: "enableBackground",
64 | end: "end",
65 | exponent: "exponent",
66 | externalresourcesrequired: "externalResourcesRequired",
67 | fill: "fill",
68 | fillopacity: "fillOpacity",
69 | fillrule: "fillRule",
70 | filter: "filter",
71 | filterres: "filterRes",
72 | filterunits: "filterUnits",
73 | floodcolor: "floodColor",
74 | floodopacity: "floodOpacity",
75 | focusable: "focusable",
76 | fontfamily: "fontFamily",
77 | fontsize: "fontSize",
78 | fontsizeadjust: "fontSizeAdjust",
79 | fontstretch: "fontStretch",
80 | fontstyle: "fontStyle",
81 | fontvariant: "fontVariant",
82 | fontweight: "fontWeight",
83 | format: "format",
84 | from: "from",
85 | fx: "fx",
86 | fy: "fy",
87 | g1: "g1",
88 | g2: "g2",
89 | glyphname: "glyphName",
90 | glyphorientationhorizontal: "glyphOrientationHorizontal",
91 | glyphorientationvertical: "glyphOrientationVertical",
92 | glyphref: "glyphRef",
93 | gradienttransform: "gradientTransform",
94 | gradientunits: "gradientUnits",
95 | hanging: "hanging",
96 | horizadvx: "horizAdvX",
97 | horizoriginx: "horizOriginX",
98 | ideographic: "ideographic",
99 | imagerendering: "imageRendering",
100 | in: "in",
101 | in2: "in2",
102 | intercept: "intercept",
103 | k: "k",
104 | k1: "k1",
105 | k2: "k2",
106 | k3: "k3",
107 | k4: "k4",
108 | kernelmatrix: "kernelMatrix",
109 | kernelunitlength: "kernelUnitLength",
110 | kerning: "kerning",
111 | keypoints: "keyPoints",
112 | keysplines: "keySplines",
113 | keytimes: "keyTimes",
114 | lengthadjust: "lengthAdjust",
115 | letterspacing: "letterSpacing",
116 | lightingcolor: "lightingColor",
117 | limitingconeangle: "limitingConeAngle",
118 | local: "local",
119 | markerend: "markerEnd",
120 | markerheight: "markerHeight",
121 | markermid: "markerMid",
122 | markerstart: "markerStart",
123 | markerunits: "markerUnits",
124 | markerwidth: "markerWidth",
125 | mask: "mask",
126 | maskcontentunits: "maskContentUnits",
127 | maskunits: "maskUnits",
128 | mathematical: "mathematical",
129 | mode: "mode",
130 | numoctaves: "numOctaves",
131 | offset: "offset",
132 | opacity: "opacity",
133 | operator: "operator",
134 | order: "order",
135 | orient: "orient",
136 | orientation: "orientation",
137 | origin: "origin",
138 | overflow: "overflow",
139 | overlineposition: "overlinePosition",
140 | overlinethickness: "overlineThickness",
141 | paintorder: "paintOrder",
142 | panose1: "panose1",
143 | pathlength: "pathLength",
144 | patterncontentunits: "patternContentUnits",
145 | patterntransform: "patternTransform",
146 | patternunits: "patternUnits",
147 | pointerevents: "pointerEvents",
148 | points: "points",
149 | pointsatx: "pointsAtX",
150 | pointsaty: "pointsAtY",
151 | pointsatz: "pointsAtZ",
152 | preservealpha: "preserveAlpha",
153 | preserveaspectratio: "preserveAspectRatio",
154 | primitiveunits: "primitiveUnits",
155 | r: "r",
156 | radius: "radius",
157 | refx: "refX",
158 | refy: "refY",
159 | renderingintent: "renderingIntent",
160 | repeatcount: "repeatCount",
161 | repeatdur: "repeatDur",
162 | requiredextensions: "requiredExtensions",
163 | requiredfeatures: "requiredFeatures",
164 | restart: "restart",
165 | result: "result",
166 | rotate: "rotate",
167 | rx: "rx",
168 | ry: "ry",
169 | scale: "scale",
170 | seed: "seed",
171 | shaperendering: "shapeRendering",
172 | slope: "slope",
173 | spacing: "spacing",
174 | specularconstant: "specularConstant",
175 | specularexponent: "specularExponent",
176 | speed: "speed",
177 | spreadmethod: "spreadMethod",
178 | startoffset: "startOffset",
179 | stddeviation: "stdDeviation",
180 | stemh: "stemh",
181 | stemv: "stemv",
182 | stitchtiles: "stitchTiles",
183 | stopcolor: "stopColor",
184 | stopopacity: "stopOpacity",
185 | strikethroughposition: "strikethroughPosition",
186 | strikethroughthickness: "strikethroughThickness",
187 | string: "string",
188 | stroke: "stroke",
189 | strokedasharray: "strokeDasharray",
190 | strokedashoffset: "strokeDashoffset",
191 | strokelinecap: "strokeLinecap",
192 | strokelinejoin: "strokeLinejoin",
193 | strokemiterlimit: "strokeMiterlimit",
194 | strokeopacity: "strokeOpacity",
195 | strokewidth: "strokeWidth",
196 | surfacescale: "surfaceScale",
197 | systemlanguage: "systemLanguage",
198 | tablevalues: "tableValues",
199 | targetx: "targetX",
200 | targety: "targetY",
201 | textanchor: "textAnchor",
202 | textdecoration: "textDecoration",
203 | textlength: "textLength",
204 | textrendering: "textRendering",
205 | to: "to",
206 | transform: "transform",
207 | u1: "u1",
208 | u2: "u2",
209 | underlineposition: "underlinePosition",
210 | underlinethickness: "underlineThickness",
211 | unicode: "unicode",
212 | unicodebidi: "unicodeBidi",
213 | unicoderange: "unicodeRange",
214 | unitsperem: "unitsPerEm",
215 | valphabetic: "vAlphabetic",
216 | vhanging: "vHanging",
217 | videographic: "vIdeographic",
218 | vmathematical: "vMathematical",
219 | values: "values",
220 | vectoreffect: "vectorEffect",
221 | version: "version",
222 | vertadvy: "vertAdvY",
223 | vertoriginx: "vertOriginX",
224 | vertoriginy: "vertOriginY",
225 | viewbox: "viewBox",
226 | viewtarget: "viewTarget",
227 | visibility: "visibility",
228 | widths: "widths",
229 | wordspacing: "wordSpacing",
230 | writingmode: "writingMode",
231 | x: "x",
232 | x1: "x1",
233 | x2: "x2",
234 | xchannelselector: "xChannelSelector",
235 | xheight: "xHeight",
236 | xlinkactuate: "xlinkActuate",
237 | xlinkarcrole: "xlinkArcrole",
238 | xlinkhref: "xlinkHref",
239 | xlinkrole: "xlinkRole",
240 | xlinkshow: "xlinkShow",
241 | xlinktitle: "xlinkTitle",
242 | xlinktype: "xlinkType",
243 | xmlns: "xmlns",
244 | xmlnsxlink: "xmlnsXlink",
245 | xmlbase: "xmlBase",
246 | xmllang: "xmlLang",
247 | xmlspace: "xmlSpace",
248 | y: "y",
249 | y1: "y1",
250 | y2: "y2",
251 | ychannelselector: "yChannelSelector",
252 | z: "z",
253 | zoomandpan: "zoomAndPan",
254 | };
255 |
256 | const extraCharRegex = /[-:]/g;
257 |
258 | export function useCustomSvgIconRenderer(resourceUrl?: string) {
259 | const { ensureCustomSvgIcon, customSvgs } = useIconRegistry();
260 | useIsomorphicLayoutEffect(() => {
261 | if (!resourceUrl) {
262 | return;
263 | }
264 | void ensureCustomSvgIcon(resourceUrl);
265 | }, [ensureCustomSvgIcon, resourceUrl]);
266 | const customSvg = resourceUrl ? customSvgs[resourceUrl] : null;
267 |
268 | const iconRenderer = useCallback(
269 | ({ style, className }: any) => {
270 | if (!customSvg) {
271 | return null;
272 | }
273 | const { attributes, name } = customSvg;
274 | const safeAttributes: any = {};
275 | Object.entries(attributes).forEach(([key, value]) => {
276 | let safeKey = key;
277 | if (/^(data-|aria-)/.test(key)) {
278 | safeKey = key;
279 | } else {
280 | safeKey = key.replace(extraCharRegex, "").toLowerCase();
281 | }
282 |
283 | safeAttributes[svgAttributeMap[safeKey] || key] = value;
284 | });
285 | return (
286 | <svg {...safeAttributes} style={style} className={className}>
287 | <use href={`#${name}`} />
288 | </svg>
289 | );
290 | },
291 | [customSvg],
292 | );
293 |
294 | return !resourceUrl ? null : iconRenderer;
295 | }
296 |
```
--------------------------------------------------------------------------------
/xmlui/src/components/Select/Select.md:
--------------------------------------------------------------------------------
```markdown
1 | %-DESC-START
2 |
3 | **Key features:**
4 | - **Flexible selection modes**: Single selection by default, with optional multi-select capability
5 | - **Option containers**: Uses Option components to define selectable items with separate values and labels
6 | - **Search functionality**: Optional filtering to quickly find options in large lists
7 | - **Custom templates**: Configurable option display, value presentation, and empty state templates
8 | - **Dynamic options**: Supports both static [Option](/components/Option) children and dynamic lists via [Items](/components/Items).
9 |
10 | ## Using `Select`
11 |
12 | The component accepts `Option` components as children defining a particular option's label-value pair.
13 | `Option` requires a `value` property and while also having a `label` that is displayed in the list.
14 | If the `label` is not specified `value` is shown.
15 |
16 | ```xmlui-pg copy display name="Example: using Select" height="200px"
17 | <App>
18 | <Select>
19 | <Option value="opt1" label="first"/>
20 | <Option value="opt2" label="second"/>
21 | <Option value="opt3" label="third"/>
22 | </Select>
23 | </App>
24 | ```
25 |
26 | You can use `Select` with dynamic options:
27 |
28 | ```xmlui-pg copy display name="Example: using Select with dynamic options" height="200px"
29 | <App>
30 | <Select>
31 | <Items data="{['one', 'two', 'three']}" >
32 | <Option value="{$itemIndex}" label="{$item}" />
33 | </Items>
34 | </Select>
35 | </App>
36 | ```
37 |
38 | %-DESC-END
39 |
40 | %-PROP-START initialValue
41 |
42 | ```xmlui-pg copy display name="Example: initialValue" height="200px"
43 | <App>
44 | <Select initialValue="opt3">
45 | <Option value="opt1" label="first"/>
46 | <Option value="opt2" label="second"/>
47 | <Option value="opt3" label="third"/>
48 | </Select>
49 | </App>
50 | ```
51 |
52 | %-PROP-END
53 |
54 | %-PROP-START optionTemplate
55 |
56 | ```xmlui-pg copy display name="Example: optionTemplate" height="200px"
57 | <App>
58 | <Select>
59 | <property name="optionTemplate">
60 | <HStack verticalAlignment="center" gap="$space-0_5">
61 | <Icon name="info" />
62 | <Text value="{$item.label}" variant="strong" />
63 | </HStack>
64 | </property>
65 | <Option value="opt1" label="first"/>
66 | <Option value="opt2" label="second"/>
67 | <Option value="opt3" label="third"/>
68 | </Select>
69 | </App>
70 | ```
71 |
72 | %-PROP-END
73 |
74 | %-PROP-START placeholder
75 |
76 | ```xmlui-pg copy display name="Example: placeholder" height="200px"
77 | <App>
78 | <Select placeholder="Please select an item">
79 | <Option value="opt1" label="first"/>
80 | <Option value="opt2" label="second"/>
81 | <Option value="opt3" label="third"/>
82 | </Select>
83 | </App>
84 | ```
85 |
86 | %-PROP-END
87 |
88 | %-PROP-START validationStatus
89 |
90 | ```xmlui-pg copy display name="Example: validationStatus" height="280px"
91 | <App>
92 | <Select />
93 | <Select validationStatus="valid" />
94 | <Select validationStatus="warning" />
95 | <Select validationStatus="error" />
96 | </App>
97 | ```
98 |
99 | %-PROP-END
100 |
101 | %-PROP-START enabled
102 |
103 | ```xmlui-pg copy display name="Example: enabled"
104 | <App>
105 | <Select enabled="false" />
106 | </App>
107 | ```
108 |
109 | %-PROP-END
110 |
111 | %-PROP-START emptyListTemplate
112 |
113 | Click on the second field to see the custom empty list indicator.
114 |
115 | ```xmlui-pg copy {9-11} display name="Example: emptyListTemplate" height="260px"
116 | <App>
117 | <VStack>
118 | <Text value="Default:" />
119 | <Select />
120 | </VStack>
121 | <VStack>
122 | <Text value="Custom:" />
123 | <Select>
124 | <property name="emptyListTemplate">
125 | <Text variant="strong" value="Nothing to see here!" />
126 | </property>
127 | </Select>
128 | </VStack>
129 | </App>
130 | ```
131 |
132 | %-PROP-END
133 |
134 | %-PROP-START dropdownHeight
135 |
136 | ```xmlui-pg copy display name="Example: dropdownHeight" height="300px"
137 | <App>
138 | <Select dropdownHeight="180px">
139 | <Option value="opt1" label="first"/>
140 | <Option value="opt2" label="second"/>
141 | <Option value="opt3" label="third"/>
142 | <Option value="opt4" label="fourth"/>
143 | <Option value="opt5" label="fifth"/>
144 | <Option value="opt6" label="sixth"/>
145 | <Option value="opt7" label="seventh"/>
146 | <Option value="opt8" label="eighth"/>
147 | <Option value="opt9" label="ninth"/>
148 | <Option value="opt10" label="tenth"/>
149 | <Option value="opt11" label="eleventh"/>
150 | <Option value="opt12" label="twelfth"/>
151 | </Select>
152 | </App>
153 | ```
154 |
155 | %-PROP-END
156 |
157 | %-PROP-START multiSelect
158 |
159 | ```xmlui-pg copy display name="Example: multiSelect" height="300px"
160 | <App>
161 | <Select multiSelect="true" dropdownHeight="180px" >
162 | <Option value="opt1" label="first"/>
163 | <Option value="opt2" label="second"/>
164 | <Option value="opt3" label="third"/>
165 | <Option value="opt4" label="fourth"/>
166 | <Option value="opt5" label="fifth"/>
167 | <Option value="opt6" label="sixth"/>
168 | <Option value="opt7" label="seventh"/>
169 | <Option value="opt8" label="eighth"/>
170 | <Option value="opt9" label="ninth"/>
171 | <Option value="opt10" label="tenth"/>
172 | <Option value="opt11" label="eleventh"/>
173 | <Option value="opt12" label="twelfth"/>
174 | </Select>
175 | </App>
176 | ```
177 |
178 | %-PROP-END
179 |
180 | %-PROP-START optionLabelTemplate
181 |
182 | In the template definition, you can use the `$item` context property to access the particular item's `label` and `value`.
183 |
184 | ```xmlui-pg copy {3-9} display name="Example: optionLabelTemplate" height="300px"
185 | <App>
186 | <Select initialValue="{0}" placeholder="Select..." searchable>
187 | <property name="optionLabelTemplate">
188 | <HStack
189 | paddingHorizontal="$padding-tight"
190 | border="2px dotted $color-primary-500">
191 | <Text>{$item.label}</Text>
192 | </HStack>
193 | </property>
194 | <Option value="{0}" label="zero"/>
195 | <Option value="opt1" label="first"/>
196 | <Option value="opt2" label="second"/>
197 | <Option value="opt3" label="third"/>
198 | </Select>
199 | </App>
200 | ```
201 |
202 | %-PROP-END
203 |
204 | %-PROP-START valueTemplate
205 |
206 | In the template definition, you can use the `$item` context property to access the particular item's `label` and `value`. The `$itemContext` property provides a `removeItem` method to delete a value from the current selection.
207 |
208 | ```xmlui-pg copy {3-15} display name="Example: valueTemplate" height="300px"
209 | <App>
210 | <Select initialValue="{0}" placeholder="Select..." multiSelect>
211 | <property name="valueTemplate">
212 | <HStack
213 | paddingLeft="$padding-tight"
214 | border="2px dotted $color-primary-500"
215 | verticalAlignment="center">
216 | <Text>{$item.label}</Text>
217 | <Button
218 | variant="ghost"
219 | icon="close"
220 | size="xs"
221 | onClick="$itemContext.removeItem()"/>
222 | </HStack>
223 | </property>
224 | <Option value="{0}" label="zero"/>
225 | <Option value="opt1" label="first"/>
226 | <Option value="opt2" label="second"/>
227 | <Option value="opt3" label="third"/>
228 | </Select>
229 | </App>
230 | ```
231 |
232 | %-PROP-END
233 |
234 | %-EVENT-START didChange
235 |
236 | ```xmlui-pg copy display name="Example: didChange" height="260px"
237 | <App>
238 | <variable name="newValue" value="(none)" />
239 | <Text value="{newValue}" />
240 | <Select onDidChange="(newItem) => newValue = newItem">
241 | <Option value="opt1" label="first"/>
242 | <Option value="opt2" label="second"/>
243 | <Option value="opt3" label="third"/>
244 | </Select>
245 | </App>
246 | ```
247 |
248 | %-EVENT-END
249 |
250 | %-EVENT-START gotFocus
251 |
252 | ```xmlui-pg copy {5-6} display name="Example: gotFocus/lostFocus" height="260px"
253 | <App>
254 | <variable name="isFocused" value="{false}" />
255 | <Text value="Input control is focused: {isFocused}" />
256 | <Select
257 | onGotFocus="isFocused = true"
258 | onLostFocus="isFocused = false">
259 | <Option value="opt1" label="first"/>
260 | <Option value="opt2" label="second"/>
261 | <Option value="opt3" label="third"/>
262 | </Select>
263 | </App>
264 | ```
265 |
266 | %-EVENT-END
267 |
268 | %-API-START focus
269 |
270 | ```xmlui-pg copy display name="Example: focus()" height="260px"
271 | <App>
272 | <Button label="Focus Input" onClick="inputControl.focus()" />
273 | <Select id="inputControl">
274 | <Option value="opt1" label="first"/>
275 | <Option value="opt2" label="second"/>
276 | <Option value="opt3" label="third"/>
277 | </Select>
278 | </App>
279 | ```
280 |
281 | %-API-END
282 |
283 | %-API-START setValue
284 |
285 | ```xmlui-pg copy display name="Example: setValue()" height="260px"
286 | <App>
287 | <Select id="inputControl">
288 | <Option value="opt1" label="first"/>
289 | <Option value="opt2" label="second"/>
290 | <Option value="opt3" label="third"/>
291 | </Select>
292 | <HStack>
293 | <Button
294 | label="Select 2nd Item"
295 | onClick="inputControl.setValue('opt2')" />
296 | <Button
297 | label="Remove Selection"
298 | onClick="inputControl.setValue('')" />
299 | </HStack>
300 | </App>
301 | ```
302 |
303 | %-API-END
304 |
```
--------------------------------------------------------------------------------
/xmlui/src/components-core/utils/statementUtils.ts:
--------------------------------------------------------------------------------
```typescript
1 | import {
2 | T_ARROW_EXPRESSION,
3 | T_ARROW_EXPRESSION_STATEMENT,
4 | T_BLOCK_STATEMENT,
5 | T_CALCULATED_MEMBER_ACCESS_EXPRESSION,
6 | T_EMPTY_STATEMENT,
7 | T_EXPRESSION_STATEMENT,
8 | T_FUNCTION_INVOCATION_EXPRESSION,
9 | T_IDENTIFIER,
10 | T_LITERAL,
11 | T_MEMBER_ACCESS_EXPRESSION,
12 | T_RETURN_STATEMENT,
13 | type ArrowExpression,
14 | type ArrowExpressionStatement,
15 | type BlockStatement,
16 | type EmptyStatement,
17 | type Expression,
18 | type ExpressionStatement,
19 | type FunctionInvocationExpression,
20 | type Identifier,
21 | type Statement,
22 | } from "../script-runner/ScriptingSourceTree";
23 | import type { QueueInfo } from "../script-runner/statement-queue";
24 | import type { BindingTreeEvaluationContext } from "../script-runner/BindingTreeEvaluationContext";
25 | import type { LogicalThread } from "../../abstractions/scripting/LogicalThread";
26 |
27 | import { Parser } from "../../parsers/scripting/Parser";
28 | import { processStatementQueueAsync } from "../script-runner/process-statement-async";
29 | import { reportEngineError } from "../reportEngineError";
30 | import { ScriptParseError } from "../EngineError";
31 |
32 | /**
33 | * Parse the specified source code as event handler code
34 | * @param source Event handler source code
35 | */
36 | export function parseHandlerCode(source: string): Statement[] {
37 | // --- Parse the event code
38 | const wParser = new Parser(source);
39 | let parsedStatements: Statement[] | null = null;
40 | try {
41 | // --- Invoke the parser
42 | parsedStatements = wParser.parseStatements();
43 | } catch (err) {
44 | // --- Parsing error with explicit error code
45 | if (wParser.errors.length > 0) {
46 | const err = wParser.errors[0];
47 | reportEngineError(
48 | new ScriptParseError(
49 | `${err.code}(${err.line}, ${err.column}): ${wParser.errors[0].text}`,
50 | source,
51 | err.position,
52 | ),
53 | );
54 | } else {
55 | throw err;
56 | }
57 | }
58 |
59 | // --- Check for the completeness of source code parsing
60 | if (!wParser.isEof) {
61 | const tail = wParser.getTail();
62 | reportEngineError(
63 | new ScriptParseError(`Invalid tail found`, source, source.length - tail.length + 1),
64 | );
65 | }
66 |
67 | // --- Done
68 | return parsedStatements!;
69 | }
70 |
71 | /**
72 | * Optionally transform statements in an event handler to an arrow expression statement
73 | * @param stmts Statements to transform
74 | * @param evalContext Optional event arguments
75 | */
76 | export function prepareHandlerStatements(
77 | stmts: Statement[],
78 | evalContext?: BindingTreeEvaluationContext,
79 | ): Statement[] {
80 | const stmtLength = stmts?.length ?? 0;
81 | if (stmtLength === 0) {
82 | // -- Use a no-op arrow function
83 | return [
84 | {
85 | type: T_ARROW_EXPRESSION_STATEMENT,
86 | expr: {
87 | type: T_ARROW_EXPRESSION,
88 | args: [],
89 | statement: {
90 | type: T_EMPTY_STATEMENT,
91 | } as EmptyStatement,
92 | } as ArrowExpression,
93 | } as ArrowExpressionStatement,
94 | ];
95 | }
96 |
97 | if (stmtLength === 1) {
98 | const stmt = stmts[0];
99 |
100 | if (stmt.type === T_EXPRESSION_STATEMENT) {
101 | // --- Handle single expression statements
102 | if (evalContext) {
103 | // --- We have a context in which the event handler is executed
104 | if (stmt.expr.type === T_IDENTIFIER) {
105 | // --- A single identifier, it is supposed to be an arrow function
106 | // --- Use this arrow expression
107 | return [convertExpressionToFunctionInvocation(stmt.expr)];
108 | }
109 |
110 | if (isMemberExpressionChain(stmt.expr)) {
111 | // --- A single member expression chain, it is supposed to be an arrow function
112 | // --- Use this arrow expression
113 | return [convertExpressionToFunctionInvocation(stmt.expr)];
114 | }
115 | }
116 |
117 | if (stmt.expr.type === T_ARROW_EXPRESSION) {
118 | // --- A single arrow expression
119 | return [
120 | {
121 | type: T_ARROW_EXPRESSION_STATEMENT,
122 | expr: stmt.expr,
123 | } as ArrowExpressionStatement,
124 | ];
125 | }
126 |
127 | // --- A single statement, turn into an arrow expression
128 | return [
129 | {
130 | type: T_ARROW_EXPRESSION_STATEMENT,
131 | expr: {
132 | type: T_ARROW_EXPRESSION,
133 | args: [],
134 | statement: stmts[0],
135 | } as ArrowExpression,
136 | } as ArrowExpressionStatement,
137 | ];
138 | }
139 |
140 | if (stmt.type === T_RETURN_STATEMENT) {
141 | // --- A single arrow expression with a return
142 | return [
143 | {
144 | type: T_ARROW_EXPRESSION_STATEMENT,
145 | expr: {
146 | type: T_ARROW_EXPRESSION,
147 | args: [],
148 | statement: {
149 | type: T_BLOCK_STATEMENT,
150 | stmts: [stmt],
151 | } as BlockStatement,
152 | } as ArrowExpression,
153 | } as ArrowExpressionStatement,
154 | ];
155 | }
156 |
157 | if (stmt.type === T_BLOCK_STATEMENT) {
158 | // --- A single block statement?
159 | if (
160 | stmt.stmts[0].type === T_EXPRESSION_STATEMENT &&
161 | stmt.stmts[0].expr.type === T_ARROW_EXPRESSION
162 | ) {
163 | // --- A single block statement with a single arrow expression?
164 | return [
165 | {
166 | type: T_ARROW_EXPRESSION_STATEMENT,
167 | expr: stmt.stmts[0].expr,
168 | } as ArrowExpressionStatement,
169 | ];
170 | } else {
171 | // --- Consider as a body of a no-arg arrow function
172 | return [
173 | {
174 | type: T_ARROW_EXPRESSION_STATEMENT,
175 | expr: {
176 | type: T_ARROW_EXPRESSION,
177 | args: [],
178 | statement: stmts[0],
179 | } as unknown as ArrowExpression,
180 | } as ArrowExpressionStatement,
181 | ];
182 | }
183 | }
184 | }
185 |
186 | if (stmtLength > 1) {
187 | // --- Use the statements as the body of a no-arg arrow function
188 | return [
189 | {
190 | type: T_ARROW_EXPRESSION_STATEMENT,
191 | expr: {
192 | type: T_ARROW_EXPRESSION,
193 | args: [],
194 | statement: {
195 | type: T_BLOCK_STATEMENT,
196 | stmts,
197 | } as BlockStatement,
198 | } as unknown as ArrowExpression,
199 | } as ArrowExpressionStatement,
200 | ];
201 | }
202 |
203 | // --- Nothing to transform
204 | return stmts;
205 |
206 | function isMemberExpressionChain(expr: Expression): boolean {
207 | return (
208 | (expr.type === T_MEMBER_ACCESS_EXPRESSION ||
209 | (expr.type === T_CALCULATED_MEMBER_ACCESS_EXPRESSION && expr.member.type === T_LITERAL)) &&
210 | (isMemberExpressionChain(expr.obj) || expr.obj.type === T_IDENTIFIER)
211 | );
212 | }
213 |
214 | function convertExpressionToFunctionInvocation(expr: Expression): ArrowExpressionStatement {
215 | // --- A single identifier, it is supposed to be an arrow function
216 | // --- Create formal arguments
217 | const formalArgs = evalContext.eventArgs
218 | ? evalContext.eventArgs.map(
219 | (_, idx) =>
220 | ({
221 | type: T_IDENTIFIER,
222 | name: `__arg@@#__${idx}__`,
223 | }) as Identifier,
224 | )
225 | : [];
226 |
227 | // --- Add formal argument with current values
228 | if (evalContext.eventArgs) {
229 | evalContext.eventArgs.forEach((val, idx) => {
230 | evalContext.localContext[`__arg@@#__${idx}__`] = val;
231 | });
232 | }
233 |
234 | // --- Create the arrow expression
235 | const arrowExpr: ArrowExpression = {
236 | type: T_ARROW_EXPRESSION,
237 | args: formalArgs,
238 | statement: {
239 | type: T_EXPRESSION_STATEMENT,
240 | expr: {
241 | type: T_FUNCTION_INVOCATION_EXPRESSION,
242 | obj: expr,
243 | arguments: [...formalArgs],
244 | } as unknown as FunctionInvocationExpression,
245 | } as ExpressionStatement,
246 | } as ArrowExpression;
247 |
248 | // --- Use this arrow expression
249 | return {
250 | type: T_ARROW_EXPRESSION_STATEMENT,
251 | expr: arrowExpr,
252 | } as ArrowExpressionStatement;
253 | }
254 | }
255 |
256 | /**
257 | * Runs the specified event handler code
258 | * @param source Event handler source code
259 | * @param evalContext Evaluation context to use
260 | * @param thread Logical thread to use
261 | * @param onStatementCompleted Callback for statement completion
262 | */
263 | export async function runEventHandlerCode(
264 | source: string,
265 | evalContext: BindingTreeEvaluationContext,
266 | thread?: LogicalThread,
267 | ): Promise<QueueInfo> {
268 | const statements = prepareHandlerStatements(parseHandlerCode(source));
269 | return await processStatementQueueAsync(statements, evalContext, thread);
270 | }
271 |
```
--------------------------------------------------------------------------------
/xmlui/src/components-core/rendering/valueExtractor.ts:
--------------------------------------------------------------------------------
```typescript
1 | import type { MutableRefObject } from "react";
2 | import memoizeOne from "memoize-one";
3 | import { isPlainObject, isString } from "lodash-es";
4 |
5 | import type { AppContextObject } from "../../abstractions/AppContextDefs";
6 | import type { MemoedVars } from "../abstractions/ComponentRenderer";
7 | import { parseParameterString } from "../script-runner/ParameterParser";
8 | import type { ComponentApi, ContainerState } from "../rendering/ContainerWrapper";
9 | import { isPrimitive, pickFromObject, shallowCompare } from "../utils/misc";
10 | import { collectVariableDependencies } from "../script-runner/visitors";
11 | import { extractParam } from "../utils/extractParam";
12 | import { StyleParser, toCssVar } from "../../parsers/style-parser/StyleParser";
13 | import type { ValueExtractor } from "../../abstractions/RendererDefs";
14 |
15 | function parseStringArray(input: string): string[] {
16 | const trimmedInput = input.trim();
17 |
18 | if (trimmedInput.startsWith("[") && trimmedInput.endsWith("]")) {
19 | const content = trimmedInput.slice(1, -1);
20 | const items = content.split(",").map((item) => item.trim());
21 | return items.map((item) => item.replace(/^['"]|['"]$/g, ""));
22 | } else {
23 | throw new Error("Invalid array format");
24 | }
25 | }
26 |
27 | function collectParams(expression: any) {
28 | const params = [];
29 |
30 | if (typeof expression === "string") {
31 | params.push(...parseParameterString(expression));
32 | } else if (Array.isArray(expression)) {
33 | expression.forEach((exp) => {
34 | params.push(...collectParams(exp));
35 | });
36 | } else if (isPlainObject(expression)) {
37 | Object.entries(expression).forEach(([key, value]) => {
38 | params.push(...collectParams(value));
39 | });
40 | }
41 |
42 | return params;
43 | }
44 |
45 | export function asOptionalBoolean(value: any, defValue?: boolean | undefined) {
46 | if (value === undefined || value === null) return defValue;
47 |
48 | // Empty array returns false
49 | if (Array.isArray(value) && value.length === 0) {
50 | return false;
51 | }
52 |
53 | // Empty object returns false
54 | if (typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length === 0) {
55 | return false;
56 | }
57 |
58 | if (typeof value === "number") {
59 | return value !== 0;
60 | }
61 | if (typeof value === "string") {
62 |
63 | value = value.trim().toLowerCase();
64 | if (value === "") {
65 | return false;
66 | }
67 | if (value === "true") {
68 | return true;
69 | }
70 | if (value === "false") {
71 | return false;
72 | }
73 | if (value !== "") {
74 | return true;
75 | }
76 | }
77 | if (typeof value === "boolean") {
78 | return value;
79 | }
80 | return true;
81 | }
82 |
83 | // This function represents the extractor function we pass to extractValue
84 | export function createValueExtractor(
85 | state: ContainerState,
86 | appContext: AppContextObject | undefined,
87 | referenceTrackedApi: Record<string, ComponentApi>,
88 | memoedVarsRef: MutableRefObject<MemoedVars>
89 | ): ValueExtractor {
90 | // --- Extract the parameter and retrieve as is is
91 | const extractor = (expression?: any, strict?: boolean): any => {
92 | if (!expression) {
93 | return expression;
94 | }
95 | if (isPrimitive(expression) && !isString(expression)) {
96 | return expression;
97 | }
98 |
99 | let expressionString = expression;
100 | if (typeof expression !== "string") {
101 | if (strict) {
102 | return expression;
103 | }
104 | expressionString = JSON.stringify(expression);
105 | }
106 |
107 | if (!memoedVarsRef.current.has(expressionString)) {
108 | const params = collectParams(expression);
109 | memoedVarsRef.current.set(expressionString, {
110 | getDependencies: memoizeOne((_expressionString, referenceTrackedApi) => {
111 | let ret = new Set<string>();
112 | params.forEach((param) => {
113 | if (param.type === "expression") {
114 | ret = new Set([...ret, ...collectVariableDependencies(param.value, referenceTrackedApi)]);
115 | }
116 | });
117 | return Array.from(ret);
118 | }),
119 | obtainValue: memoizeOne(
120 | (expression, state, appContext, strict, deps, appContextDeps) => {
121 | // console.log("COMP, BUST, obtain value called with", expression, state, appContext, deps, appContextDeps);
122 | return extractParam(state, expression, appContext, strict);
123 | },
124 | (
125 | [_newExpression, _newState, _newAppContext, _newStrict, newDeps, newAppContextDeps],
126 | [_lastExpression, _lastState, _lastAppContext, _lastStrict, lastDeps, lastAppContextDeps]
127 | ) => {
128 | return shallowCompare(newDeps, lastDeps) && shallowCompare(newAppContextDeps, lastAppContextDeps);
129 | }
130 | ),
131 | });
132 | }
133 | const expressionDependencies = memoedVarsRef.current
134 | .get(expressionString)!
135 | .getDependencies(expressionString, referenceTrackedApi);
136 | const depValues = pickFromObject(state, expressionDependencies);
137 | const appContextDepValues = pickFromObject(appContext, expressionDependencies);
138 | // console.log("COMP, obtain value called with", depValues, appContextDepValues, expressionDependencies);
139 | return memoedVarsRef.current.get(expressionString)!.obtainValue!(
140 | expression,
141 | state,
142 | appContext,
143 | strict,
144 | depValues,
145 | appContextDepValues
146 | );
147 | };
148 |
149 | // --- Extract a string value
150 | extractor.asString = (expression?: any) => {
151 | return extractor(expression)?.toString() ?? "";
152 | };
153 |
154 | // --- Extract an optional string value
155 | extractor.asOptionalString = <T extends string>(expression?: any, defValue?: string) => {
156 | const value = extractor(expression)?.toString();
157 | if (value === undefined || value === null) return defValue;
158 | return value as T;
159 | };
160 |
161 | extractor.asDisplayText = (expression?: any) => {
162 | let text = extractor(expression)?.toString();
163 | if (text) {
164 | let replaced = "";
165 | let spaceFound = false;
166 | for (const char of text) {
167 | if (char === " " || char === "\t") {
168 | replaced += spaceFound ? "\xa0" : " ";
169 | spaceFound = true;
170 | } else {
171 | replaced += char;
172 | spaceFound = char === "\xa0";
173 | }
174 | }
175 | text = replaced;
176 | }
177 | return text;
178 | };
179 |
180 | // ---Extract an array of strings
181 | extractor.asOptionalStringArray = (expression?: any) => {
182 | const value = extractor(expression);
183 | if (value === undefined || value === null) return [];
184 | if (typeof value === "string" && value.trim() !== "") {
185 | //console.log(parseStringArray(value));
186 | return parseStringArray(value);
187 | }
188 | if (Array.isArray(value)) {
189 | return value.map((item) => item.toString());
190 | }
191 | throw new Error(`An array of strings expected but ${typeof value} received.`);
192 | };
193 |
194 | // --- Extract a numeric value
195 | extractor.asNumber = (expression?: any) => {
196 | const value = extractor(expression);
197 | if (typeof value === "number") return value;
198 | throw new Error(`A numeric value expected but ${typeof value} received.`);
199 | };
200 |
201 | // --- Extract an optional numeric value
202 | extractor.asOptionalNumber = (expression?: any, defValue?: number) => {
203 | const value = extractor(expression);
204 | if (value === undefined || value === null) return defValue;
205 | if (typeof value === "string" && !isNaN(parseFloat(value))) {
206 | return Number(value);
207 | }
208 | if (typeof value === "number") return value;
209 | throw new Error(`A numeric value expected but ${typeof value} received.`);
210 | };
211 |
212 | // --- Extract a Boolean value
213 | extractor.asBoolean = (expression?: any) => {
214 | return !!extractor(expression);
215 | };
216 |
217 | // --- Extract an optional Boolean value
218 | extractor.asOptionalBoolean = (expression?: any, defValue?: boolean) => {
219 | return asOptionalBoolean(extractor(expression), defValue);
220 | };
221 |
222 | // --- Extract an optional size value
223 | extractor.asSize = (expression?: any) => {
224 | const value = extractor(expression);
225 | if (value === undefined || value === null) return undefined;
226 |
227 | try {
228 | const parser = new StyleParser(value);
229 | const size = parser.parseSize();
230 | if (size?.themeId) {
231 | return toCssVar(size.themeId);
232 | }
233 | return size ? `${size.value}${size.unit ?? "px"}` : undefined;
234 | } catch {
235 | return undefined;
236 | }
237 | };
238 |
239 | // --- Done.
240 | return extractor as ValueExtractor;
241 | }
242 |
```
--------------------------------------------------------------------------------
/xmlui/src/components/Carousel/Carousel.md:
--------------------------------------------------------------------------------
```markdown
1 | %-DESC-START
2 |
3 | `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.
4 |
5 | **Key features:**
6 | - **Multiple orientations**: Supports both horizontal and vertical scrolling
7 | - **Navigation controls**: Built-in previous/next buttons with customizable icons
8 | - **Indicators**: Visual dots showing current position and allowing direct navigation
9 | - **Autoplay functionality**: Automatic slide progression with configurable intervals
10 | - **Loop support**: Continuous cycling through slides
11 | - **Keyboard navigation**: Arrow key support for accessibility
12 | - **Exposed methods**: Programmatic control via `scrollTo()`, `scrollNext()`, `scrollPrev()`, `canScrollNext()`, `canScrollPrev()`
13 |
14 | %-DESC-END
15 |
16 | %-PROP-START autoplay
17 |
18 | This property indicates whether the carousel automatically scrolls through slides.
19 |
20 | ```xmlui-pg copy display name="Example: autoplay"
21 | <App>
22 | <Carousel autoplay autoplayInterval="2000" height="120px" loop>
23 | <CarouselItem>
24 | <Card title="Slide 1" />
25 | </CarouselItem>
26 | <CarouselItem>
27 | <Card title="Slide 2" />
28 | </CarouselItem>
29 | <CarouselItem>
30 | <Card title="Slide 3" />
31 | </CarouselItem>
32 | </Carousel>
33 | </App>
34 | ```
35 |
36 | %-PROP-END
37 |
38 | %-PROP-START controls
39 |
40 | This property indicates whether the carousel displays navigation controls (previous/next buttons).
41 |
42 | ```xmlui-pg copy display name="Example: controls"
43 | <App>
44 | <Carousel controls="true" height="120px">
45 | <CarouselItem>Slide 1 with controls</CarouselItem>
46 | <CarouselItem>Slide 2 with controls</CarouselItem>
47 | </Carousel>
48 | </App>
49 | ```
50 |
51 | %-PROP-END
52 |
53 | %-PROP-START indicators
54 |
55 | This property indicates whether the carousel displays position indicators.
56 |
57 | ```xmlui-pg copy display name="Example: indicators"
58 | <App>
59 | <Carousel indicators="true" height="120px">
60 | <CarouselItem>Slide 1 with indicators</CarouselItem>
61 | <CarouselItem>Slide 2 with indicators</CarouselItem>
62 | <CarouselItem>Slide 3 with indicators</CarouselItem>
63 | </Carousel>
64 | </App>
65 | ```
66 |
67 | %-PROP-END
68 |
69 | %-PROP-START loop
70 |
71 | This property indicates whether the carousel loops continuously from the last slide back to the first.
72 |
73 | ```xmlui-pg copy display name="Example: loop"
74 | <App>
75 | <Carousel loop="true" height="120px">
76 | <CarouselItem>
77 | <Card title="First Slide" />
78 | </CarouselItem>
79 | <CarouselItem>
80 | <Card title="Second Slide" />
81 | </CarouselItem>
82 | <CarouselItem>
83 | <Card title="Third Slide" />
84 | </CarouselItem>
85 | </Carousel>
86 | </App>
87 | ```
88 |
89 | %-PROP-END
90 |
91 | %-PROP-START orientation
92 |
93 | 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.
94 |
95 | Available values:
96 |
97 | | Value | Description |
98 | | --- | --- |
99 | | `horizontal` | The carousel moves horizontally **(default)** |
100 | | `vertical` | The carousel moves vertically |
101 |
102 | ```xmlui-pg copy display name="Example: orientation"
103 | <App>
104 | <Carousel orientation="horizontal" height="120px">
105 | <CarouselItem>Horizontal Slide 1</CarouselItem>
106 | <CarouselItem>Horizontal Slide 2</CarouselItem>
107 | </Carousel>
108 | </App>
109 | ```
110 |
111 | %-PROP-END
112 |
113 | %-PROP-START startIndex
114 |
115 | This property indicates the index of the first slide to display when the carousel initializes.
116 |
117 | ```xmlui-pg copy display name="Example: startIndex"
118 | <App>
119 | <Carousel startIndex="2" height="120px">
120 | <CarouselItem>Slide 1</CarouselItem>
121 | <CarouselItem>Slide 2</CarouselItem>
122 | <CarouselItem>Slide 3 (starts here)</CarouselItem>
123 | <CarouselItem>Slide 4</CarouselItem>
124 | </Carousel>
125 | </App>
126 | ```
127 |
128 | %-PROP-END
129 |
130 | %-PROP-START nextIcon
131 |
132 | This property specifies the icon to display for the next control button.
133 |
134 | ```xmlui-pg copy display name="Example: custom icons"
135 | <App>
136 | <Carousel nextIcon="chevronright" prevIcon="chevronleft" height="120px">
137 | <CarouselItem>Slide 1</CarouselItem>
138 | <CarouselItem>Slide 2</CarouselItem>
139 | <CarouselItem>Slide 3</CarouselItem>
140 | </Carousel>
141 | </App>
142 | ```
143 |
144 | %-PROP-END
145 |
146 | %-PROP-START prevIcon
147 |
148 | This property specifies the icon to display for the previous control button.
149 |
150 | %-PROP-END
151 |
152 | %-PROP-START autoplayInterval
153 |
154 | This property specifies the interval between autoplay transitions in milliseconds.
155 |
156 | %-PROP-END
157 |
158 | %-PROP-START stopAutoplayOnInteraction
159 |
160 | This property indicates whether autoplay stops when the user interacts with the carousel (clicking controls, indicators, or using keyboard navigation).
161 |
162 | %-PROP-END
163 |
164 | %-PROP-START transitionDuration
165 |
166 | This property indicates the duration of the transition between slides in milliseconds.
167 |
168 | %-PROP-END
169 |
170 | %-EVENT-START displayDidChange
171 |
172 | This event is triggered when the active slide changes.
173 |
174 | The event handler receives the active slide index as a parameter.
175 |
176 | ```xmlui-pg copy display name="Example: displayDidChange"
177 | <App var.currentSlide="0">
178 | <Carousel onDisplayDidChange="(index) => currentSlide = index" height="120px">
179 | <CarouselItem>Slide 1</CarouselItem>
180 | <CarouselItem>Slide 2</CarouselItem>
181 | <CarouselItem>Slide 3</CarouselItem>
182 | </Carousel>
183 | <Text>Current slide: {currentSlide + 1}</Text>
184 | </App>
185 | ```
186 |
187 | %-EVENT-END
188 |
189 | %-STYLE-START
190 |
191 | ### Theme Variables
192 |
193 | | Variable | Default Value (Light) | Default Value (Dark) |
194 | | --- | --- | --- |
195 | | [backgroundColor](../styles-and-themes/common-units/#color)-control-Carousel | $color-primary | $color-primary |
196 | | [backgroundColor](../styles-and-themes/common-units/#color)-control-active-Carousel | $color-primary | $color-primary |
197 | | [backgroundColor](../styles-and-themes/common-units/#color)-control-disabled-Carousel | $color-surface-200 | $color-surface-200 |
198 | | [backgroundColor](../styles-and-themes/common-units/#color)-control-hover-Carousel | $color-primary | $color-primary |
199 | | [backgroundColor](../styles-and-themes/common-units/#color)-indicator-Carousel | $color-surface-200 | $color-surface-200 |
200 | | [backgroundColor](../styles-and-themes/common-units/#color)-indicator-active-Carousel | $color-primary | $color-primary |
201 | | [backgroundColor](../styles-and-themes/common-units/#color)-indicator-hover-Carousel | $color-surface-200 | $color-surface-200 |
202 | | [borderRadius](../styles-and-themes/common-units/#border-rounding)-control-Carousel | 50% | 50% |
203 | | [height](../styles-and-themes/common-units/#size)-Carousel | 100% | 100% |
204 | | [height](../styles-and-themes/common-units/#size)-control-Carousel | 36px | 36px |
205 | | [height](../styles-and-themes/common-units/#size)-indicator-Carousel | 6px | 6px |
206 | | [textColor](../styles-and-themes/common-units/#color)-control-Carousel | $textColor | $textColor |
207 | | [textColor](../styles-and-themes/common-units/#color)-control-active-Carousel | $color-primary | $color-primary |
208 | | [textColor](../styles-and-themes/common-units/#color)-control-disabled-Carousel | $textColor-disabled | $textColor-disabled |
209 | | [textColor](../styles-and-themes/common-units/#color)-control-hover-Carousel | $textColor | $textColor |
210 | | [textColor](../styles-and-themes/common-units/#color)-indicator-Carousel | $color-primary | $color-primary |
211 | | [textColor](../styles-and-themes/common-units/#color)-indicator-active-Carousel | $color-primary | $color-primary |
212 | | [textColor](../styles-and-themes/common-units/#color)-indicator-hover-Carousel | $color-primary | $color-primary |
213 | | [width](../styles-and-themes/common-units/#size)-Carousel | 100% | 100% |
214 | | [width](../styles-and-themes/common-units/#size)-control-Carousel | 36px | 36px |
215 | | [width](../styles-and-themes/common-units/#size)-indicator-Carousel | 25px | 25px |
216 |
217 | ### Navigation Controls
218 |
219 | The carousel provides built-in navigation controls that can be customized through theme variables:
220 |
221 | ```xmlui-pg copy name="Example: Custom control styling"
222 | <App>
223 | <Theme
224 | backgroundColor-control-Carousel="red"
225 | textColor-control-Carousel="white"
226 | borderRadius-control-Carousel="4px">
227 | <Carousel height="120px">
228 | <CarouselItem>Slide 1</CarouselItem>
229 | <CarouselItem>Slide 2</CarouselItem>
230 | <CarouselItem>Slide 3</CarouselItem>
231 | </Carousel>
232 | </Theme>
233 | </App>
234 | ```
235 |
236 | %-STYLE-END
237 |
```
--------------------------------------------------------------------------------
/xmlui/src/components/_conventions.md:
--------------------------------------------------------------------------------
```markdown
1 | # XMLUI Component Conventions
2 |
3 | This document outlines the conventions and patterns used in the XMLUI component system, based on analysis of components like Avatar, Button, and Card.
4 |
5 | ## Component Structure
6 |
7 | ### Dual-File Pattern
8 |
9 | Components are structured using a dual-file pattern:
10 |
11 | - **Native Component** (`*Native.tsx`)
12 | - Pure React implementation
13 | - Uses `forwardRef` pattern
14 | - Contains the actual rendering logic
15 | - Defines prop types and default props
16 |
17 | - **Renderer Component** (`.tsx`)
18 | - Provides metadata using `createMetadata`
19 | - Registers component with `createComponentRenderer`
20 | - Defines theme variables
21 | - Maps XMLUI props to native component props
22 |
23 | Example relationship:
24 | ```typescript
25 | // Avatar.tsx (Renderer)
26 | export const avatarComponentRenderer = createComponentRenderer(
27 | "Avatar",
28 | AvatarMd,
29 | ({ node, extractValue, lookupEventHandler, layoutCss, extractResourceUrl }) => {
30 | return (
31 | <Avatar
32 | size={node.props?.size}
33 | url={extractResourceUrl(node.props.url)}
34 | name={extractValue(node.props.name)}
35 | style={layoutCss}
36 | onClick={lookupEventHandler("click")}
37 | />
38 | );
39 | },
40 | );
41 |
42 | // AvatarNative.tsx (Implementation)
43 | export const Avatar = forwardRef(function Avatar(
44 | { size = defaultProps.size, url, name, style, onClick, ...rest }: Props,
45 | ref: Ref<any>,
46 | ) {
47 | // Implementation details...
48 | });
49 | ```
50 |
51 | ### Single-File Pattern Variation
52 |
53 | Some XMLUI components are implemented using a single-file pattern:
54 |
55 | - **Combined Component** (`.tsx`)
56 | - Contains both the React implementation and XMLUI renderer in a single file
57 | - Defines metadata using `createMetadata`
58 | - Registers component with `createComponentRenderer`
59 | - Does not separate the implementation into a `*Native.tsx` file
60 | - Often used for simpler components or those that primarily compose other native components
61 |
62 | Example: `ToneChangerButton.tsx` combines the implementation and renderer in one file:
63 |
64 | ```typescript
65 | export function ToneChangerButton({
66 | lightToDarkIcon = defaultProps.lightToDarkIcon,
67 | darkToLightIcon = defaultProps.darkToLightIcon,
68 | }) {
69 | // Implementation...
70 | }
71 |
72 | export const toneChangerButtonComponentRenderer = createComponentRenderer(
73 | COMP,
74 | ToneChangerButtonMd,
75 | ({ node, extractValue }) => {
76 | return (
77 | <ToneChangerButton
78 | lightToDarkIcon={extractValue.asOptionalString(node.props.lightToDarkIcon)}
79 | darkToLightIcon={extractValue.asOptionalString(node.props.darkToLightIcon)}
80 | />
81 | );
82 | },
83 | );
84 | ```
85 |
86 | This variation is still a fully functional XMLUI component and is registered in the ComponentProvider, making it available in XMLUI markup.
87 |
88 | ## Component API and State
89 |
90 | ### API Registration
91 |
92 | Components expose methods through a standardized API registration pattern:
93 |
94 | ```typescript
95 | useEffect(() => {
96 | registerComponentApi?.({
97 | setValue,
98 | focus,
99 | });
100 | }, [registerComponentApi, setValue, focus]);
101 | ```
102 |
103 | ### State Synchronization
104 |
105 | Components keep their internal state synchronized with XMLUI:
106 |
107 | ```typescript
108 | // Update component state and notify XMLUI
109 | const setValue = useEvent((newValue) => {
110 | setInternalState(newValue);
111 | updateState?.({ value: newValue });
112 | });
113 |
114 | // Sync state on initial render or changes
115 | useEffect(() => {
116 | updateState?.({ value: currentState });
117 | }, [updateState, currentState]);
118 | ```
119 |
120 | ## Event Handling
121 |
122 | Events are:
123 | 1. Declared in component metadata
124 | 2. Implemented in the native component
125 | 3. Connected via `lookupEventHandler` in the renderer
126 |
127 | Common events:
128 | - `click` / `onClick`
129 | - `didChange` (value changes)
130 | - `gotFocus` / `lostFocus`
131 | - Component-specific events (e.g., `onReset`)
132 |
133 | ## Styling and Theming
134 |
135 | ### SCSS Module Pattern
136 |
137 | - Each component has its own SCSS module (e.g., `Avatar.module.scss`)
138 | - Theme variables follow naming convention: `propertyName-ComponentName`
139 | - Variables are exported and parsed with `parseScssVar`
140 | - CSS classes are composed with the `classnames` library
141 |
142 | ### Theme Variables
143 |
144 | Theme variables are defined in the component metadata:
145 |
146 | ```typescript
147 | defaultThemeVars: {
148 | [`borderRadius-${COMP}`]: "4px",
149 | [`boxShadow-${COMP}`]: "inset 0 0 0 1px rgba(4,32,69,0.1)",
150 | [`textColor-${COMP}`]: "$textColor-secondary",
151 | // ...other variables
152 | }
153 | ```
154 |
155 | Components can be styled using the theme system or through direct props.
156 |
157 | ## Component Registration
158 |
159 | Components are registered with the `ComponentRegistry` during framework initialization:
160 |
161 | ```typescript
162 | if (process.env.VITE_USED_COMPONENTS_Avatar !== "false") {
163 | this.registerCoreComponent(avatarComponentRenderer);
164 | }
165 | ```
166 |
167 | This allows conditional inclusion of components and provides centralized component management.
168 |
169 | ## Testing
170 |
171 | Components use a driver pattern for testing:
172 |
173 | ```typescript
174 | // Each component has a dedicated driver class
175 | export class AvatarDriver extends ComponentDriver {
176 | // Driver-specific methods
177 | }
178 |
179 | // Used in tests
180 | test("can render 2 initials", async ({ initTestBed, createAvatarDriver }) => {
181 | await initTestBed(`<Avatar name="Tim Smith"/>`);
182 | await expect((await createAvatarDriver()).component).toContainText("TS");
183 | });
184 | ```
185 |
186 | ## Reusable Component System
187 |
188 | XMLUI supports user-defined components using the `<Component>` tag:
189 |
190 | ```xml
191 | <Component name="InfoCard">
192 | <Card width="{$props.width}" borderRadius="8px" boxShadow="$boxShadow-spread">
193 | <Text>{$props.title}</Text>
194 | <Text fontWeight="$fontWeight-extra-bold" fontSize="larger">
195 | { $props.currency === 'true' ? '$' + $props.value : $props.value }
196 | </Text>
197 | </Card>
198 | </Component>
199 | ```
200 |
201 | Components can access properties via `$props` context variable, and expose methods through the API registration mechanism.
202 |
203 | ## Layout Integration
204 |
205 | Components behave differently depending on their parent layout containers:
206 |
207 | - In `Stack` layouts, component layout properties might be ignored
208 | - In `FlowLayout`, components get wrapped in container elements
209 | - Explicit layout containers within components help control arrangement
210 |
211 | ## Native-Only React Components
212 |
213 | 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.
214 |
215 | - `InspectButton`: A button component used for turning inspection mode on/off in XMLUI development environment
216 | - `ProfileMenu`: A profile menu component used within other components but not exposed directly to XMLUI
217 | - `Toggle`: A toggle component that provides base functionality that may be used by other components
218 | - `VisuallyHidden`: A utility component that wraps Radix UI's VisuallyHidden for accessibility
219 | - `SlotItem`: An internal component for implementing the slot mechanism
220 | - `IconProvider`: Provides icon context for the icon system
221 | - `IconRegistryContext`: Manages icon registry context
222 |
223 | ### Component Structure Observations
224 |
225 | 1. **Pure Native Components** tend to:
226 | - Be located in a single file (no paired renderer)
227 | - Have a `.tsx` extension (sometimes with a `.module.scss` file)
228 | - Not include `createComponentRenderer` or `createMetadata` calls
229 | - Often be used internally by other XMLUI components
230 |
231 | 2. **Helper Components**:
232 | - Typically simple with a focused utility purpose
233 | - May be wrappers around third-party components
234 | - Often have minimal props and internal state
235 |
236 | 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.
237 |
238 | ## Best Practices
239 |
240 | 1. Keep native component implementations pure and focused
241 | 2. Define clear metadata and documentation for components
242 | 3. Support proper theming through theme variables
243 | 4. Implement consistent API and event patterns
244 | 5. Ensure proper testing with component drivers
245 | 6. Support accessibility through ARIA attributes and keyboard navigation
246 | 7. Ensure proper reference forwarding through `forwardRef`
247 | 8. Provide sensible defaults for all props
248 |
```