This is page 27 of 141. Use http://codebase.md/xmlui-org/xmlui/xmlui-latest.js?lines=false&page={x} to view the full context.
# Directory Structure
```
├── .changeset
│ └── config.json
├── .eslintrc.cjs
├── .github
│ ├── build-checklist.png
│ ├── ISSUE_TEMPLATE
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ └── workflows
│ ├── deploy-blog-optimized.yml
│ ├── deploy-blog.yml
│ ├── deploy-docs-optimized.yml
│ ├── deploy-docs.yml
│ ├── prepare-versions.yml
│ ├── release-packages.yml
│ ├── run-all-tests.yml
│ └── run-smoke-tests.yml
├── .gitignore
├── .prettierrc.js
├── .vscode
│ ├── launch.json
│ └── settings.json
├── blog
│ ├── .gitignore
│ ├── .gitkeep
│ ├── CHANGELOG.md
│ ├── extensions.ts
│ ├── index.html
│ ├── index.ts
│ ├── package.json
│ ├── public
│ │ ├── blog
│ │ │ ├── images
│ │ │ │ ├── an-advanced-codefence.gif
│ │ │ │ ├── an-advanced-codefence.mp4
│ │ │ │ ├── blog-page-component.png
│ │ │ │ ├── blog-scrabble.png
│ │ │ │ ├── codefence-runner.png
│ │ │ │ ├── integrated-blog-search.png
│ │ │ │ ├── lorem-ipsum.png
│ │ │ │ ├── playground-checkbox-source.png
│ │ │ │ ├── playground.png
│ │ │ │ ├── use-xmlui-mcp-to-find-a-howto.png
│ │ │ │ └── xmlui-demo-gallery.png
│ │ │ ├── introducing-xmlui.md
│ │ │ ├── lorem-ipsum.md
│ │ │ ├── newest-post.md
│ │ │ ├── older-post.md
│ │ │ ├── xmlui-playground.md
│ │ │ └── xmlui-powered-blog.md
│ │ ├── mockServiceWorker.js
│ │ ├── resources
│ │ │ ├── favicon.ico
│ │ │ ├── files
│ │ │ │ └── for-download
│ │ │ │ └── xmlui
│ │ │ │ └── xmlui-standalone.umd.js
│ │ │ ├── github.svg
│ │ │ ├── llms.txt
│ │ │ ├── logo-dark.svg
│ │ │ ├── logo.svg
│ │ │ ├── pg-popout.svg
│ │ │ ├── rss.svg
│ │ │ └── xmlui-logo.svg
│ │ ├── serve.json
│ │ └── web.config
│ ├── scripts
│ │ ├── download-latest-xmlui.js
│ │ ├── generate-rss.js
│ │ ├── get-releases.js
│ │ └── utils.js
│ ├── src
│ │ ├── components
│ │ │ ├── BlogOverview.xmlui
│ │ │ ├── BlogPage.xmlui
│ │ │ └── PageNotFound.xmlui
│ │ ├── config.ts
│ │ ├── Main.xmlui
│ │ └── themes
│ │ └── blog-theme.ts
│ └── tsconfig.json
├── CONTRIBUTING.md
├── docs
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── ComponentRefLinks.txt
│ ├── content
│ │ ├── _meta.json
│ │ ├── components
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── APICall.md
│ │ │ ├── App.md
│ │ │ ├── AppHeader.md
│ │ │ ├── AppState.md
│ │ │ ├── AutoComplete.md
│ │ │ ├── Avatar.md
│ │ │ ├── Backdrop.md
│ │ │ ├── Badge.md
│ │ │ ├── BarChart.md
│ │ │ ├── Bookmark.md
│ │ │ ├── Breakout.md
│ │ │ ├── Button.md
│ │ │ ├── Card.md
│ │ │ ├── Carousel.md
│ │ │ ├── ChangeListener.md
│ │ │ ├── Checkbox.md
│ │ │ ├── CHStack.md
│ │ │ ├── ColorPicker.md
│ │ │ ├── Column.md
│ │ │ ├── ContentSeparator.md
│ │ │ ├── CVStack.md
│ │ │ ├── DataSource.md
│ │ │ ├── DateInput.md
│ │ │ ├── DatePicker.md
│ │ │ ├── DonutChart.md
│ │ │ ├── DropdownMenu.md
│ │ │ ├── EmojiSelector.md
│ │ │ ├── ExpandableItem.md
│ │ │ ├── FileInput.md
│ │ │ ├── FileUploadDropZone.md
│ │ │ ├── FlowLayout.md
│ │ │ ├── Footer.md
│ │ │ ├── Form.md
│ │ │ ├── FormItem.md
│ │ │ ├── FormSection.md
│ │ │ ├── Fragment.md
│ │ │ ├── H1.md
│ │ │ ├── H2.md
│ │ │ ├── H3.md
│ │ │ ├── H4.md
│ │ │ ├── H5.md
│ │ │ ├── H6.md
│ │ │ ├── Heading.md
│ │ │ ├── HSplitter.md
│ │ │ ├── HStack.md
│ │ │ ├── Icon.md
│ │ │ ├── IFrame.md
│ │ │ ├── Image.md
│ │ │ ├── Items.md
│ │ │ ├── LabelList.md
│ │ │ ├── Legend.md
│ │ │ ├── LineChart.md
│ │ │ ├── Link.md
│ │ │ ├── List.md
│ │ │ ├── Logo.md
│ │ │ ├── Markdown.md
│ │ │ ├── MenuItem.md
│ │ │ ├── MenuSeparator.md
│ │ │ ├── ModalDialog.md
│ │ │ ├── NavGroup.md
│ │ │ ├── NavLink.md
│ │ │ ├── NavPanel.md
│ │ │ ├── NoResult.md
│ │ │ ├── NumberBox.md
│ │ │ ├── Option.md
│ │ │ ├── Page.md
│ │ │ ├── PageMetaTitle.md
│ │ │ ├── Pages.md
│ │ │ ├── Pagination.md
│ │ │ ├── PasswordInput.md
│ │ │ ├── PieChart.md
│ │ │ ├── ProgressBar.md
│ │ │ ├── Queue.md
│ │ │ ├── RadioGroup.md
│ │ │ ├── RealTimeAdapter.md
│ │ │ ├── Redirect.md
│ │ │ ├── Select.md
│ │ │ ├── Slider.md
│ │ │ ├── Slot.md
│ │ │ ├── SpaceFiller.md
│ │ │ ├── Spinner.md
│ │ │ ├── Splitter.md
│ │ │ ├── Stack.md
│ │ │ ├── StickyBox.md
│ │ │ ├── SubMenuItem.md
│ │ │ ├── Switch.md
│ │ │ ├── TabItem.md
│ │ │ ├── Table.md
│ │ │ ├── TableOfContents.md
│ │ │ ├── Tabs.md
│ │ │ ├── Text.md
│ │ │ ├── TextArea.md
│ │ │ ├── TextBox.md
│ │ │ ├── Theme.md
│ │ │ ├── TimeInput.md
│ │ │ ├── Timer.md
│ │ │ ├── ToneChangerButton.md
│ │ │ ├── ToneSwitch.md
│ │ │ ├── Tooltip.md
│ │ │ ├── Tree.md
│ │ │ ├── VSplitter.md
│ │ │ ├── VStack.md
│ │ │ ├── xmlui-animations
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── Animation.md
│ │ │ │ ├── FadeAnimation.md
│ │ │ │ ├── FadeInAnimation.md
│ │ │ │ ├── FadeOutAnimation.md
│ │ │ │ ├── ScaleAnimation.md
│ │ │ │ └── SlideInAnimation.md
│ │ │ ├── xmlui-pdf
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Pdf.md
│ │ │ ├── xmlui-spreadsheet
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Spreadsheet.md
│ │ │ └── xmlui-website-blocks
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── Carousel.md
│ │ │ ├── HelloMd.md
│ │ │ ├── HeroSection.md
│ │ │ └── ScrollToTop.md
│ │ └── extensions
│ │ ├── _meta.json
│ │ ├── xmlui-animations
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── Animation.md
│ │ │ ├── FadeAnimation.md
│ │ │ ├── FadeInAnimation.md
│ │ │ ├── FadeOutAnimation.md
│ │ │ ├── ScaleAnimation.md
│ │ │ └── SlideInAnimation.md
│ │ └── xmlui-website-blocks
│ │ ├── _meta.json
│ │ ├── _overview.md
│ │ ├── Carousel.md
│ │ ├── FancyButton.md
│ │ ├── HeroSection.md
│ │ └── ScrollToTop.md
│ ├── extensions.ts
│ ├── index.html
│ ├── index.ts
│ ├── package.json
│ ├── public
│ │ ├── feed.rss
│ │ ├── mockServiceWorker.js
│ │ ├── pages
│ │ │ ├── _meta.json
│ │ │ ├── app-structure.md
│ │ │ ├── build-editor-component.md
│ │ │ ├── build-hello-world-component.md
│ │ │ ├── components-intro.md
│ │ │ ├── context-variables.md
│ │ │ ├── forms.md
│ │ │ ├── globals.md
│ │ │ ├── glossary.md
│ │ │ ├── helper-tags.md
│ │ │ ├── hosted-deployment.md
│ │ │ ├── howto
│ │ │ │ ├── assign-a-complex-json-literal-to-a-component-variable.md
│ │ │ │ ├── chain-a-refetch.md
│ │ │ │ ├── control-cache-invalidation.md
│ │ │ │ ├── debounce-user-input-for-api-calls.md
│ │ │ │ ├── debounce-with-changelistener.md
│ │ │ │ ├── debug-a-component.md
│ │ │ │ ├── delay-a-datasource-until-another-datasource-is-ready.md
│ │ │ │ ├── delegate-a-method.md
│ │ │ │ ├── do-custom-form-validation.md
│ │ │ │ ├── expose-a-method-from-a-component.md
│ │ │ │ ├── filter-and-transform-data-from-an-api.md
│ │ │ │ ├── group-items-in-list-by-a-property.md
│ │ │ │ ├── handle-background-operations.md
│ │ │ │ ├── hide-an-element-until-its-datasource-is-ready.md
│ │ │ │ ├── make-a-set-of-equal-width-cards.md
│ │ │ │ ├── make-a-table-responsive.md
│ │ │ │ ├── make-navpanel-width-responsive.md
│ │ │ │ ├── modify-a-value-reported-in-a-column.md
│ │ │ │ ├── paginate-a-list.md
│ │ │ │ ├── pass-data-to-a-modal-dialog.md
│ │ │ │ ├── react-to-button-click-not-keystrokes.md
│ │ │ │ ├── set-the-initial-value-of-a-select-from-fetched-data.md
│ │ │ │ ├── share-a-modaldialog-across-components.md
│ │ │ │ ├── sync-selections-between-table-and-list-views.md
│ │ │ │ ├── update-ui-optimistically.md
│ │ │ │ ├── use-built-in-form-validation.md
│ │ │ │ └── use-the-same-modaldialog-to-add-or-edit.md
│ │ │ ├── howto.md
│ │ │ ├── intro.md
│ │ │ ├── layout.md
│ │ │ ├── markup.md
│ │ │ ├── mcp.md
│ │ │ ├── modal-dialogs.md
│ │ │ ├── news-and-reviews.md
│ │ │ ├── reactive-intro.md
│ │ │ ├── refactoring.md
│ │ │ ├── routing-and-links.md
│ │ │ ├── samples
│ │ │ │ ├── color-palette.xmlui
│ │ │ │ ├── color-values.xmlui
│ │ │ │ ├── shadow-sizes.xmlui
│ │ │ │ ├── spacing-sizes.xmlui
│ │ │ │ ├── swatch.xmlui
│ │ │ │ ├── theme-gallery-brief.xmlui
│ │ │ │ └── theme-gallery.xmlui
│ │ │ ├── scoping.md
│ │ │ ├── scripting.md
│ │ │ ├── styles-and-themes
│ │ │ │ ├── common-units.md
│ │ │ │ ├── layout-props.md
│ │ │ │ ├── theme-variable-defaults.md
│ │ │ │ ├── theme-variables.md
│ │ │ │ └── themes.md
│ │ │ ├── template-properties.md
│ │ │ ├── test.md
│ │ │ ├── tutorial-01.md
│ │ │ ├── tutorial-02.md
│ │ │ ├── tutorial-03.md
│ │ │ ├── tutorial-04.md
│ │ │ ├── tutorial-05.md
│ │ │ ├── tutorial-06.md
│ │ │ ├── tutorial-07.md
│ │ │ ├── tutorial-08.md
│ │ │ ├── tutorial-09.md
│ │ │ ├── tutorial-10.md
│ │ │ ├── tutorial-11.md
│ │ │ ├── tutorial-12.md
│ │ │ ├── universal-properties.md
│ │ │ ├── user-defined-components.md
│ │ │ ├── vscode.md
│ │ │ ├── working-with-markdown.md
│ │ │ ├── working-with-text.md
│ │ │ ├── xmlui-animations
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── Animation.md
│ │ │ │ ├── FadeAnimation.md
│ │ │ │ ├── FadeInAnimation.md
│ │ │ │ ├── FadeOutAnimation.md
│ │ │ │ ├── ScaleAnimation.md
│ │ │ │ └── SlideInAnimation.md
│ │ │ ├── xmlui-charts
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── BarChart.md
│ │ │ │ ├── DonutChart.md
│ │ │ │ ├── LabelList.md
│ │ │ │ ├── Legend.md
│ │ │ │ ├── LineChart.md
│ │ │ │ └── PieChart.md
│ │ │ ├── xmlui-pdf
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Pdf.md
│ │ │ └── xmlui-spreadsheet
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ └── Spreadsheet.md
│ │ ├── resources
│ │ │ ├── devdocs
│ │ │ │ ├── debug-proxy-object-2.png
│ │ │ │ ├── debug-proxy-object.png
│ │ │ │ ├── table_editor_01.png
│ │ │ │ ├── table_editor_02.png
│ │ │ │ ├── table_editor_03.png
│ │ │ │ ├── table_editor_04.png
│ │ │ │ ├── table_editor_05.png
│ │ │ │ ├── table_editor_06.png
│ │ │ │ ├── table_editor_07.png
│ │ │ │ ├── table_editor_08.png
│ │ │ │ ├── table_editor_09.png
│ │ │ │ ├── table_editor_10.png
│ │ │ │ ├── table_editor_11.png
│ │ │ │ ├── table-editor-01.png
│ │ │ │ ├── table-editor-02.png
│ │ │ │ ├── table-editor-03.png
│ │ │ │ ├── table-editor-04.png
│ │ │ │ ├── table-editor-06.png
│ │ │ │ ├── table-editor-07.png
│ │ │ │ ├── table-editor-08.png
│ │ │ │ ├── table-editor-09.png
│ │ │ │ └── xmlui-rendering-of-tiptap-markdown.png
│ │ │ ├── favicon.ico
│ │ │ ├── files
│ │ │ │ ├── clients.json
│ │ │ │ ├── daily-revenue.json
│ │ │ │ ├── dashboard-stats.json
│ │ │ │ ├── demo.xmlui
│ │ │ │ ├── demo.xmlui.xs
│ │ │ │ ├── downloads
│ │ │ │ │ └── downloads.json
│ │ │ │ ├── for-download
│ │ │ │ │ ├── index-with-api.html
│ │ │ │ │ ├── index.html
│ │ │ │ │ ├── mockApi.js
│ │ │ │ │ ├── start-darwin.sh
│ │ │ │ │ ├── start-linux.sh
│ │ │ │ │ ├── start.bat
│ │ │ │ │ └── xmlui
│ │ │ │ │ └── xmlui-standalone.umd.js
│ │ │ │ ├── getting-started
│ │ │ │ │ ├── cl-tutorial-final.zip
│ │ │ │ │ ├── cl-tutorial.zip
│ │ │ │ │ ├── cl-tutorial2.zip
│ │ │ │ │ ├── cl-tutorial3.zip
│ │ │ │ │ ├── cl-tutorial4.zip
│ │ │ │ │ ├── cl-tutorial5.zip
│ │ │ │ │ ├── cl-tutorial6.zip
│ │ │ │ │ ├── getting-started.zip
│ │ │ │ │ ├── hello-xmlui.zip
│ │ │ │ │ ├── xmlui-empty.zip
│ │ │ │ │ └── xmlui-starter.zip
│ │ │ │ ├── howto
│ │ │ │ │ └── component-icons
│ │ │ │ │ └── up-arrow.svg
│ │ │ │ ├── invoices.json
│ │ │ │ ├── monthly-status.json
│ │ │ │ ├── news-and-reviews.json
│ │ │ │ ├── products.json
│ │ │ │ ├── releases.json
│ │ │ │ ├── tutorials
│ │ │ │ │ ├── datasource
│ │ │ │ │ │ └── api.ts
│ │ │ │ │ └── p2do
│ │ │ │ │ ├── api.ts
│ │ │ │ │ └── todo-logo.svg
│ │ │ │ └── xmlui.json
│ │ │ ├── github.svg
│ │ │ ├── images
│ │ │ │ ├── apiaction-tutorial
│ │ │ │ │ ├── add-success.png
│ │ │ │ │ ├── apiaction-param.png
│ │ │ │ │ ├── change-completed.png
│ │ │ │ │ ├── change-in-progress.png
│ │ │ │ │ ├── confirm-delete.png
│ │ │ │ │ ├── data-error.png
│ │ │ │ │ ├── data-progress.png
│ │ │ │ │ ├── data-success.png
│ │ │ │ │ ├── display-1.png
│ │ │ │ │ ├── item-deleted.png
│ │ │ │ │ ├── item-updated.png
│ │ │ │ │ ├── missing-api-key.png
│ │ │ │ │ ├── new-item-added.png
│ │ │ │ │ └── test-message.png
│ │ │ │ ├── chat-api
│ │ │ │ │ └── domain-model.svg
│ │ │ │ ├── components
│ │ │ │ │ ├── image
│ │ │ │ │ │ └── breakfast.jpg
│ │ │ │ │ ├── markdown
│ │ │ │ │ │ └── colors.png
│ │ │ │ │ └── modal
│ │ │ │ │ ├── deep_link_dialog_1.jpg
│ │ │ │ │ └── deep_link_dialog_2.jpg
│ │ │ │ ├── create-apps
│ │ │ │ │ ├── collapsed-vertical.png
│ │ │ │ │ ├── using-forms-warning-dialog.png
│ │ │ │ │ └── using-forms.png
│ │ │ │ ├── datasource-tutorial
│ │ │ │ │ ├── data-with-header.png
│ │ │ │ │ ├── filtered-data.png
│ │ │ │ │ ├── filtered-items.png
│ │ │ │ │ ├── initial-page-items.png
│ │ │ │ │ ├── list-items.png
│ │ │ │ │ ├── next-page-items.png
│ │ │ │ │ ├── no-data.png
│ │ │ │ │ ├── pagination-1.jpg
│ │ │ │ │ ├── pagination-1.png
│ │ │ │ │ ├── polling-1.png
│ │ │ │ │ ├── refetch-data.png
│ │ │ │ │ ├── slow-loading.png
│ │ │ │ │ ├── test-message.png
│ │ │ │ │ ├── Thumbs.db
│ │ │ │ │ ├── unconventional-data.png
│ │ │ │ │ └── unfiltered-items.png
│ │ │ │ ├── flower.jpg
│ │ │ │ ├── get-started
│ │ │ │ │ ├── add-new-contact.png
│ │ │ │ │ ├── app-modified.png
│ │ │ │ │ ├── app-start.png
│ │ │ │ │ ├── app-with-boxes.png
│ │ │ │ │ ├── app-with-toast.png
│ │ │ │ │ ├── boilerplate-structure.png
│ │ │ │ │ ├── cl-initial.png
│ │ │ │ │ ├── cl-start.png
│ │ │ │ │ ├── contact-counts.png
│ │ │ │ │ ├── contact-dialog-title.png
│ │ │ │ │ ├── contact-dialog.png
│ │ │ │ │ ├── contact-menus.png
│ │ │ │ │ ├── contact-predicates.png
│ │ │ │ │ ├── context-menu.png
│ │ │ │ │ ├── dashboard-numbers.png
│ │ │ │ │ ├── default-contact-list.png
│ │ │ │ │ ├── delete-contact.png
│ │ │ │ │ ├── delete-task.png
│ │ │ │ │ ├── detailed-template.png
│ │ │ │ │ ├── edit-contact-details.png
│ │ │ │ │ ├── edited-contact-saved.png
│ │ │ │ │ ├── empty-sections.png
│ │ │ │ │ ├── filter-completed.png
│ │ │ │ │ ├── fullwidth-desktop.png
│ │ │ │ │ ├── fullwidth-mobile.png
│ │ │ │ │ ├── initial-table.png
│ │ │ │ │ ├── items-and-badges.png
│ │ │ │ │ ├── loading-message.png
│ │ │ │ │ ├── new-contact-button.png
│ │ │ │ │ ├── new-contact-saved.png
│ │ │ │ │ ├── no-empty-sections.png
│ │ │ │ │ ├── personal-todo-initial.png
│ │ │ │ │ ├── piechart.png
│ │ │ │ │ ├── review-today.png
│ │ │ │ │ ├── rudimentary-dashboard.png
│ │ │ │ │ ├── section-collapsed.png
│ │ │ │ │ ├── sectioned-items.png
│ │ │ │ │ ├── sections-ordered.png
│ │ │ │ │ ├── spacex-list-with-links.png
│ │ │ │ │ ├── spacex-list.png
│ │ │ │ │ ├── start-personal-todo-1.png
│ │ │ │ │ ├── submit-new-contact.png
│ │ │ │ │ ├── submit-new-task.png
│ │ │ │ │ ├── syntax-highlighting.png
│ │ │ │ │ ├── table-with-badge.png
│ │ │ │ │ ├── template-with-card.png
│ │ │ │ │ ├── test-emulated-api.png
│ │ │ │ │ ├── Thumbs.db
│ │ │ │ │ ├── todo-logo.png
│ │ │ │ │ └── xmlui-tools.png
│ │ │ │ ├── HelloApp.png
│ │ │ │ ├── HelloApp2.png
│ │ │ │ ├── logos
│ │ │ │ │ ├── xmlui1.svg
│ │ │ │ │ ├── xmlui2.svg
│ │ │ │ │ ├── xmlui3.svg
│ │ │ │ │ ├── xmlui4.svg
│ │ │ │ │ ├── xmlui5.svg
│ │ │ │ │ ├── xmlui6.svg
│ │ │ │ │ └── xmlui7.svg
│ │ │ │ ├── pdf
│ │ │ │ │ └── dummy-pdf.jpg
│ │ │ │ ├── rendering-engine
│ │ │ │ │ ├── AppEngine-flow.svg
│ │ │ │ │ ├── Component.svg
│ │ │ │ │ ├── CompoundComponent.svg
│ │ │ │ │ ├── RootComponent.svg
│ │ │ │ │ └── tree-with-containers.svg
│ │ │ │ ├── reviewers-guide
│ │ │ │ │ ├── AppEngine-flow.svg
│ │ │ │ │ └── incbutton-in-action.png
│ │ │ │ ├── tools
│ │ │ │ │ └── boilerplate-structure.png
│ │ │ │ ├── try.svg
│ │ │ │ ├── tutorial
│ │ │ │ │ ├── app-chat-history.png
│ │ │ │ │ ├── app-content-placeholder.png
│ │ │ │ │ ├── app-header-and-content.png
│ │ │ │ │ ├── app-links-channel-selected.png
│ │ │ │ │ ├── app-links-click.png
│ │ │ │ │ ├── app-navigation.png
│ │ │ │ │ ├── finished-ex01.png
│ │ │ │ │ ├── finished-ex02.png
│ │ │ │ │ ├── hello.png
│ │ │ │ │ ├── splash-screen-advanced.png
│ │ │ │ │ ├── splash-screen-after-click.png
│ │ │ │ │ ├── splash-screen-centered.png
│ │ │ │ │ ├── splash-screen-events.png
│ │ │ │ │ ├── splash-screen-expression.png
│ │ │ │ │ ├── splash-screen-reuse-after.png
│ │ │ │ │ ├── splash-screen-reuse-before.png
│ │ │ │ │ └── splash-screen.png
│ │ │ │ └── tutorial-01.png
│ │ │ ├── llms.txt
│ │ │ ├── logo-dark.svg
│ │ │ ├── logo.svg
│ │ │ ├── pg-popout.svg
│ │ │ └── xmlui-logo.svg
│ │ ├── serve.json
│ │ └── web.config
│ ├── scripts
│ │ ├── download-latest-xmlui.js
│ │ ├── generate-rss.js
│ │ ├── get-releases.js
│ │ └── utils.js
│ ├── src
│ │ ├── components
│ │ │ ├── BlogOverview.xmlui
│ │ │ ├── BlogPage.xmlui
│ │ │ ├── Boxes.xmlui
│ │ │ ├── Breadcrumb.xmlui
│ │ │ ├── ChangeLog.xmlui
│ │ │ ├── ColorPalette.xmlui
│ │ │ ├── DocumentLinks.xmlui
│ │ │ ├── DocumentPage.xmlui
│ │ │ ├── DocumentPageNoTOC.xmlui
│ │ │ ├── Icons.xmlui
│ │ │ ├── IncButton.xmlui
│ │ │ ├── IncButton2.xmlui
│ │ │ ├── NameValue.xmlui
│ │ │ ├── PageNotFound.xmlui
│ │ │ ├── PaletteItem.xmlui
│ │ │ ├── Palettes.xmlui
│ │ │ ├── SectionHeader.xmlui
│ │ │ ├── TBD.xmlui
│ │ │ ├── Test.xmlui
│ │ │ ├── ThemesIntro.xmlui
│ │ │ ├── ThousandThemes.xmlui
│ │ │ ├── TubeStops.xmlui
│ │ │ ├── TubeStops.xmlui.xs
│ │ │ └── TwoColumnCode.xmlui
│ │ ├── config.ts
│ │ ├── Main.xmlui
│ │ └── themes
│ │ ├── docs-theme.ts
│ │ ├── earthtone.ts
│ │ ├── xmlui-gray-on-default.ts
│ │ ├── xmlui-green-on-default.ts
│ │ └── xmlui-orange-on-default.ts
│ └── tsconfig.json
├── LICENSE
├── package-lock.json
├── package.json
├── packages
│ ├── tsconfig.json
│ ├── xmlui-animations
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── Animation.tsx
│ │ ├── AnimationNative.tsx
│ │ ├── FadeAnimation.tsx
│ │ ├── FadeInAnimation.tsx
│ │ ├── FadeOutAnimation.tsx
│ │ ├── index.tsx
│ │ ├── ScaleAnimation.tsx
│ │ └── SlideInAnimation.tsx
│ ├── xmlui-devtools
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ ├── src
│ │ │ ├── devtools
│ │ │ │ ├── DevTools.tsx
│ │ │ │ ├── DevToolsNative.module.scss
│ │ │ │ ├── DevToolsNative.tsx
│ │ │ │ ├── ModalDialog.module.scss
│ │ │ │ ├── ModalDialog.tsx
│ │ │ │ ├── ModalVisibilityContext.tsx
│ │ │ │ ├── Tooltip.module.scss
│ │ │ │ ├── Tooltip.tsx
│ │ │ │ └── utils.ts
│ │ │ ├── editor
│ │ │ │ └── Editor.tsx
│ │ │ └── index.tsx
│ │ └── vite.config-overrides.ts
│ ├── xmlui-hello-world
│ │ ├── .gitignore
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── HelloWorld.module.scss
│ │ ├── HelloWorld.tsx
│ │ ├── HelloWorldNative.tsx
│ │ └── index.tsx
│ ├── xmlui-os-frames
│ │ ├── .gitignore
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── index.tsx
│ │ ├── IPhoneFrame.module.scss
│ │ ├── IPhoneFrame.tsx
│ │ ├── MacOSAppFrame.module.scss
│ │ ├── MacOSAppFrame.tsx
│ │ ├── WindowsAppFrame.module.scss
│ │ └── WindowsAppFrame.tsx
│ ├── xmlui-pdf
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ ├── components
│ │ │ │ └── Pdf.xmlui
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── index.tsx
│ │ ├── LazyPdfNative.tsx
│ │ ├── Pdf.module.scss
│ │ └── Pdf.tsx
│ ├── xmlui-playground
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── hooks
│ │ │ ├── usePlayground.ts
│ │ │ └── useToast.ts
│ │ ├── index.tsx
│ │ ├── playground
│ │ │ ├── Box.module.scss
│ │ │ ├── Box.tsx
│ │ │ ├── CodeSelector.tsx
│ │ │ ├── ConfirmationDialog.module.scss
│ │ │ ├── ConfirmationDialog.tsx
│ │ │ ├── Editor.tsx
│ │ │ ├── Header.module.scss
│ │ │ ├── Header.tsx
│ │ │ ├── Playground.tsx
│ │ │ ├── PlaygroundContent.module.scss
│ │ │ ├── PlaygroundContent.tsx
│ │ │ ├── PlaygroundNative.module.scss
│ │ │ ├── PlaygroundNative.tsx
│ │ │ ├── Preview.module.scss
│ │ │ ├── Preview.tsx
│ │ │ ├── Select.module.scss
│ │ │ ├── StandalonePlayground.tsx
│ │ │ ├── StandalonePlaygroundNative.module.scss
│ │ │ ├── StandalonePlaygroundNative.tsx
│ │ │ ├── ThemeSwitcher.module.scss
│ │ │ ├── ThemeSwitcher.tsx
│ │ │ ├── ToneSwitcher.tsx
│ │ │ ├── Tooltip.module.scss
│ │ │ ├── Tooltip.tsx
│ │ │ └── utils.ts
│ │ ├── providers
│ │ │ ├── Toast.module.scss
│ │ │ └── ToastProvider.tsx
│ │ ├── state
│ │ │ └── store.ts
│ │ ├── themes
│ │ │ └── theme.ts
│ │ └── utils
│ │ └── helpers.ts
│ ├── xmlui-search
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── index.tsx
│ │ ├── Search.module.scss
│ │ └── Search.tsx
│ ├── xmlui-spreadsheet
│ │ ├── .gitignore
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── index.tsx
│ │ ├── Spreadsheet.tsx
│ │ └── SpreadsheetNative.tsx
│ └── xmlui-website-blocks
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── demo
│ │ ├── components
│ │ │ ├── HeroBackgroundBreakoutPage.xmlui
│ │ │ ├── HeroBackgroundsPage.xmlui
│ │ │ ├── HeroContentsPage.xmlui
│ │ │ ├── HeroTextAlignPage.xmlui
│ │ │ ├── HeroTextPage.xmlui
│ │ │ └── HeroTonesPage.xmlui
│ │ ├── Main.xmlui
│ │ └── themes
│ │ └── default.ts
│ ├── index.html
│ ├── index.ts
│ ├── meta
│ │ └── componentsMetadata.ts
│ ├── package.json
│ ├── public
│ │ └── resources
│ │ ├── building.jpg
│ │ └── xmlui-logo.svg
│ └── src
│ ├── Carousel
│ │ ├── Carousel.module.scss
│ │ ├── Carousel.tsx
│ │ ├── CarouselContext.tsx
│ │ └── CarouselNative.tsx
│ ├── FancyButton
│ │ ├── FancyButton.module.scss
│ │ ├── FancyButton.tsx
│ │ └── FancyButton.xmlui
│ ├── Hello
│ │ ├── Hello.tsx
│ │ ├── Hello.xmlui
│ │ └── Hello.xmlui.xs
│ ├── HeroSection
│ │ ├── HeroSection.module.scss
│ │ ├── HeroSection.spec.ts
│ │ ├── HeroSection.tsx
│ │ └── HeroSectionNative.tsx
│ ├── index.tsx
│ ├── ScrollToTop
│ │ ├── ScrollToTop.module.scss
│ │ ├── ScrollToTop.tsx
│ │ └── ScrollToTopNative.tsx
│ └── vite-env.d.ts
├── playwright.config.ts
├── README.md
├── tools
│ ├── codefence
│ │ └── xmlui-code-fence-docs.md
│ ├── create-app
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── create-app.ts
│ │ ├── helpers
│ │ │ ├── copy.ts
│ │ │ ├── get-pkg-manager.ts
│ │ │ ├── git.ts
│ │ │ ├── install.ts
│ │ │ ├── is-folder-empty.ts
│ │ │ ├── is-writeable.ts
│ │ │ ├── make-dir.ts
│ │ │ └── validate-pkg.ts
│ │ ├── index.ts
│ │ ├── package.json
│ │ ├── templates
│ │ │ ├── default
│ │ │ │ └── ts
│ │ │ │ ├── gitignore
│ │ │ │ ├── index.html
│ │ │ │ ├── index.ts
│ │ │ │ ├── public
│ │ │ │ │ ├── mockServiceWorker.js
│ │ │ │ │ ├── resources
│ │ │ │ │ │ ├── favicon.ico
│ │ │ │ │ │ └── xmlui-logo.svg
│ │ │ │ │ └── serve.json
│ │ │ │ └── src
│ │ │ │ ├── components
│ │ │ │ │ ├── ApiAware.xmlui
│ │ │ │ │ ├── Home.xmlui
│ │ │ │ │ ├── IncButton.xmlui
│ │ │ │ │ └── PagePanel.xmlui
│ │ │ │ ├── config.ts
│ │ │ │ └── Main.xmlui
│ │ │ ├── index.ts
│ │ │ └── types.ts
│ │ └── tsconfig.json
│ ├── create-xmlui-hello-world
│ │ ├── index.js
│ │ └── package.json
│ └── vscode
│ ├── .gitignore
│ ├── .vscode
│ │ ├── launch.json
│ │ └── tasks.json
│ ├── .vscodeignore
│ ├── build.sh
│ ├── CHANGELOG.md
│ ├── esbuild.js
│ ├── eslint.config.mjs
│ ├── formatter-docs.md
│ ├── generate-test-sample.sh
│ ├── LICENSE.md
│ ├── package-lock.json
│ ├── package.json
│ ├── README.md
│ ├── resources
│ │ ├── xmlui-logo.png
│ │ └── xmlui-markup-syntax-highlighting.png
│ ├── src
│ │ ├── extension.ts
│ │ └── server.ts
│ ├── syntaxes
│ │ └── xmlui.tmLanguage.json
│ ├── test-samples
│ │ └── sample.xmlui
│ ├── tsconfig.json
│ └── tsconfig.tsbuildinfo
├── turbo.json
└── xmlui
├── .gitignore
├── bin
│ ├── bootstrap.cjs
│ ├── bootstrap.js
│ ├── build-lib.ts
│ ├── build.ts
│ ├── index.ts
│ ├── preview.ts
│ ├── start.ts
│ ├── vite-xmlui-plugin.ts
│ └── viteConfig.ts
├── CHANGELOG.md
├── conventions
│ ├── component-qa-checklist.md
│ ├── copilot-conventions.md
│ ├── create-xmlui-components.md
│ ├── mermaid.md
│ ├── testing-conventions.md
│ └── xmlui-in-a-nutshell.md
├── dev-docs
│ ├── accessibility.md
│ ├── build-system.md
│ ├── build-xmlui.md
│ ├── component-behaviors.md
│ ├── components-with-options.md
│ ├── containers.md
│ ├── data-operations.md
│ ├── glossary.md
│ ├── index.md
│ ├── next
│ │ ├── component-dev-guide.md
│ │ ├── configuration-management-enhancement-summary.md
│ │ ├── documentation-scripts-refactoring-complete-summary.md
│ │ ├── documentation-scripts-refactoring-plan.md
│ │ ├── duplicate-pattern-extraction-summary.md
│ │ ├── error-handling-standardization-summary.md
│ │ ├── generating-component-reference.md
│ │ ├── index.md
│ │ ├── logging-consistency-implementation-summary.md
│ │ ├── project-build.md
│ │ ├── project-structure.md
│ │ ├── theme-context.md
│ │ ├── tiptap-design-considerations.md
│ │ ├── working-with-code.md
│ │ ├── xmlui-runtime-architecture
│ │ └── xmlui-wcag-accessibility-report.md
│ ├── react-fundamentals.md
│ ├── release-method.md
│ ├── standalone-app.md
│ ├── ud-components.md
│ └── xmlui-repo.md
├── package.json
├── scripts
│ ├── coverage-only.js
│ ├── e2e-test-summary.js
│ ├── generate-docs
│ │ ├── build-downloads-map.mjs
│ │ ├── build-pages-map.mjs
│ │ ├── components-config.json
│ │ ├── configuration-management.mjs
│ │ ├── constants.mjs
│ │ ├── create-theme-files.mjs
│ │ ├── DocsGenerator.mjs
│ │ ├── error-handling.mjs
│ │ ├── extensions-config.json
│ │ ├── folders.mjs
│ │ ├── generate-summary-files.mjs
│ │ ├── get-docs.mjs
│ │ ├── input-handler.mjs
│ │ ├── logger.mjs
│ │ ├── logging-standards.mjs
│ │ ├── MetadataProcessor.mjs
│ │ ├── pattern-utilities.mjs
│ │ └── utils.mjs
│ ├── get-langserver-metadata.js
│ ├── inline-links.mjs
│ └── README-e2e-summary.md
├── src
│ ├── abstractions
│ │ ├── _conventions.md
│ │ ├── ActionDefs.ts
│ │ ├── AppContextDefs.ts
│ │ ├── ComponentDefs.ts
│ │ ├── ContainerDefs.ts
│ │ ├── ExtensionDefs.ts
│ │ ├── FunctionDefs.ts
│ │ ├── RendererDefs.ts
│ │ ├── scripting
│ │ │ ├── BlockScope.ts
│ │ │ ├── Compilation.ts
│ │ │ ├── LogicalThread.ts
│ │ │ ├── LoopScope.ts
│ │ │ ├── modules.ts
│ │ │ ├── ScriptParserError.ts
│ │ │ ├── Token.ts
│ │ │ ├── TryScope.ts
│ │ │ └── TryScopeExp.ts
│ │ └── ThemingDefs.ts
│ ├── components
│ │ ├── _conventions.md
│ │ ├── abstractions.ts
│ │ ├── Accordion
│ │ │ ├── Accordion.md
│ │ │ ├── Accordion.module.scss
│ │ │ ├── Accordion.spec.ts
│ │ │ ├── Accordion.tsx
│ │ │ ├── AccordionContext.tsx
│ │ │ ├── AccordionItem.tsx
│ │ │ ├── AccordionItemNative.tsx
│ │ │ └── AccordionNative.tsx
│ │ ├── Animation
│ │ │ └── AnimationNative.tsx
│ │ ├── APICall
│ │ │ ├── APICall.md
│ │ │ ├── APICall.spec.ts
│ │ │ ├── APICall.tsx
│ │ │ └── APICallNative.tsx
│ │ ├── App
│ │ │ ├── App.md
│ │ │ ├── App.module.scss
│ │ │ ├── App.spec.ts
│ │ │ ├── App.tsx
│ │ │ ├── AppLayoutContext.ts
│ │ │ ├── AppNative.tsx
│ │ │ ├── AppStateContext.ts
│ │ │ ├── doc-resources
│ │ │ │ ├── condensed-sticky.xmlui
│ │ │ │ ├── condensed.xmlui
│ │ │ │ ├── horizontal-sticky.xmlui
│ │ │ │ ├── horizontal.xmlui
│ │ │ │ ├── vertical-full-header.xmlui
│ │ │ │ ├── vertical-sticky.xmlui
│ │ │ │ └── vertical.xmlui
│ │ │ ├── IndexerContext.ts
│ │ │ ├── LinkInfoContext.ts
│ │ │ ├── SearchContext.tsx
│ │ │ ├── Sheet.module.scss
│ │ │ └── Sheet.tsx
│ │ ├── AppHeader
│ │ │ ├── AppHeader.md
│ │ │ ├── AppHeader.module.scss
│ │ │ ├── AppHeader.spec.ts
│ │ │ ├── AppHeader.tsx
│ │ │ └── AppHeaderNative.tsx
│ │ ├── AppState
│ │ │ ├── AppState.md
│ │ │ ├── AppState.spec.ts
│ │ │ ├── AppState.tsx
│ │ │ └── AppStateNative.tsx
│ │ ├── AutoComplete
│ │ │ ├── AutoComplete.md
│ │ │ ├── AutoComplete.module.scss
│ │ │ ├── AutoComplete.spec.ts
│ │ │ ├── AutoComplete.tsx
│ │ │ ├── AutoCompleteContext.tsx
│ │ │ └── AutoCompleteNative.tsx
│ │ ├── Avatar
│ │ │ ├── Avatar.md
│ │ │ ├── Avatar.module.scss
│ │ │ ├── Avatar.spec.ts
│ │ │ ├── Avatar.tsx
│ │ │ └── AvatarNative.tsx
│ │ ├── Backdrop
│ │ │ ├── Backdrop.md
│ │ │ ├── Backdrop.module.scss
│ │ │ ├── Backdrop.spec.ts
│ │ │ ├── Backdrop.tsx
│ │ │ └── BackdropNative.tsx
│ │ ├── Badge
│ │ │ ├── Badge.md
│ │ │ ├── Badge.module.scss
│ │ │ ├── Badge.spec.ts
│ │ │ ├── Badge.tsx
│ │ │ └── BadgeNative.tsx
│ │ ├── Bookmark
│ │ │ ├── Bookmark.md
│ │ │ ├── Bookmark.module.scss
│ │ │ ├── Bookmark.spec.ts
│ │ │ ├── Bookmark.tsx
│ │ │ └── BookmarkNative.tsx
│ │ ├── Breakout
│ │ │ ├── Breakout.module.scss
│ │ │ ├── Breakout.spec.ts
│ │ │ ├── Breakout.tsx
│ │ │ └── BreakoutNative.tsx
│ │ ├── Button
│ │ │ ├── Button-style.spec.ts
│ │ │ ├── Button.md
│ │ │ ├── Button.module.scss
│ │ │ ├── Button.spec.ts
│ │ │ ├── Button.tsx
│ │ │ └── ButtonNative.tsx
│ │ ├── Card
│ │ │ ├── Card.md
│ │ │ ├── Card.module.scss
│ │ │ ├── Card.spec.ts
│ │ │ ├── Card.tsx
│ │ │ └── CardNative.tsx
│ │ ├── Carousel
│ │ │ ├── Carousel.md
│ │ │ ├── Carousel.module.scss
│ │ │ ├── Carousel.spec.ts
│ │ │ ├── Carousel.tsx
│ │ │ ├── CarouselContext.tsx
│ │ │ ├── CarouselItem.tsx
│ │ │ ├── CarouselItemNative.tsx
│ │ │ └── CarouselNative.tsx
│ │ ├── ChangeListener
│ │ │ ├── ChangeListener.md
│ │ │ ├── ChangeListener.spec.ts
│ │ │ ├── ChangeListener.tsx
│ │ │ └── ChangeListenerNative.tsx
│ │ ├── chart-color-schemes.ts
│ │ ├── Charts
│ │ │ ├── AreaChart
│ │ │ │ ├── AreaChart.md
│ │ │ │ ├── AreaChart.spec.ts
│ │ │ │ ├── AreaChart.tsx
│ │ │ │ └── AreaChartNative.tsx
│ │ │ ├── BarChart
│ │ │ │ ├── BarChart.md
│ │ │ │ ├── BarChart.module.scss
│ │ │ │ ├── BarChart.spec.ts
│ │ │ │ ├── BarChart.tsx
│ │ │ │ └── BarChartNative.tsx
│ │ │ ├── DonutChart
│ │ │ │ ├── DonutChart.spec.ts
│ │ │ │ └── DonutChart.tsx
│ │ │ ├── LabelList
│ │ │ │ ├── LabelList.spec.ts
│ │ │ │ ├── LabelList.tsx
│ │ │ │ ├── LabelListNative.module.scss
│ │ │ │ └── LabelListNative.tsx
│ │ │ ├── Legend
│ │ │ │ ├── Legend.spec.ts
│ │ │ │ ├── Legend.tsx
│ │ │ │ └── LegendNative.tsx
│ │ │ ├── LineChart
│ │ │ │ ├── LineChart.md
│ │ │ │ ├── LineChart.module.scss
│ │ │ │ ├── LineChart.spec.ts
│ │ │ │ ├── LineChart.tsx
│ │ │ │ └── LineChartNative.tsx
│ │ │ ├── PieChart
│ │ │ │ ├── PieChart.md
│ │ │ │ ├── PieChart.spec.ts
│ │ │ │ ├── PieChart.tsx
│ │ │ │ ├── PieChartNative.module.scss
│ │ │ │ └── PieChartNative.tsx
│ │ │ ├── RadarChart
│ │ │ │ ├── RadarChart.md
│ │ │ │ ├── RadarChart.spec.ts
│ │ │ │ ├── RadarChart.tsx
│ │ │ │ └── RadarChartNative.tsx
│ │ │ ├── Tooltip
│ │ │ │ ├── TooltipContent.module.scss
│ │ │ │ ├── TooltipContent.spec.ts
│ │ │ │ └── TooltipContent.tsx
│ │ │ └── utils
│ │ │ ├── abstractions.ts
│ │ │ └── ChartProvider.tsx
│ │ ├── Checkbox
│ │ │ ├── Checkbox.md
│ │ │ ├── Checkbox.spec.ts
│ │ │ └── Checkbox.tsx
│ │ ├── CodeBlock
│ │ │ ├── CodeBlock.module.scss
│ │ │ ├── CodeBlock.spec.ts
│ │ │ ├── CodeBlock.tsx
│ │ │ ├── CodeBlockNative.tsx
│ │ │ └── highlight-code.ts
│ │ ├── collectedComponentMetadata.ts
│ │ ├── ColorPicker
│ │ │ ├── ColorPicker.md
│ │ │ ├── ColorPicker.module.scss
│ │ │ ├── ColorPicker.spec.ts
│ │ │ ├── ColorPicker.tsx
│ │ │ └── ColorPickerNative.tsx
│ │ ├── Column
│ │ │ ├── Column.md
│ │ │ ├── Column.tsx
│ │ │ ├── ColumnNative.tsx
│ │ │ ├── doc-resources
│ │ │ │ └── list-component-data.js
│ │ │ └── TableContext.tsx
│ │ ├── component-utils.ts
│ │ ├── ComponentProvider.tsx
│ │ ├── ComponentRegistryContext.tsx
│ │ ├── container-helpers.tsx
│ │ ├── ContentSeparator
│ │ │ ├── ContentSeparator.md
│ │ │ ├── ContentSeparator.module.scss
│ │ │ ├── ContentSeparator.spec.ts
│ │ │ ├── ContentSeparator.tsx
│ │ │ └── ContentSeparatorNative.tsx
│ │ ├── DataSource
│ │ │ ├── DataSource.md
│ │ │ └── DataSource.tsx
│ │ ├── DateInput
│ │ │ ├── DateInput.md
│ │ │ ├── DateInput.module.scss
│ │ │ ├── DateInput.spec.ts
│ │ │ ├── DateInput.tsx
│ │ │ └── DateInputNative.tsx
│ │ ├── DatePicker
│ │ │ ├── DatePicker.md
│ │ │ ├── DatePicker.module.scss
│ │ │ ├── DatePicker.spec.ts
│ │ │ ├── DatePicker.tsx
│ │ │ └── DatePickerNative.tsx
│ │ ├── DropdownMenu
│ │ │ ├── DropdownMenu.md
│ │ │ ├── DropdownMenu.module.scss
│ │ │ ├── DropdownMenu.spec.ts
│ │ │ ├── DropdownMenu.tsx
│ │ │ ├── DropdownMenuNative.tsx
│ │ │ ├── MenuItem.md
│ │ │ └── SubMenuItem.md
│ │ ├── EmojiSelector
│ │ │ ├── EmojiSelector.md
│ │ │ ├── EmojiSelector.spec.ts
│ │ │ ├── EmojiSelector.tsx
│ │ │ └── EmojiSelectorNative.tsx
│ │ ├── ExpandableItem
│ │ │ ├── ExpandableItem.module.scss
│ │ │ ├── ExpandableItem.spec.ts
│ │ │ ├── ExpandableItem.tsx
│ │ │ └── ExpandableItemNative.tsx
│ │ ├── FileInput
│ │ │ ├── FileInput.md
│ │ │ ├── FileInput.module.scss
│ │ │ ├── FileInput.spec.ts
│ │ │ ├── FileInput.tsx
│ │ │ └── FileInputNative.tsx
│ │ ├── FileUploadDropZone
│ │ │ ├── FileUploadDropZone.md
│ │ │ ├── FileUploadDropZone.module.scss
│ │ │ ├── FileUploadDropZone.spec.ts
│ │ │ ├── FileUploadDropZone.tsx
│ │ │ └── FileUploadDropZoneNative.tsx
│ │ ├── FlowLayout
│ │ │ ├── FlowLayout.md
│ │ │ ├── FlowLayout.module.scss
│ │ │ ├── FlowLayout.spec.ts
│ │ │ ├── FlowLayout.spec.ts-snapshots
│ │ │ │ └── Edge-cases-boxShadow-is-not-clipped-1-non-smoke-darwin.png
│ │ │ ├── FlowLayout.tsx
│ │ │ └── FlowLayoutNative.tsx
│ │ ├── Footer
│ │ │ ├── Footer.md
│ │ │ ├── Footer.module.scss
│ │ │ ├── Footer.spec.ts
│ │ │ ├── Footer.tsx
│ │ │ └── FooterNative.tsx
│ │ ├── Form
│ │ │ ├── Form.md
│ │ │ ├── Form.module.scss
│ │ │ ├── Form.spec.ts
│ │ │ ├── Form.tsx
│ │ │ ├── formActions.ts
│ │ │ ├── FormContext.ts
│ │ │ └── FormNative.tsx
│ │ ├── FormItem
│ │ │ ├── FormItem.md
│ │ │ ├── FormItem.module.scss
│ │ │ ├── FormItem.spec.ts
│ │ │ ├── FormItem.tsx
│ │ │ ├── FormItemNative.tsx
│ │ │ ├── HelperText.module.scss
│ │ │ ├── HelperText.tsx
│ │ │ ├── ItemWithLabel.tsx
│ │ │ └── Validations.ts
│ │ ├── FormSection
│ │ │ ├── FormSection.md
│ │ │ ├── FormSection.ts
│ │ │ └── FormSection.xmlui
│ │ ├── Fragment
│ │ │ ├── Fragment.spec.ts
│ │ │ └── Fragment.tsx
│ │ ├── Heading
│ │ │ ├── abstractions.ts
│ │ │ ├── H1.md
│ │ │ ├── H1.spec.ts
│ │ │ ├── H2.md
│ │ │ ├── H2.spec.ts
│ │ │ ├── H3.md
│ │ │ ├── H3.spec.ts
│ │ │ ├── H4.md
│ │ │ ├── H4.spec.ts
│ │ │ ├── H5.md
│ │ │ ├── H5.spec.ts
│ │ │ ├── H6.md
│ │ │ ├── H6.spec.ts
│ │ │ ├── Heading.md
│ │ │ ├── Heading.module.scss
│ │ │ ├── Heading.spec.ts
│ │ │ ├── Heading.tsx
│ │ │ └── HeadingNative.tsx
│ │ ├── HoverCard
│ │ │ ├── HoverCard.tsx
│ │ │ └── HovercardNative.tsx
│ │ ├── HtmlTags
│ │ │ ├── HtmlTags.module.scss
│ │ │ ├── HtmlTags.spec.ts
│ │ │ └── HtmlTags.tsx
│ │ ├── Icon
│ │ │ ├── AdmonitionDanger.tsx
│ │ │ ├── AdmonitionInfo.tsx
│ │ │ ├── AdmonitionNote.tsx
│ │ │ ├── AdmonitionTip.tsx
│ │ │ ├── AdmonitionWarning.tsx
│ │ │ ├── ApiIcon.tsx
│ │ │ ├── ArrowDropDown.module.scss
│ │ │ ├── ArrowDropDown.tsx
│ │ │ ├── ArrowDropUp.module.scss
│ │ │ ├── ArrowDropUp.tsx
│ │ │ ├── ArrowLeft.module.scss
│ │ │ ├── ArrowLeft.tsx
│ │ │ ├── ArrowRight.module.scss
│ │ │ ├── ArrowRight.tsx
│ │ │ ├── Attach.tsx
│ │ │ ├── Binding.module.scss
│ │ │ ├── Binding.tsx
│ │ │ ├── BoardIcon.tsx
│ │ │ ├── BoxIcon.tsx
│ │ │ ├── CheckIcon.tsx
│ │ │ ├── ChevronDownIcon.tsx
│ │ │ ├── ChevronLeft.tsx
│ │ │ ├── ChevronRight.tsx
│ │ │ ├── ChevronUpIcon.tsx
│ │ │ ├── CodeFileIcon.tsx
│ │ │ ├── CodeSandbox.tsx
│ │ │ ├── CompactListIcon.tsx
│ │ │ ├── ContentCopyIcon.tsx
│ │ │ ├── DarkToLightIcon.tsx
│ │ │ ├── DatabaseIcon.module.scss
│ │ │ ├── DatabaseIcon.tsx
│ │ │ ├── DocFileIcon.tsx
│ │ │ ├── DocIcon.tsx
│ │ │ ├── DotMenuHorizontalIcon.tsx
│ │ │ ├── DotMenuIcon.tsx
│ │ │ ├── EmailIcon.tsx
│ │ │ ├── EmptyFolderIcon.tsx
│ │ │ ├── ErrorIcon.tsx
│ │ │ ├── ExpressionIcon.tsx
│ │ │ ├── FillPlusCricleIcon.tsx
│ │ │ ├── FilterIcon.tsx
│ │ │ ├── FolderIcon.tsx
│ │ │ ├── GlobeIcon.tsx
│ │ │ ├── HomeIcon.tsx
│ │ │ ├── HyperLinkIcon.tsx
│ │ │ ├── Icon.md
│ │ │ ├── Icon.module.scss
│ │ │ ├── Icon.spec.ts
│ │ │ ├── Icon.tsx
│ │ │ ├── IconNative.tsx
│ │ │ ├── ImageFileIcon.tsx
│ │ │ ├── Inspect.tsx
│ │ │ ├── LightToDark.tsx
│ │ │ ├── LinkIcon.tsx
│ │ │ ├── ListIcon.tsx
│ │ │ ├── LooseListIcon.tsx
│ │ │ ├── MoonIcon.tsx
│ │ │ ├── MoreOptionsIcon.tsx
│ │ │ ├── NoSortIcon.tsx
│ │ │ ├── PDFIcon.tsx
│ │ │ ├── PenIcon.tsx
│ │ │ ├── PhoneIcon.tsx
│ │ │ ├── PhotoIcon.tsx
│ │ │ ├── PlusIcon.tsx
│ │ │ ├── SearchIcon.tsx
│ │ │ ├── ShareIcon.tsx
│ │ │ ├── SortAscendingIcon.tsx
│ │ │ ├── SortDescendingIcon.tsx
│ │ │ ├── StarsIcon.tsx
│ │ │ ├── SunIcon.tsx
│ │ │ ├── svg
│ │ │ │ ├── admonition_danger.svg
│ │ │ │ ├── admonition_info.svg
│ │ │ │ ├── admonition_note.svg
│ │ │ │ ├── admonition_tip.svg
│ │ │ │ ├── admonition_warning.svg
│ │ │ │ ├── api.svg
│ │ │ │ ├── arrow-dropdown.svg
│ │ │ │ ├── arrow-left.svg
│ │ │ │ ├── arrow-right.svg
│ │ │ │ ├── arrow-up.svg
│ │ │ │ ├── attach.svg
│ │ │ │ ├── binding.svg
│ │ │ │ ├── box.svg
│ │ │ │ ├── bulb.svg
│ │ │ │ ├── code-file.svg
│ │ │ │ ├── code-sandbox.svg
│ │ │ │ ├── dark_to_light.svg
│ │ │ │ ├── database.svg
│ │ │ │ ├── doc.svg
│ │ │ │ ├── empty-folder.svg
│ │ │ │ ├── expression.svg
│ │ │ │ ├── eye-closed.svg
│ │ │ │ ├── eye-dark.svg
│ │ │ │ ├── eye.svg
│ │ │ │ ├── file-text.svg
│ │ │ │ ├── filter.svg
│ │ │ │ ├── folder.svg
│ │ │ │ ├── img.svg
│ │ │ │ ├── inspect.svg
│ │ │ │ ├── light_to_dark.svg
│ │ │ │ ├── moon.svg
│ │ │ │ ├── pdf.svg
│ │ │ │ ├── photo.svg
│ │ │ │ ├── share.svg
│ │ │ │ ├── stars.svg
│ │ │ │ ├── sun.svg
│ │ │ │ ├── trending-down.svg
│ │ │ │ ├── trending-level.svg
│ │ │ │ ├── trending-up.svg
│ │ │ │ ├── txt.svg
│ │ │ │ ├── unknown-file.svg
│ │ │ │ ├── unlink.svg
│ │ │ │ └── xls.svg
│ │ │ ├── TableDeleteColumnIcon.tsx
│ │ │ ├── TableDeleteRowIcon.tsx
│ │ │ ├── TableInsertColumnIcon.tsx
│ │ │ ├── TableInsertRowIcon.tsx
│ │ │ ├── TrashIcon.tsx
│ │ │ ├── TrendingDownIcon.tsx
│ │ │ ├── TrendingLevelIcon.tsx
│ │ │ ├── TrendingUpIcon.tsx
│ │ │ ├── TxtIcon.tsx
│ │ │ ├── UnknownFileIcon.tsx
│ │ │ ├── UnlinkIcon.tsx
│ │ │ ├── UserIcon.tsx
│ │ │ ├── WarningIcon.tsx
│ │ │ └── XlsIcon.tsx
│ │ ├── IconProvider.tsx
│ │ ├── IconRegistryContext.tsx
│ │ ├── IFrame
│ │ │ ├── IFrame.md
│ │ │ ├── IFrame.module.scss
│ │ │ ├── IFrame.spec.ts
│ │ │ ├── IFrame.tsx
│ │ │ └── IFrameNative.tsx
│ │ ├── Image
│ │ │ ├── Image.md
│ │ │ ├── Image.module.scss
│ │ │ ├── Image.spec.ts
│ │ │ ├── Image.tsx
│ │ │ └── ImageNative.tsx
│ │ ├── Input
│ │ │ ├── index.ts
│ │ │ ├── InputAdornment.module.scss
│ │ │ ├── InputAdornment.tsx
│ │ │ ├── InputDivider.module.scss
│ │ │ ├── InputDivider.tsx
│ │ │ ├── InputLabel.module.scss
│ │ │ ├── InputLabel.tsx
│ │ │ ├── PartialInput.module.scss
│ │ │ └── PartialInput.tsx
│ │ ├── InspectButton
│ │ │ ├── InspectButton.module.scss
│ │ │ └── InspectButton.tsx
│ │ ├── Items
│ │ │ ├── Items.md
│ │ │ ├── Items.spec.ts
│ │ │ ├── Items.tsx
│ │ │ └── ItemsNative.tsx
│ │ ├── Link
│ │ │ ├── Link.md
│ │ │ ├── Link.module.scss
│ │ │ ├── Link.spec.ts
│ │ │ ├── Link.tsx
│ │ │ └── LinkNative.tsx
│ │ ├── List
│ │ │ ├── doc-resources
│ │ │ │ └── list-component-data.js
│ │ │ ├── List.md
│ │ │ ├── List.module.scss
│ │ │ ├── List.spec.ts
│ │ │ ├── List.tsx
│ │ │ └── ListNative.tsx
│ │ ├── Logo
│ │ │ ├── doc-resources
│ │ │ │ └── xmlui-logo.svg
│ │ │ ├── Logo.md
│ │ │ ├── Logo.tsx
│ │ │ └── LogoNative.tsx
│ │ ├── Markdown
│ │ │ ├── CodeText.module.scss
│ │ │ ├── CodeText.tsx
│ │ │ ├── Markdown.md
│ │ │ ├── Markdown.module.scss
│ │ │ ├── Markdown.spec.ts
│ │ │ ├── Markdown.tsx
│ │ │ ├── MarkdownNative.tsx
│ │ │ ├── parse-binding-expr.ts
│ │ │ └── utils.ts
│ │ ├── metadata-helpers.ts
│ │ ├── ModalDialog
│ │ │ ├── ConfirmationModalContextProvider.tsx
│ │ │ ├── Dialog.module.scss
│ │ │ ├── Dialog.tsx
│ │ │ ├── ModalDialog.md
│ │ │ ├── ModalDialog.module.scss
│ │ │ ├── ModalDialog.spec.ts
│ │ │ ├── ModalDialog.tsx
│ │ │ ├── ModalDialogNative.tsx
│ │ │ └── ModalVisibilityContext.tsx
│ │ ├── NavGroup
│ │ │ ├── NavGroup.md
│ │ │ ├── NavGroup.module.scss
│ │ │ ├── NavGroup.spec.ts
│ │ │ ├── NavGroup.tsx
│ │ │ ├── NavGroupContext.ts
│ │ │ └── NavGroupNative.tsx
│ │ ├── NavLink
│ │ │ ├── NavLink.md
│ │ │ ├── NavLink.module.scss
│ │ │ ├── NavLink.spec.ts
│ │ │ ├── NavLink.tsx
│ │ │ └── NavLinkNative.tsx
│ │ ├── NavPanel
│ │ │ ├── NavPanel.md
│ │ │ ├── NavPanel.module.scss
│ │ │ ├── NavPanel.spec.ts
│ │ │ ├── NavPanel.tsx
│ │ │ └── NavPanelNative.tsx
│ │ ├── NestedApp
│ │ │ ├── AppWithCodeView.module.scss
│ │ │ ├── AppWithCodeView.tsx
│ │ │ ├── AppWithCodeViewNative.tsx
│ │ │ ├── defaultProps.tsx
│ │ │ ├── logo.svg
│ │ │ ├── NestedApp.module.scss
│ │ │ ├── NestedApp.tsx
│ │ │ ├── NestedAppNative.tsx
│ │ │ ├── Tooltip.module.scss
│ │ │ ├── Tooltip.tsx
│ │ │ └── utils.ts
│ │ ├── NoResult
│ │ │ ├── NoResult.md
│ │ │ ├── NoResult.module.scss
│ │ │ ├── NoResult.spec.ts
│ │ │ ├── NoResult.tsx
│ │ │ └── NoResultNative.tsx
│ │ ├── NumberBox
│ │ │ ├── numberbox-abstractions.ts
│ │ │ ├── NumberBox.md
│ │ │ ├── NumberBox.module.scss
│ │ │ ├── NumberBox.spec.ts
│ │ │ ├── NumberBox.tsx
│ │ │ └── NumberBoxNative.tsx
│ │ ├── Option
│ │ │ ├── Option.md
│ │ │ ├── Option.spec.ts
│ │ │ ├── Option.tsx
│ │ │ ├── OptionNative.tsx
│ │ │ └── OptionTypeProvider.tsx
│ │ ├── PageMetaTitle
│ │ │ ├── PageMetaTilteNative.tsx
│ │ │ ├── PageMetaTitle.md
│ │ │ ├── PageMetaTitle.spec.ts
│ │ │ └── PageMetaTitle.tsx
│ │ ├── Pages
│ │ │ ├── Page.md
│ │ │ ├── Pages.md
│ │ │ ├── Pages.module.scss
│ │ │ ├── Pages.tsx
│ │ │ └── PagesNative.tsx
│ │ ├── Pagination
│ │ │ ├── Pagination.md
│ │ │ ├── Pagination.module.scss
│ │ │ ├── Pagination.spec.ts
│ │ │ ├── Pagination.tsx
│ │ │ └── PaginationNative.tsx
│ │ ├── PositionedContainer
│ │ │ ├── PositionedContainer.module.scss
│ │ │ ├── PositionedContainer.tsx
│ │ │ └── PositionedContainerNative.tsx
│ │ ├── ProfileMenu
│ │ │ ├── ProfileMenu.module.scss
│ │ │ └── ProfileMenu.tsx
│ │ ├── ProgressBar
│ │ │ ├── ProgressBar.md
│ │ │ ├── ProgressBar.module.scss
│ │ │ ├── ProgressBar.spec.ts
│ │ │ ├── ProgressBar.tsx
│ │ │ └── ProgressBarNative.tsx
│ │ ├── Queue
│ │ │ ├── Queue.md
│ │ │ ├── Queue.spec.ts
│ │ │ ├── Queue.tsx
│ │ │ ├── queueActions.ts
│ │ │ └── QueueNative.tsx
│ │ ├── RadioGroup
│ │ │ ├── RadioGroup.md
│ │ │ ├── RadioGroup.module.scss
│ │ │ ├── RadioGroup.spec.ts
│ │ │ ├── RadioGroup.tsx
│ │ │ ├── RadioGroupNative.tsx
│ │ │ ├── RadioItem.tsx
│ │ │ └── RadioItemNative.tsx
│ │ ├── RealTimeAdapter
│ │ │ ├── RealTimeAdapter.tsx
│ │ │ └── RealTimeAdapterNative.tsx
│ │ ├── Redirect
│ │ │ ├── Redirect.md
│ │ │ ├── Redirect.spec.ts
│ │ │ └── Redirect.tsx
│ │ ├── ResponsiveBar
│ │ │ ├── README.md
│ │ │ ├── ResponsiveBar.md
│ │ │ ├── ResponsiveBar.module.scss
│ │ │ ├── ResponsiveBar.spec.ts
│ │ │ ├── ResponsiveBar.tsx
│ │ │ └── ResponsiveBarNative.tsx
│ │ ├── Select
│ │ │ ├── HiddenOption.tsx
│ │ │ ├── OptionContext.ts
│ │ │ ├── Select.md
│ │ │ ├── Select.module.scss
│ │ │ ├── Select.spec.ts
│ │ │ ├── Select.tsx
│ │ │ ├── SelectContext.tsx
│ │ │ └── SelectNative.tsx
│ │ ├── SelectionStore
│ │ │ ├── SelectionStore.md
│ │ │ ├── SelectionStore.tsx
│ │ │ └── SelectionStoreNative.tsx
│ │ ├── Slider
│ │ │ ├── Slider.md
│ │ │ ├── Slider.module.scss
│ │ │ ├── Slider.spec.ts
│ │ │ ├── Slider.tsx
│ │ │ └── SliderNative.tsx
│ │ ├── Slot
│ │ │ ├── Slot.md
│ │ │ ├── Slot.spec.ts
│ │ │ └── Slot.ts
│ │ ├── SlotItem.tsx
│ │ ├── SpaceFiller
│ │ │ ├── SpaceFiller.md
│ │ │ ├── SpaceFiller.module.scss
│ │ │ ├── SpaceFiller.spec.ts
│ │ │ ├── SpaceFiller.tsx
│ │ │ └── SpaceFillerNative.tsx
│ │ ├── Spinner
│ │ │ ├── Spinner.md
│ │ │ ├── Spinner.module.scss
│ │ │ ├── Spinner.spec.ts
│ │ │ ├── Spinner.tsx
│ │ │ └── SpinnerNative.tsx
│ │ ├── Splitter
│ │ │ ├── HSplitter.md
│ │ │ ├── HSplitter.spec.ts
│ │ │ ├── Splitter.md
│ │ │ ├── Splitter.module.scss
│ │ │ ├── Splitter.spec.ts
│ │ │ ├── Splitter.tsx
│ │ │ ├── SplitterNative.tsx
│ │ │ ├── utils.ts
│ │ │ ├── VSplitter.md
│ │ │ └── VSplitter.spec.ts
│ │ ├── Stack
│ │ │ ├── CHStack.md
│ │ │ ├── CHStack.spec.ts
│ │ │ ├── CVStack.md
│ │ │ ├── CVStack.spec.ts
│ │ │ ├── HStack.md
│ │ │ ├── HStack.spec.ts
│ │ │ ├── Stack.md
│ │ │ ├── Stack.module.scss
│ │ │ ├── Stack.spec.ts
│ │ │ ├── Stack.tsx
│ │ │ ├── StackNative.tsx
│ │ │ ├── VStack.md
│ │ │ └── VStack.spec.ts
│ │ ├── StickyBox
│ │ │ ├── StickyBox.md
│ │ │ ├── StickyBox.module.scss
│ │ │ ├── StickyBox.tsx
│ │ │ └── StickyBoxNative.tsx
│ │ ├── Switch
│ │ │ ├── Switch.md
│ │ │ ├── Switch.spec.ts
│ │ │ └── Switch.tsx
│ │ ├── Table
│ │ │ ├── doc-resources
│ │ │ │ └── list-component-data.js
│ │ │ ├── react-table-config.d.ts
│ │ │ ├── Table.md
│ │ │ ├── Table.module.scss
│ │ │ ├── Table.spec.ts
│ │ │ ├── Table.tsx
│ │ │ ├── TableNative.tsx
│ │ │ └── useRowSelection.tsx
│ │ ├── TableOfContents
│ │ │ ├── TableOfContents.module.scss
│ │ │ ├── TableOfContents.spec.ts
│ │ │ ├── TableOfContents.tsx
│ │ │ └── TableOfContentsNative.tsx
│ │ ├── Tabs
│ │ │ ├── TabContext.tsx
│ │ │ ├── TabItem.md
│ │ │ ├── TabItem.tsx
│ │ │ ├── TabItemNative.tsx
│ │ │ ├── Tabs.md
│ │ │ ├── Tabs.module.scss
│ │ │ ├── Tabs.spec.ts
│ │ │ ├── Tabs.tsx
│ │ │ └── TabsNative.tsx
│ │ ├── Text
│ │ │ ├── Text.md
│ │ │ ├── Text.module.scss
│ │ │ ├── Text.spec.ts
│ │ │ ├── Text.tsx
│ │ │ └── TextNative.tsx
│ │ ├── TextArea
│ │ │ ├── TextArea.md
│ │ │ ├── TextArea.module.scss
│ │ │ ├── TextArea.spec.ts
│ │ │ ├── TextArea.tsx
│ │ │ ├── TextAreaNative.tsx
│ │ │ ├── TextAreaResizable.tsx
│ │ │ └── useComposedRef.ts
│ │ ├── TextBox
│ │ │ ├── TextBox.md
│ │ │ ├── TextBox.module.scss
│ │ │ ├── TextBox.spec.ts
│ │ │ ├── TextBox.tsx
│ │ │ └── TextBoxNative.tsx
│ │ ├── Theme
│ │ │ ├── NotificationToast.tsx
│ │ │ ├── Theme.md
│ │ │ ├── Theme.module.scss
│ │ │ ├── Theme.spec.ts
│ │ │ ├── Theme.tsx
│ │ │ └── ThemeNative.tsx
│ │ ├── TimeInput
│ │ │ ├── TimeInput.md
│ │ │ ├── TimeInput.module.scss
│ │ │ ├── TimeInput.spec.ts
│ │ │ ├── TimeInput.tsx
│ │ │ ├── TimeInputNative.tsx
│ │ │ └── utils.ts
│ │ ├── Timer
│ │ │ ├── Timer.md
│ │ │ ├── Timer.spec.ts
│ │ │ ├── Timer.tsx
│ │ │ └── TimerNative.tsx
│ │ ├── Toggle
│ │ │ ├── Toggle.module.scss
│ │ │ └── Toggle.tsx
│ │ ├── ToneChangerButton
│ │ │ ├── ToneChangerButton.md
│ │ │ ├── ToneChangerButton.spec.ts
│ │ │ └── ToneChangerButton.tsx
│ │ ├── ToneSwitch
│ │ │ ├── ToneSwitch.md
│ │ │ ├── ToneSwitch.module.scss
│ │ │ ├── ToneSwitch.spec.ts
│ │ │ ├── ToneSwitch.tsx
│ │ │ └── ToneSwitchNative.tsx
│ │ ├── Tooltip
│ │ │ ├── Tooltip.md
│ │ │ ├── Tooltip.module.scss
│ │ │ ├── Tooltip.spec.ts
│ │ │ ├── Tooltip.tsx
│ │ │ └── TooltipNative.tsx
│ │ ├── Tree
│ │ │ ├── testData.ts
│ │ │ ├── Tree-dynamic.spec.ts
│ │ │ ├── Tree-icons.spec.ts
│ │ │ ├── Tree.md
│ │ │ ├── Tree.spec.ts
│ │ │ ├── TreeComponent.module.scss
│ │ │ ├── TreeComponent.tsx
│ │ │ └── TreeNative.tsx
│ │ ├── TreeDisplay
│ │ │ ├── TreeDisplay.md
│ │ │ ├── TreeDisplay.module.scss
│ │ │ ├── TreeDisplay.tsx
│ │ │ └── TreeDisplayNative.tsx
│ │ ├── ValidationSummary
│ │ │ ├── ValidationSummary.module.scss
│ │ │ └── ValidationSummary.tsx
│ │ └── VisuallyHidden.tsx
│ ├── components-core
│ │ ├── abstractions
│ │ │ ├── ComponentRenderer.ts
│ │ │ ├── LoaderRenderer.ts
│ │ │ ├── standalone.ts
│ │ │ └── treeAbstractions.ts
│ │ ├── action
│ │ │ ├── actions.ts
│ │ │ ├── APICall.tsx
│ │ │ ├── FileDownloadAction.tsx
│ │ │ ├── FileUploadAction.tsx
│ │ │ ├── NavigateAction.tsx
│ │ │ └── TimedAction.tsx
│ │ ├── ApiBoundComponent.tsx
│ │ ├── appContext
│ │ │ ├── date-functions.ts
│ │ │ ├── math-function.ts
│ │ │ └── misc-utils.ts
│ │ ├── AppContext.tsx
│ │ ├── behaviors
│ │ │ ├── Behavior.tsx
│ │ │ └── CoreBehaviors.tsx
│ │ ├── component-hooks.ts
│ │ ├── ComponentDecorator.tsx
│ │ ├── ComponentViewer.tsx
│ │ ├── CompoundComponent.tsx
│ │ ├── constants.ts
│ │ ├── DebugViewProvider.tsx
│ │ ├── descriptorHelper.ts
│ │ ├── devtools
│ │ │ ├── InspectorDialog.module.scss
│ │ │ ├── InspectorDialog.tsx
│ │ │ └── InspectorDialogVisibilityContext.tsx
│ │ ├── EngineError.ts
│ │ ├── event-handlers.ts
│ │ ├── InspectorButton.module.scss
│ │ ├── InspectorContext.tsx
│ │ ├── interception
│ │ │ ├── abstractions.ts
│ │ │ ├── ApiInterceptor.ts
│ │ │ ├── ApiInterceptorProvider.tsx
│ │ │ ├── apiInterceptorWorker.ts
│ │ │ ├── Backend.ts
│ │ │ ├── Errors.ts
│ │ │ ├── IndexedDb.ts
│ │ │ ├── initMock.ts
│ │ │ ├── InMemoryDb.ts
│ │ │ ├── ReadonlyCollection.ts
│ │ │ └── useApiInterceptorContext.tsx
│ │ ├── loader
│ │ │ ├── ApiLoader.tsx
│ │ │ ├── DataLoader.tsx
│ │ │ ├── ExternalDataLoader.tsx
│ │ │ ├── Loader.tsx
│ │ │ ├── MockLoaderRenderer.tsx
│ │ │ └── PageableLoader.tsx
│ │ ├── LoaderComponent.tsx
│ │ ├── markup-check.ts
│ │ ├── parts.ts
│ │ ├── renderers.ts
│ │ ├── rendering
│ │ │ ├── AppContent.tsx
│ │ │ ├── AppRoot.tsx
│ │ │ ├── AppWrapper.tsx
│ │ │ ├── buildProxy.ts
│ │ │ ├── collectFnVarDeps.ts
│ │ │ ├── ComponentAdapter.tsx
│ │ │ ├── ComponentWrapper.tsx
│ │ │ ├── Container.tsx
│ │ │ ├── containers.ts
│ │ │ ├── ContainerWrapper.tsx
│ │ │ ├── ErrorBoundary.module.scss
│ │ │ ├── ErrorBoundary.tsx
│ │ │ ├── InvalidComponent.module.scss
│ │ │ ├── InvalidComponent.tsx
│ │ │ ├── nodeUtils.ts
│ │ │ ├── reducer.ts
│ │ │ ├── renderChild.tsx
│ │ │ ├── StandaloneComponent.tsx
│ │ │ ├── StateContainer.tsx
│ │ │ ├── UnknownComponent.module.scss
│ │ │ ├── UnknownComponent.tsx
│ │ │ └── valueExtractor.ts
│ │ ├── reportEngineError.ts
│ │ ├── RestApiProxy.ts
│ │ ├── script-runner
│ │ │ ├── asyncProxy.ts
│ │ │ ├── AttributeValueParser.ts
│ │ │ ├── bannedFunctions.ts
│ │ │ ├── BindingTreeEvaluationContext.ts
│ │ │ ├── eval-tree-async.ts
│ │ │ ├── eval-tree-common.ts
│ │ │ ├── eval-tree-sync.ts
│ │ │ ├── ParameterParser.ts
│ │ │ ├── process-statement-async.ts
│ │ │ ├── process-statement-common.ts
│ │ │ ├── process-statement-sync.ts
│ │ │ ├── ScriptingSourceTree.ts
│ │ │ ├── simplify-expression.ts
│ │ │ ├── statement-queue.ts
│ │ │ └── visitors.ts
│ │ ├── StandaloneApp.tsx
│ │ ├── StandaloneExtensionManager.ts
│ │ ├── TableOfContentsContext.tsx
│ │ ├── theming
│ │ │ ├── _themes.scss
│ │ │ ├── component-layout-resolver.ts
│ │ │ ├── extendThemeUtils.ts
│ │ │ ├── hvar.ts
│ │ │ ├── layout-resolver.ts
│ │ │ ├── parse-layout-props.ts
│ │ │ ├── StyleContext.tsx
│ │ │ ├── StyleRegistry.ts
│ │ │ ├── ThemeContext.tsx
│ │ │ ├── ThemeProvider.tsx
│ │ │ ├── themes
│ │ │ │ ├── base-utils.ts
│ │ │ │ ├── palette.ts
│ │ │ │ ├── root.ts
│ │ │ │ ├── solid.ts
│ │ │ │ ├── theme-colors.ts
│ │ │ │ └── xmlui.ts
│ │ │ ├── themeVars.module.scss
│ │ │ ├── themeVars.ts
│ │ │ ├── transformThemeVars.ts
│ │ │ └── utils.ts
│ │ ├── utils
│ │ │ ├── actionUtils.ts
│ │ │ ├── audio-utils.ts
│ │ │ ├── base64-utils.ts
│ │ │ ├── compound-utils.ts
│ │ │ ├── css-utils.ts
│ │ │ ├── DataLoaderQueryKeyGenerator.ts
│ │ │ ├── date-utils.ts
│ │ │ ├── extractParam.ts
│ │ │ ├── hooks.tsx
│ │ │ ├── LruCache.ts
│ │ │ ├── mergeProps.ts
│ │ │ ├── misc.ts
│ │ │ ├── request-params.ts
│ │ │ ├── statementUtils.ts
│ │ │ └── treeUtils.ts
│ │ └── xmlui-parser.ts
│ ├── index-standalone.ts
│ ├── index.scss
│ ├── index.ts
│ ├── language-server
│ │ ├── server-common.ts
│ │ ├── server-web-worker.ts
│ │ ├── server.ts
│ │ ├── services
│ │ │ ├── common
│ │ │ │ ├── docs-generation.ts
│ │ │ │ ├── lsp-utils.ts
│ │ │ │ ├── metadata-utils.ts
│ │ │ │ └── syntax-node-utilities.ts
│ │ │ ├── completion.ts
│ │ │ ├── diagnostic.ts
│ │ │ ├── format.ts
│ │ │ └── hover.ts
│ │ └── xmlui-metadata-generated.js
│ ├── logging
│ │ ├── LoggerContext.tsx
│ │ ├── LoggerInitializer.tsx
│ │ ├── LoggerService.ts
│ │ └── xmlui.ts
│ ├── logo.svg
│ ├── parsers
│ │ ├── common
│ │ │ ├── GenericToken.ts
│ │ │ ├── InputStream.ts
│ │ │ └── utils.ts
│ │ ├── scripting
│ │ │ ├── code-behind-collect.ts
│ │ │ ├── Lexer.ts
│ │ │ ├── modules.ts
│ │ │ ├── Parser.ts
│ │ │ ├── ParserError.ts
│ │ │ ├── ScriptingNodeTypes.ts
│ │ │ ├── TokenTrait.ts
│ │ │ ├── TokenType.ts
│ │ │ └── tree-visitor.ts
│ │ ├── style-parser
│ │ │ ├── errors.ts
│ │ │ ├── source-tree.ts
│ │ │ ├── StyleInputStream.ts
│ │ │ ├── StyleLexer.ts
│ │ │ ├── StyleParser.ts
│ │ │ └── tokens.ts
│ │ └── xmlui-parser
│ │ ├── CharacterCodes.ts
│ │ ├── diagnostics.ts
│ │ ├── fileExtensions.ts
│ │ ├── index.ts
│ │ ├── lint.ts
│ │ ├── parser.ts
│ │ ├── ParserError.ts
│ │ ├── scanner.ts
│ │ ├── syntax-kind.ts
│ │ ├── syntax-node.ts
│ │ ├── transform.ts
│ │ ├── utils.ts
│ │ ├── xmlui-serializer.ts
│ │ └── xmlui-tree.ts
│ ├── react-app-env.d.ts
│ ├── syntax
│ │ ├── monaco
│ │ │ ├── grammar.monacoLanguage.ts
│ │ │ ├── index.ts
│ │ │ ├── xmlui-dark.ts
│ │ │ ├── xmlui-light.ts
│ │ │ └── xmluiscript.monacoLanguage.ts
│ │ └── textMate
│ │ ├── index.ts
│ │ ├── xmlui-dark.json
│ │ ├── xmlui-light.json
│ │ ├── xmlui.json
│ │ └── xmlui.tmLanguage.json
│ ├── testing
│ │ ├── assertions.ts
│ │ ├── component-test-helpers.ts
│ │ ├── ComponentDrivers.ts
│ │ ├── drivers
│ │ │ ├── DateInputDriver.ts
│ │ │ ├── index.ts
│ │ │ ├── ModalDialogDriver.ts
│ │ │ ├── NumberBoxDriver.ts
│ │ │ ├── TextBoxDriver.ts
│ │ │ ├── TimeInputDriver.ts
│ │ │ ├── TimerDriver.ts
│ │ │ └── TreeDriver.ts
│ │ ├── fixtures.ts
│ │ ├── index.ts
│ │ ├── infrastructure
│ │ │ ├── index.html
│ │ │ ├── main.tsx
│ │ │ ├── public
│ │ │ │ ├── mockServiceWorker.js
│ │ │ │ ├── resources
│ │ │ │ │ ├── bell.svg
│ │ │ │ │ ├── box.svg
│ │ │ │ │ ├── doc.svg
│ │ │ │ │ ├── eye.svg
│ │ │ │ │ ├── flower-640x480.jpg
│ │ │ │ │ ├── sun.svg
│ │ │ │ │ ├── test-image-100x100.jpg
│ │ │ │ │ └── txt.svg
│ │ │ │ └── serve.json
│ │ │ └── TestBed.tsx
│ │ └── themed-app-test-helpers.ts
│ └── vite-env.d.ts
├── tests
│ ├── components
│ │ ├── CodeBlock
│ │ │ └── hightlight-code.test.ts
│ │ ├── playground-pattern.test.ts
│ │ └── Tree
│ │ └── Tree-states.test.ts
│ ├── components-core
│ │ ├── abstractions
│ │ │ └── treeAbstractions.test.ts
│ │ ├── container
│ │ │ └── buildProxy.test.ts
│ │ ├── interception
│ │ │ ├── orderBy.test.ts
│ │ │ ├── ReadOnlyCollection.test.ts
│ │ │ └── request-param-converter.test.ts
│ │ ├── scripts-runner
│ │ │ ├── AttributeValueParser.test.ts
│ │ │ ├── eval-tree-arrow-async.test.ts
│ │ │ ├── eval-tree-arrow.test.ts
│ │ │ ├── eval-tree-func-decl-async.test.ts
│ │ │ ├── eval-tree-func-decl.test.ts
│ │ │ ├── eval-tree-pre-post.test.ts
│ │ │ ├── eval-tree-regression.test.ts
│ │ │ ├── eval-tree.test.ts
│ │ │ ├── function-proxy.test.ts
│ │ │ ├── parser-regression.test.ts
│ │ │ ├── process-event.test.ts
│ │ │ ├── process-function.test.ts
│ │ │ ├── process-implicit-context.test.ts
│ │ │ ├── process-statement-asgn.test.ts
│ │ │ ├── process-statement-destruct.test.ts
│ │ │ ├── process-statement-regs.test.ts
│ │ │ ├── process-statement-sync.test.ts
│ │ │ ├── process-statement.test.ts
│ │ │ ├── process-switch-sync.test.ts
│ │ │ ├── process-switch.test.ts
│ │ │ ├── process-try-sync.test.ts
│ │ │ ├── process-try.test.ts
│ │ │ └── test-helpers.ts
│ │ ├── test-metadata-handler.ts
│ │ ├── theming
│ │ │ ├── border-segments.test.ts
│ │ │ ├── component-layout.resolver.test.ts
│ │ │ ├── layout-property-parser.test.ts
│ │ │ ├── layout-resolver.test.ts
│ │ │ ├── layout-resolver2.test.ts
│ │ │ ├── layout-vp-override.test.ts
│ │ │ └── padding-segments.test.ts
│ │ └── utils
│ │ ├── date-utils.test.ts
│ │ ├── format-human-elapsed-time.test.ts
│ │ └── LruCache.test.ts
│ ├── language-server
│ │ ├── completion.test.ts
│ │ ├── format.test.ts
│ │ ├── hover.test.ts
│ │ └── mockData.ts
│ └── parsers
│ ├── common
│ │ └── input-stream.test.ts
│ ├── markdown
│ │ └── parse-binding-expression.test.ts
│ ├── parameter-parser.test.ts
│ ├── paremeter-parser.test.ts
│ ├── scripting
│ │ ├── eval-tree-arrow.test.ts
│ │ ├── eval-tree-pre-post.test.ts
│ │ ├── eval-tree.test.ts
│ │ ├── function-proxy.test.ts
│ │ ├── lexer-literals.test.ts
│ │ ├── lexer-misc.test.ts
│ │ ├── module-parse.test.ts
│ │ ├── parser-arrow.test.ts
│ │ ├── parser-assignments.test.ts
│ │ ├── parser-binary.test.ts
│ │ ├── parser-destructuring.test.ts
│ │ ├── parser-errors.test.ts
│ │ ├── parser-expressions.test.ts
│ │ ├── parser-function.test.ts
│ │ ├── parser-literals.test.ts
│ │ ├── parser-primary.test.ts
│ │ ├── parser-regex.test.ts
│ │ ├── parser-statements.test.ts
│ │ ├── parser-unary.test.ts
│ │ ├── process-event.test.ts
│ │ ├── process-implicit-context.test.ts
│ │ ├── process-statement-asgn.test.ts
│ │ ├── process-statement-destruct.test.ts
│ │ ├── process-statement-regs.test.ts
│ │ ├── process-statement-sync.test.ts
│ │ ├── process-statement.test.ts
│ │ ├── process-switch-sync.test.ts
│ │ ├── process-switch.test.ts
│ │ ├── process-try-sync.test.ts
│ │ ├── process-try.test.ts
│ │ ├── simplify-expression.test.ts
│ │ ├── statement-hooks.test.ts
│ │ └── test-helpers.ts
│ ├── style-parser
│ │ ├── generateHvarChain.test.ts
│ │ ├── parseHVar.test.ts
│ │ ├── parser.test.ts
│ │ └── tokens.test.ts
│ └── xmlui
│ ├── lint.test.ts
│ ├── parser.test.ts
│ ├── scanner.test.ts
│ ├── transform.attr.test.ts
│ ├── transform.circular.test.ts
│ ├── transform.element.test.ts
│ ├── transform.errors.test.ts
│ ├── transform.escape.test.ts
│ ├── transform.regression.test.ts
│ ├── transform.script.test.ts
│ ├── transform.test.ts
│ └── xmlui.ts
├── tests-e2e
│ ├── api-bound-component-regression.spec.ts
│ ├── api-call-as-extracted-component.spec.ts
│ ├── assign-to-object-or-array-regression.spec.ts
│ ├── binding-regression.spec.ts
│ ├── children-as-template-context-vars.spec.ts
│ ├── compound-component.spec.ts
│ ├── context-vars-regression.spec.ts
│ ├── data-bindings.spec.ts
│ ├── datasource-and-api-usage-in-var.spec.ts
│ ├── datasource-direct-binding.spec.ts
│ ├── datasource-onLoaded-regression.spec.ts
│ ├── modify-array-item-regression.spec.ts
│ ├── namespaces.spec.ts
│ ├── push-to-array-regression.spec.ts
│ ├── screen-breakpoints.spec.ts
│ ├── scripting.spec.ts
│ ├── state-scope-in-pages.spec.ts
│ └── state-var-scopes.spec.ts
├── tsconfig.bin.json
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
└── vitest.config.ts
```
# Files
--------------------------------------------------------------------------------
/packages/xmlui-website-blocks/src/ScrollToTop/ScrollToTopNative.tsx:
--------------------------------------------------------------------------------
```typescript
import { forwardRef, useEffect, useState, useCallback } from "react";
import { Icon } from "xmlui";
import classnames from "classnames";
import styles from "./ScrollToTop.module.scss";
type Props = {
position?: "start" | "center" | "end";
visible?: boolean;
threshold?: number;
icon?: string;
behavior?: "smooth" | "instant" | "auto";
onClick?: () => void;
className?: string;
style?: React.CSSProperties;
};
export const defaultProps: Pick<Props, "position" | "visible" | "threshold" | "icon" | "behavior"> = {
position: "end",
visible: true,
threshold: 300,
icon: "chevronup",
behavior: "smooth",
};
export const ScrollToTop = forwardRef<HTMLButtonElement, Props>(
function ScrollToTop(
{
position = defaultProps.position,
visible = defaultProps.visible,
threshold = defaultProps.threshold,
icon = defaultProps.icon,
behavior = defaultProps.behavior,
onClick,
className,
style,
}: Props,
ref,
) {
const [isVisible, setIsVisible] = useState(false);
// Validate position and fall back to "end" if invalid
const validPosition = ["start", "center", "end"].includes(position || "") ? position : "end";
// Validate behavior and fall back to "smooth" if invalid
const validBehavior = ["smooth", "instant", "auto"].includes(behavior || "") ? behavior : "smooth";
// Check scroll position to determine visibility
useEffect(() => {
if (!visible) {
setIsVisible(false);
return;
}
const handleScroll = () => {
// Check multiple possible scroll containers using the same logic as scroll-to-top
const windowScrollTop = window.pageYOffset || document.documentElement.scrollTop;
const bodyScrollTop = document.body.scrollTop;
let maxScrollTop = Math.max(windowScrollTop, bodyScrollTop);
// Check all elements that might be scrolled (same as in handleClick)
const allElements = document.querySelectorAll('*');
allElements.forEach((element) => {
if (element instanceof HTMLElement && element.scrollTop > 0) {
maxScrollTop = Math.max(maxScrollTop, element.scrollTop);
}
});
// Also check common XMLUI containers
const xmluiContainers = document.querySelectorAll(
'.xmlui-app, .xmlui-page, .xmlui-container, [data-xmlui], main, .main, #root, .app'
);
xmluiContainers.forEach((element) => {
if (element instanceof HTMLElement) {
maxScrollTop = Math.max(maxScrollTop, element.scrollTop);
}
});
// If threshold is 0, show the button regardless of scroll position
if (threshold === 0) {
setIsVisible(true);
} else {
setIsVisible(maxScrollTop > (threshold || 0));
}
};
window.addEventListener("scroll", handleScroll);
document.addEventListener("scroll", handleScroll, true); // Capture phase for all scroll events
// Also listen to scroll events on common container elements
const xmluiContainers = document.querySelectorAll(
'.xmlui-app, .xmlui-page, .xmlui-container, [data-xmlui], main, .main, #root, .app'
);
xmluiContainers.forEach((element) => {
element.addEventListener("scroll", handleScroll);
});
handleScroll(); // Check initial position
return () => {
window.removeEventListener("scroll", handleScroll);
document.removeEventListener("scroll", handleScroll, true);
xmluiContainers.forEach((element) => {
element.removeEventListener("scroll", handleScroll);
});
};
}, [visible, threshold]);
// Scroll to top functionality
const handleClick = useCallback(() => {
// Force scroll to top using multiple methods
// This will work regardless of which container is scrolled
// Convert behavior prop to ScrollBehavior type
const scrollBehavior: ScrollBehavior = validBehavior === "instant" ? "instant" : validBehavior === "auto" ? "auto" : "smooth";
// Method 1: Standard window scroll
window.scrollTo({ top: 0, behavior: scrollBehavior });
// Method 2: Direct property setting (for instant behavior)
if (validBehavior === "instant") {
document.documentElement.scrollTop = 0;
document.body.scrollTop = 0;
}
// Method 3: Find and scroll any scrolled containers
const allElements = document.querySelectorAll('*');
allElements.forEach((element) => {
if (element instanceof HTMLElement && element.scrollTop > 0) {
if (validBehavior === "instant") {
element.scrollTop = 0;
} else {
element.scrollTo({ top: 0, behavior: scrollBehavior });
}
}
});
// Method 4: Scroll specific XMLUI containers (common patterns)
const xmluiContainers = document.querySelectorAll(
'.xmlui-app, .xmlui-page, .xmlui-container, [data-xmlui], main, .main, #root, .app'
);
xmluiContainers.forEach((element) => {
if (element instanceof HTMLElement) {
if (validBehavior === "instant") {
element.scrollTop = 0;
} else {
element.scrollTo({ top: 0, behavior: scrollBehavior });
}
}
});
onClick?.();
}, [validBehavior, onClick]);
if (!isVisible) {
return null;
}
return (
<button
ref={ref}
className={classnames(
styles.scrollToTop,
{
[styles.positionStart]: validPosition === "start",
[styles.positionCenter]: validPosition === "center",
[styles.positionEnd]: validPosition === "end",
},
className,
)}
onClick={handleClick}
style={style}
aria-label="Scroll to top"
type="button"
>
<Icon name={icon} fallback="chevronup" aria-hidden />
</button>
);
},
);
```
--------------------------------------------------------------------------------
/xmlui/tests/components-core/scripts-runner/AttributeValueParser.test.ts:
--------------------------------------------------------------------------------
```typescript
import { assert, describe, expect, it } from "vitest";
import { parseAttributeValue } from "../../../src/components-core/script-runner/AttributeValueParser";
import { Identifier, ObjectLiteral, T_IDENTIFIER } from "../../../src/components-core/script-runner/ScriptingSourceTree";
import { T_OBJECT_LITERAL } from "../../../src/parsers/scripting/ScriptingNodeTypes";
describe("Attribute value parsing", () => {
it("Empty value", () => {
// --- Act
const source = "";
const val = parseAttributeValue(source)!;
// --- Assert
expect(val.__PARSED).toBe(true);
expect(val.parseId).toBeTypeOf("number");
expect(val.segments).toHaveLength(0);
});
it("single string literal", () => {
// --- Act
const source = "hello";
const val = parseAttributeValue(source)!;
// --- Assert
expect(val.__PARSED).toBe(true);
expect(val.parseId).toBeTypeOf("number");
expect(val.segments).toHaveLength(1);
expect(val.segments![0].expr).toBeUndefined();
expect(val.segments![0].literal).toBe("hello");
});
it("single expression value", () => {
// --- Act
const source = "{myId}";
const val = parseAttributeValue(source)!;
// --- Assert
expect(val.__PARSED).toBe(true);
expect(val.segments).toHaveLength(1);
expect(val.segments![0].expr).toBeDefined();
expect(val.segments![0].literal).toBeUndefined();
expect(val.segments![0].expr.type).toBe(T_IDENTIFIER);
expect((val.segments![0].expr as Identifier).name).toBe("myId");
});
it("compound value #1", () => {
// --- Act
const source = "{myId}hello";
const val = parseAttributeValue(source)!;
// --- Assert
expect(val.__PARSED).toBe(true);
expect(val.parseId).toBeTypeOf("number");
expect(val.segments).toHaveLength(2);
expect(val.segments![0].expr).toBeDefined();
expect(val.segments![0].literal).toBeUndefined();
expect(val.segments![0].expr.type).toBe(T_IDENTIFIER);
expect((val.segments![0].expr as Identifier).name).toBe("myId");
expect(val.segments![1].expr).toBeUndefined();
expect(val.segments![1].literal).toBe("hello");
});
it("compound value #2", () => {
// --- Act
const source = "hello{myId}";
const val = parseAttributeValue(source)!;
// --- Assert
expect(val.__PARSED).toBe(true);
expect(val.parseId).toBeTypeOf("number");
expect(val.segments).toHaveLength(2);
expect(val.segments![0].expr).toBeUndefined();
expect(val.segments![0].literal).toBe("hello");
expect(val.segments![1].expr).toBeDefined();
expect(val.segments![1].literal).toBeUndefined();
expect(val.segments![1].expr.type).toBe(T_IDENTIFIER);
expect((val.segments![1].expr as Identifier).name).toBe("myId");
});
it("compound value #3", () => {
// --- Act
const source = "hello{myId}world";
const val = parseAttributeValue(source)!;
// --- Assert
expect(val.__PARSED).toBe(true);
expect(val.parseId).toBeTypeOf("number");
expect(val.segments).toHaveLength(3);
expect(val.segments![0].expr).toBeUndefined();
expect(val.segments![0].literal).toBe("hello");
expect(val.segments![1].expr).toBeDefined();
expect(val.segments![1].literal).toBeUndefined();
expect(val.segments![1].expr.type).toBe(T_IDENTIFIER);
expect((val.segments![1].expr as Identifier).name).toBe("myId");
expect(val.segments![2].expr).toBeUndefined();
expect(val.segments![2].literal).toBe("world");
});
it("value with escaped brace #1", () => {
// --- Act
const source = "\\{myId";
const val = parseAttributeValue(source)!;
// --- Assert
expect(val.__PARSED).toBe(true);
expect(val.parseId).toBeTypeOf("number");
expect(val.segments).toHaveLength(1);
expect(val.segments![0].expr).toBeUndefined();
expect(val.segments![0].literal).toBe("{myId");
});
it("value with escaped brace #2", () => {
// --- Act
const source = "\\{myId}hello";
const val = parseAttributeValue(source)!;
// --- Assert
expect(val.__PARSED).toBe(true);
expect(val.parseId).toBeTypeOf("number");
expect(val.segments).toHaveLength(1);
expect(val.segments![0].expr).toBeUndefined();
expect(val.segments![0].literal).toBe("{myId}hello");
});
it("value with escaped brace #3", () => {
// --- Act
const source = "hello\\{myId}";
const val = parseAttributeValue(source)!;
// --- Assert
expect(val.__PARSED).toBe(true);
expect(val.parseId).toBeTypeOf("number");
expect(val.segments).toHaveLength(1);
expect(val.segments![0].expr).toBeUndefined();
expect(val.segments![0].literal).toBe("hello{myId}");
});
it("value with escaped brace #4", () => {
// --- Act
const source = "hello\\{myId}{world}";
const val = parseAttributeValue(source)!;
// --- Assert
expect(val.__PARSED).toBe(true);
expect(val.parseId).toBeTypeOf("number");
expect(val.segments).toHaveLength(2);
expect(val.segments![0].expr).toBeUndefined();
expect(val.segments![0].literal).toBe("hello{myId}");
expect(val.segments![1].expr).toBeDefined();
expect(val.segments![1].literal).toBeUndefined();
expect(val.segments![1].expr.type).toBe(T_IDENTIFIER);
expect((val.segments![1].expr as Identifier).name).toBe("world");
});
it("value with unclosed brace", () => {
// --- Act
const source = "{myId";
try {
const val = parseAttributeValue(source)!;
} catch (err) {
expect(err.toString()).toContain("Unclosed");
return;
}
assert.fail("Exception expected");
});
it("object value #1", () => {
// --- Act
const source = "{{ from: from, to: to }}";
const val = parseAttributeValue(source)!;
// --- Assert
expect(val.__PARSED).toBe(true);
expect(val.parseId).toBeTypeOf("number");
expect(val.segments).toHaveLength(1);
expect(val.segments![0].expr).toBeDefined();
expect(val.segments![0].literal).toBeUndefined();
expect(val.segments![0].expr.type).toBe(T_OBJECT_LITERAL);
expect((val.segments![0].expr as ObjectLiteral).props.length).toBe(2);
});
});
```
--------------------------------------------------------------------------------
/docs/public/pages/tutorial-10.md:
--------------------------------------------------------------------------------
```markdown
# Search
The `Search` component uses [Tabs](/components/Tabs) to enable switching between two different search experiences.
```xmlui-pg display noHeader
---app display
<App>
<Search />
</App>
---comp display
<Component name="Search">
<Tabs>
<TabItem label="Find invoices issued after date">
<SearchInvoicesAfter />
</TabItem>
<TabItem label="Search clients, products, and invoices">
<SearchEverything />
</TabItem>
</Tabs>
</Component>
---comp display
<Component name="SearchInvoicesAfter">
This is SearchInvoicesAfter.
</Component>
---comp display
<Component name="SearchEverything">
This is SearchEverything.
</Component>
---desc
Try switching between the two tabs.
```
## Search invoices after date
Here is `SearchInvoicesAfter`. Try changing the date.
```xmlui-pg noHeader
---app display
<App>
<SearchInvoicesAfter />
</App>
---comp
<Component
name="StatusBadge"
var.statusColors="{{
draft: { background: '#f59e0b', label: 'white' },
sent: { background: '#3b82f6', label: 'white' },
paid: { background: '#10b981', label: 'white' }
}}"
>
<Badge
value="{$props.status}"
colorMap="{statusColors}"
variant="pill"
/>
</Component>
---comp
<Component name="SearchInvoicesAfter">
<VStack marginTop="1rem">
<DatePicker
id="dateAfter"
width="20rem"
initialValue="2025-01-01"
dateFormat="yyyy-MM-dd"
onDidChange="(val) => console.log('Date selected:', val)"
/>
<DataSource
id="invoicesAfter"
url="/resources/files/invoices.json"
when="{dateAfter.value}"
transformResult="{(data) => window.filterInvoicesAfter(data || [], dateAfter.value)}"
/>
<ChangeListener
listenTo="{dateAfter.value}"
onDidChange="invoicesAfter.reload()"
/>
<Fragment when="{invoicesAfter}">
<Card>
<VStack>
<Table data="{ invoicesAfter }">
<Column bindTo="client" header="Client" />
<Column bindTo="issue_date" header="Issue Date">
{ window.formatDate($item.issue_date) }
</Column>
<Column header="Status">
<StatusBadge status="{$item.status}" />
</Column>
<Column bindTo="total" header="Total" >
${$item.total}
</Column>
</Table>
</VStack>
</Card>
</Fragment>
</VStack>
</Component>
```
```xmlui /when/
<Component name="SearchInvoicesAfter">
<VStack paddingTop="$space-4">
<DatePicker
id="dateAfter"
width="20rem"
initialValue="2025-01-01"
dateFormat="yyyy-MM-dd"
onDidChange="(val) => console.log('Date selected:', val)"
/>
<Card when="{dateAfter.value}">
<Table data="/api/invoices/after/{dateAfter.value}">
<Column bindTo="name" header="Client"/>
<Column bindTo="issue_date" header="Issue Date">
{ window.formatDate($item.issue_date) }
</Column>
<Column header="Status">
<StatusBadge status="{$item.status}"/>
</Column>
<Column bindTo="total" header="Total">
${$item.total}
</Column>
</Table>
</Card>
</VStack>
</Component>
```
The `when` guards the [DatePicker](/components/DatePicker)'s `dateAfter`, so the `Table`
s `data` URL won't fire until its dependent variable is ready.
> [!INFO]
> You can use the `when` property on *any* XMLUI component to prevent it from rendering until some condition is true.
## Search everything
Here is `SearchEverything`. Try typing `a`, then `c`, then `m`, and watch the results converge dynamically on `Acme`.
```xmlui-pg height="400px" noHeader
---app
<App>
<SearchEverything />
</App>
---comp
<Component name="SearchEverything">
<VStack marginTop="1rem">
<TextBox
placeholder="Enter search term..."
width="25rem"
id="searchTerm"
/>
<DataSource id="clients" url="/resources/files/clients.json" />
<DataSource id="products" url="/resources/files/products.json" />
<DataSource id="allInvoices" url="/resources/files/invoices.json" />
<Fragment when="{searchTerm.value}">
<Card>
<VStack>
<Text>Found {window.filterSearchResults(clients, products, allInvoices, searchTerm.value).length} results for
"{searchTerm.value}":</Text>
<Table data="{window.filterSearchResults(clients, products, allInvoices, searchTerm.value)}">
<Column bindTo="table_name" header="Type" width="100px" />
<Column bindTo="title" header="Title" width="*" />
<Column bindTo="snippet" header="Match Details" width="3*" />
</Table>
</VStack>
</Card>
</Fragment>
</VStack>
</Component>
```
It's similar to `SearchInvoicesAfter`.
```xmlui /when=/
<Component name="SearchEverything">
<VStack paddingTop="$space-4">
<TextBox
placeholder="Enter search term..."
width="25rem"
id="searchTerm"
/>
<Card when="{searchTerm.value}">
<DataSource
id="search"
url="/api/search/{searchTerm.value}"
/>
<Text>Found {search.value ? search.value.length : 0} results for
"{searchTerm.value}":</Text>
<Table data="{search}">
<Column bindTo="table_name" header="Type" width="100px" />
<Column bindTo="title" header="Title" width="*" />
<Column bindTo="snippet" header="Match Details" width="3*" />
</Table>
</Card>
</VStack>
</Component>
```
```
--------------------------------------------------------------------------------
/docs/content/components/LineChart.md:
--------------------------------------------------------------------------------
```markdown
# LineChart [#linechart]
`LineChart` displays data as connected points over a continuous axis, ideal for showing trends, changes over time, or relationships between variables. Use it time series data, progress tracking, and comparing multiple data series on the same scale.
The LineChart component accommodates the size of its parent unless you set it explicitly:
```xmlui-pg copy display height="300px" name="Example: dimension determined by the parent" /Card height="240px" width="75%"/
<Card height="240px" width="75%">
<LineChart
data="{[
{ 'sprint': 'Sprint 1', 'A': 44 },
{ 'sprint': 'Sprint 2', 'A': 32 },
{ 'sprint': 'Sprint 3', 'A': 48 },
{ 'sprint': 'Sprint 4', 'A': 72 }
]}"
yKeys="{['A']}"
xKey="sprint"
/>
</Card>
```
```xmlui-pg copy display height="300px" name="Example: dimension overwritten by LineChart" /height="240px"/ /height="200px"/
<Card height="240px">
<LineChart
height="200px"
data="{[
{ 'sprint': 'Sprint 1', 'A': 44 },
{ 'sprint': 'Sprint 2', 'A': 32 },
{ 'sprint': 'Sprint 3', 'A': 48 },
{ 'sprint': 'Sprint 4', 'A': 72 }
]}"
yKeys="{['A']}"
xKey="sprint"
/>
</Card>
```
**Key features:**
- **Flexible orientation**: Choose horizontal or vertical bar layouts
- **Multiple data series**: Display several metrics on the same chart with different colored bars
- **Stacked vs grouped**: Stack bars on top of each other or place them side by side
- **Custom formatting**: Use `tickFormatter` to format axis labels and [`LabelList`](/components/LabelList) for data labels
## Properties [#properties]
### `data` [#data]
The data to be displayed in the line chart.It needs to be an array of objects, where each object represents a data point.
### `hideTickX` (default: false) [#hidetickx-default-false]
Determines whether the X-axis ticks should be hidden. If set to (`true`), the ticks will not be displayed.
### `hideTickY` (default: false) [#hideticky-default-false]
Determines whether the Y-axis ticks should be hidden. If set to (`true`), the ticks will not be displayed.
### `hideTooltip` (default: false) [#hidetooltip-default-false]
Determines whether the tooltip should be hidden.If set to (`true`), no tooltip will be shown when hovering over data points.
### `hideX` (default: false) [#hidex-default-false]
Determines whether the X-axis should be hidden. If set to (`true`), the axis will not be displayed.
### `hideY` (default: false) [#hidey-default-false]
Determines whether the Y-axis should be hidden. If set to (`true`), the axis will not be displayed.
### `marginBottom` [#marginbottom]
The bottom margin of the chart
### `marginLeft` [#marginleft]
The left margin of the chart
### `marginRight` [#marginright]
The right margin of the chart
### `marginTop` [#margintop]
The top margin of the chart
### `showLegend` (default: false) [#showlegend-default-false]
Determines whether the legend should be displayed.
### `tickFormatterX` [#tickformatterx]
A function that formats the X-axis tick labels. It receives a tick value and returns a formatted string.
```xmlui-pg copy display height="320px" name="Example: tickFormatterX" /tickFormatterX/
<App>
<LineChart
height="240px"
data="{[
{ 'sprint': 'Sprint 1', 'A': 44 },
{ 'sprint': 'Sprint 2', 'A': 32 },
{ 'sprint': 'Sprint 3', 'A': 48 },
{ 'sprint': 'Sprint 4', 'A': 72 }
]}"
yKeys="{['A']}"
xKey="sprint"
tickFormatterX="{(value) => '(' + value + ')'}"
/>
</App>
```
### `tickFormatterY` [#tickformattery]
A function that formats the Y-axis tick labels. It receives a tick value and returns a formatted string.
```xmlui-pg copy display height="320px" name="Example: tickFormatterY" /tickFormatterY/
<App>
<LineChart
height="240px"
data="{[
{ 'sprint': 'Sprint 1', 'A': 44 },
{ 'sprint': 'Sprint 2', 'A': 32 },
{ 'sprint': 'Sprint 3', 'A': 48 },
{ 'sprint': 'Sprint 4', 'A': 72 }
]}"
yKeys="{['A']}"
xKey="sprint"
tickFormatterY="{(value) => '$' + value}"
/>
</App>
```
### `tooltipTemplate` [#tooltiptemplate]
This property allows replacing the default template to display a tooltip.
```xmlui-pg copy display height="320px" name="Example: tooltipTemplate" /tooltipTemplate/
<App>
<LineChart
height="240px"
data="{[
{ 'sprint': 'Sprint 1', 'A': 44, 'B': 28 },
{ 'sprint': 'Sprint 2', 'A': 32, 'B': 41 },
{ 'sprint': 'Sprint 3', 'A': 48, 'B': 35 },
{ 'sprint': 'Sprint 4', 'A': 72, 'B': 58 }
]}"
yKeys="{['A', 'B']}"
xKey="sprint"
>
<property name="tooltipTemplate">
<VStack backgroundColor='white' padding="$space-2">
<Text fontWeight='bold'>{$tooltip.label}</Text>
<Items data="{$tooltip.payload}">
<HStack gap="$space-2" verticalAlignment="center">
<Stack
width="8px"
height="8px"
backgroundColor="{$item.color}" />
<Text>{$item.label}: {$item.value}</Text>
</HStack>
</Items>
</VStack>
</property>
</LineChart>
</App>
```
The `tooltipTemplate` prop allows you to customize the appearance and content of chart tooltips. The template receives a `$tooltip` context variable containing:
- `$tooltip.label`: The label for the data point (typically the yKey value)
- `$tooltip.payload`: An object containing all data values for the hovered point
- `$tooltip.active`: Boolean indicating if the tooltip is currently active
### `xKey` [#xkey]
The key in the data objects used for labeling different data series.
### `yKeys` [#ykeys]
This property specifies the keys in the data objects that should be used for rendering the lines.
## Events [#events]
This component does not have any events.
## Exposed Methods [#exposed-methods]
This component does not expose any methods.
## Styling [#styling]
### Theme Variables [#theme-variables]
| Variable | Default Value (Light) | Default Value (Dark) |
| --- | --- | --- |
| [width](../styles-and-themes/common-units/#size)-line-LineChart | 1px | 1px |
```
--------------------------------------------------------------------------------
/xmlui/src/components/Carousel/Carousel.tsx:
--------------------------------------------------------------------------------
```typescript
import styles from "./Carousel.module.scss";
import { createComponentRenderer } from "../../components-core/renderers";
import { parseScssVar } from "../../components-core/theming/themeVars";
import { createMetadata, d, dDidChange } from "../metadata-helpers";
import { CarouselComponent, defaultProps } from "./CarouselNative";
import { orientationOptionMd } from "../abstractions";
const COMP = "Carousel";
export const CarouselMd = createMetadata({
status: "stable",
description:
`This component displays a slideshow by cycling through elements (images, text, or ` +
`custom slides) like a carousel.`,
props: {
orientation: {
description:
"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.",
availableValues: orientationOptionMd,
valueType: "string",
defaultValue: defaultProps.orientation,
},
indicators: {
description: "Display the individual slides as buttons (`true`) or not (`false`).",
valueType: "boolean",
defaultValue: defaultProps.indicators,
},
controls: {
description: "Display the previous/next controls (`true`) or not (`false`).",
valueType: "boolean",
defaultValue: defaultProps.controls,
},
autoplay: {
description: "Start scrolling the carousel automatically (`true`) or not (`false`).",
valueType: "boolean",
defaultValue: defaultProps.autoplay,
},
loop: {
description: "Sets whether the carousel should loop back to the start/end when it reaches the last/first slide.",
valueType: "boolean",
defaultValue: defaultProps.loop,
},
startIndex: {
description: "The index of the first slide to display.",
valueType: "number",
defaultValue: defaultProps.startIndex,
},
transitionDuration: {
description: "The duration of the transition between slides.",
valueType: "number",
defaultValue: defaultProps.transitionDuration,
},
autoplayInterval: {
description: "Specifies the interval between autoplay transitions.",
valueType: "number",
defaultValue: defaultProps.autoplayInterval,
},
stopAutoplayOnInteraction: {
description: "This property indicates whether autoplay stops on user interaction.",
valueType: "boolean",
defaultValue: defaultProps.stopAutoplayOnInteraction,
},
prevIcon: {
description: "The icon to display for the previous control.",
valueType: "string",
},
nextIcon: {
description: "The icon to display for the next control.",
valueType: "string",
},
},
events: {
displayDidChange: dDidChange(COMP),
},
apis: {
canScrollPrev: {
description: `This method returns \`true\` if the carousel can scroll to the previous slide.`,
signature: "canScrollPrev(): boolean",
},
canScrollNext: {
description: `This method returns \`true\` if the carousel can scroll to the next slide.`,
signature: "canScrollNext(): boolean",
},
scrollTo: {
description: `This method scrolls the carousel to the specified slide index.`,
signature: "scrollTo(index: number): void",
parameters: {
index: "The index of the slide to scroll to.",
},
},
scrollPrev: {
signature: "scrollPrev(): void",
description: "This method scrolls the carousel to the previous slide.",
},
scrollNext: {
signature: "scrollNext(): void",
description: "This method scrolls the carousel to the next slide.",
},
},
themeVars: parseScssVar(styles.themeVars),
defaultThemeVars: {
[`backgroundColor-control-${COMP}`]: "$color-primary",
[`textColor-control-${COMP}`]: "$textColor",
[`backgroundColor-control-hover-${COMP}`]: "$color-primary",
[`textColor-control-hover-${COMP}`]: "$textColor",
[`backgroundColor-control-active-${COMP}`]: "$color-primary",
[`backgroundColor-control-disabled-${COMP}`]: "$color-surface-200",
[`textColor-control-disabled-${COMP}`]: "$textColor-disabled",
[`textColor-control-active-${COMP}`]: "$color-primary",
[`backgroundColor-indicator-${COMP}`]: "$color-surface-200",
[`backgroundColor-indicator-active-${COMP}`]: "$color-primary",
[`textColor-indicator-${COMP}`]: "$color-primary",
[`textColor-indicator-active-${COMP}`]: "$color-primary",
[`backgroundColor-indicator-hover-${COMP}`]: "$color-surface-200",
[`textColor-indicator-hover-${COMP}`]: "$color-primary",
[`width-indicator-${COMP}`]: "25px",
[`height-indicator-${COMP}`]: "6px",
[`height-control-${COMP}`]: "36px",
[`width-control-${COMP}`]: "36px",
[`borderRadius-control-${COMP}`]: "50%",
[`height-${COMP}`]: "100%",
[`width-${COMP}`]: "100%",
},
});
export const carouselComponentRenderer = createComponentRenderer(
COMP,
CarouselMd,
({ node, renderChild, className, extractValue, lookupEventHandler, registerComponentApi }) => {
return (
<CarouselComponent
className={className}
stopAutoplayOnInteraction={extractValue.asOptionalBoolean(
node.props?.stopAutoplayOnInteraction,
)}
autoplayInterval={extractValue.asOptionalNumber(node.props?.autoplayInterval)}
transitionDuration={extractValue.asOptionalNumber(node.props?.transitionDuration)}
indicators={extractValue.asOptionalBoolean(node.props?.indicators)}
controls={extractValue.asOptionalBoolean(node.props?.controls)}
orientation={extractValue(node.props?.orientation)}
onDisplayDidChange={lookupEventHandler("displayDidChange")}
autoplay={extractValue.asOptionalBoolean(node.props?.autoplay)}
registerComponentApi={registerComponentApi}
loop={extractValue.asOptionalBoolean(node.props?.loop)}
startIndex={extractValue.asOptionalNumber(node.props?.startIndex)}
prevIcon={extractValue(node.props?.prevIcon)}
nextIcon={extractValue(node.props?.nextIcon)}
>
{renderChild(node.children)}
</CarouselComponent>
);
},
);
```
--------------------------------------------------------------------------------
/packages/xmlui-website-blocks/src/Carousel/Carousel.tsx:
--------------------------------------------------------------------------------
```typescript
import styles from "./Carousel.module.scss";
import { createComponentRenderer, parseScssVar, createMetadata, d } from "xmlui";
const dDidChange = (componentName: string) => d(`This event is fired when the displayed content of the ${componentName} changes.`);
const orientationOptionMd = ["horizontal", "vertical"];
import { CarouselComponent, defaultProps } from "./CarouselNative";
const COMP = "CarouselNew";
export const CarouselMd = createMetadata({
status: "stable",
description:
`This component displays a slideshow by cycling through elements (images, text, or ` +
`custom slides) like a carousel.`,
props: {
orientation: {
description:
"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.",
availableValues: orientationOptionMd,
valueType: "string",
defaultValue: defaultProps.orientation,
},
indicators: {
description: "Display the individual slides as buttons (`true`) or not (`false`).",
valueType: "boolean",
defaultValue: defaultProps.indicators,
},
controls: {
description: "Display the previous/next controls (`true`) or not (`false`).",
valueType: "boolean",
defaultValue: defaultProps.controls,
},
autoplay: {
description: "Start scrolling the carousel automatically (`true`) or not (`false`).",
valueType: "boolean",
defaultValue: defaultProps.autoplay,
},
loop: {
description: "Sets whether the carousel should loop back to the start/end when it reaches the last/first slide.",
valueType: "boolean",
defaultValue: defaultProps.loop,
},
startIndex: {
description: "The index of the first slide to display.",
valueType: "number",
defaultValue: defaultProps.startIndex,
},
transitionDuration: {
description: "The duration of the transition between slides.",
valueType: "number",
defaultValue: defaultProps.transitionDuration,
},
autoplayInterval: {
description: "Specifies the interval between autoplay transitions.",
valueType: "number",
defaultValue: defaultProps.autoplayInterval,
},
stopAutoplayOnInteraction: {
description: "This property indicates whether autoplay stops on user interaction.",
valueType: "boolean",
defaultValue: defaultProps.stopAutoplayOnInteraction,
},
prevIcon: {
description: "The icon to display for the previous control.",
valueType: "string",
},
nextIcon: {
description: "The icon to display for the next control.",
valueType: "string",
},
},
events: {
displayDidChange: dDidChange(COMP),
},
apis: {
canScrollPrev: {
description: `This method returns \`true\` if the carousel can scroll to the previous slide.`,
signature: "canScrollPrev(): boolean",
},
canScrollNext: {
description: `This method returns \`true\` if the carousel can scroll to the next slide.`,
signature: "canScrollNext(): boolean",
},
scrollTo: {
description: `This method scrolls the carousel to the specified slide index.`,
signature: "scrollTo(index: number): void",
parameters: {
index: "The index of the slide to scroll to.",
},
},
scrollPrev: {
signature: "scrollPrev(): void",
description: "This method scrolls the carousel to the previous slide.",
},
scrollNext: {
signature: "scrollNext(): void",
description: "This method scrolls the carousel to the next slide.",
},
},
themeVars: parseScssVar(styles.themeVars),
defaultThemeVars: {
[`backgroundColor-control-${COMP}`]: "$color-primary",
[`textColor-control-${COMP}`]: "$textColor",
[`backgroundColor-control-hover-${COMP}`]: "$color-primary",
[`textColor-control-hover-${COMP}`]: "$textColor",
[`backgroundColor-control-active-${COMP}`]: "$color-primary",
[`backgroundColor-control-disabled-${COMP}`]: "$color-surface-200",
[`textColor-control-disabled-${COMP}`]: "$textColor-disabled",
[`textColor-control-active-${COMP}`]: "$color-primary",
[`backgroundColor-indicator-${COMP}`]: "$color-surface-200",
[`backgroundColor-indicator-active-${COMP}`]: "$color-primary",
[`textColor-indicator-${COMP}`]: "$color-primary",
[`textColor-indicator-active-${COMP}`]: "$color-primary",
[`backgroundColor-indicator-hover-${COMP}`]: "$color-surface-200",
[`textColor-indicator-hover-${COMP}`]: "$color-primary",
[`width-indicator-${COMP}`]: "25px",
[`height-indicator-${COMP}`]: "6px",
[`height-control-${COMP}`]: "36px",
[`width-control-${COMP}`]: "36px",
[`borderRadius-control-${COMP}`]: "50%",
[`height-${COMP}`]: "100%",
[`width-${COMP}`]: "100%",
},
} as const);
export const carouselComponentRenderer = createComponentRenderer(
COMP,
CarouselMd,
({ node, renderChild, className, extractValue, lookupEventHandler, registerComponentApi }) => {
const props = (node.props as typeof CarouselMd.props)!;
return (
<CarouselComponent
className={className}
stopAutoplayOnInteraction={extractValue.asOptionalBoolean(
props.stopAutoplayOnInteraction,
)}
autoplayInterval={extractValue.asOptionalNumber(props.autoplayInterval)}
transitionDuration={extractValue.asOptionalNumber(props.transitionDuration)}
indicators={extractValue.asOptionalBoolean(props.indicators)}
controls={extractValue.asOptionalBoolean(props.controls)}
orientation={extractValue(props.orientation)}
onDisplayDidChange={lookupEventHandler("displayDidChange")}
autoplay={extractValue.asOptionalBoolean(props.autoplay)}
registerComponentApi={registerComponentApi}
loop={extractValue.asOptionalBoolean(props.loop)}
startIndex={extractValue.asOptionalNumber(props.startIndex)}
prevIcon={extractValue(props.prevIcon)}
nextIcon={extractValue(props.nextIcon)}
>
{renderChild(node.children)}
</CarouselComponent>
);
},
);
```
--------------------------------------------------------------------------------
/xmlui/src/components/FlowLayout/FlowLayoutNative.tsx:
--------------------------------------------------------------------------------
```typescript
import {
type CSSProperties,
type Dispatch,
type ForwardedRef,
forwardRef,
type ReactNode,
type SetStateAction,
createContext,
useContext,
useMemo,
useState,
} from "react";
import classnames from "classnames";
import { noop } from "lodash-es";
import styles from "./FlowLayout.module.scss";
import { useTheme } from "../../components-core/theming/ThemeContext";
import { normalizeCssValueForCalc, getSizeString } from "../../components-core/utils/css-utils";
import { useIsomorphicLayoutEffect, useMediaQuery } from "../../components-core/utils/hooks";
import { resolveLayoutProps } from "../../components-core/theming/layout-resolver";
import { useAppContext } from "../../components-core/AppContext";
type FlowItemProps = {
children: ReactNode;
width?: string | number;
minWidth?: string | number;
maxWidth?: string | number;
forceBreak?: boolean;
};
const resolvedCssVars: Record<string, any> = {};
interface IFlowLayoutContext {
rowGap: string | number;
columnGap: string | number;
setNumberOfChildren: Dispatch<SetStateAction<number>>;
}
const FlowLayoutContext = createContext<IFlowLayoutContext>({
rowGap: 0,
columnGap: 0,
setNumberOfChildren: noop,
});
export const FlowItemBreak = ({ force }: { force?: boolean }) => (
<div className={classnames(styles.break, { [styles.forceBreak]: force })} />
);
export const FlowItemWrapper = forwardRef(function FlowItemWrapper(
{ children, forceBreak, ...restProps }: FlowItemProps,
ref: any,
) {
const { rowGap, columnGap, setNumberOfChildren } = useContext(FlowLayoutContext);
const { mediaSize } = useAppContext();
useIsomorphicLayoutEffect(() => {
setNumberOfChildren((prev) => prev + 1);
return () => {
setNumberOfChildren((prev) => prev - 1);
};
}, [setNumberOfChildren]);
const { root } = useTheme();
const _width = restProps.width || "100%";
const _minWidth = restProps.minWidth || undefined;
const _maxWidth = restProps.maxWidth || undefined;
const {
width = _width,
minWidth,
maxWidth,
flex,
} = useMemo(() => {
return (
// --- New layout resolution
resolveLayoutProps(
{ width: _width, maxWidth: _maxWidth, minWidth: _minWidth },
{
type: "Stack",
orientation: "horizontal",
},
).cssProps || {}
// --- Old layout resolution
// compileLayout(
// { width: _width, maxWidth: _maxWidth, minWidth: _minWidth },
// activeTheme.themeVars,
// {
// type: "Stack",
// orientation: "horizontal",
// },
// ).cssProps || {}
);
}, [_maxWidth, _minWidth, _width]);
const resolvedWidth = useMemo(() => {
if (width && typeof width === "string" && width.startsWith("var(")) {
if (!resolvedCssVars[width]) {
const varName = width.substring(4, width.length - 1);
const resolved = getComputedStyle(root || document.body).getPropertyValue(varName);
resolvedCssVars[width] = resolved || _width;
}
return resolvedCssVars[width];
}
return width || _width;
}, [_width, root, width]);
const isWidthPercentage = typeof resolvedWidth === "string" && resolvedWidth.endsWith("%");
const _columnGap = normalizeCssValueForCalc(columnGap);
let responsiveWidth;
if(isWidthPercentage){
const percNumber = parseFloat(resolvedWidth);
if(mediaSize.sizeIndex <= 1){
let percentage = percNumber * 4;
if(percentage > 50){
responsiveWidth = `100%`
} else {
responsiveWidth = `min(${percentage}%, 100%)`
}
} else if(mediaSize.sizeIndex <= 2){
let percentage = percNumber * 3;
if(percentage >= 50 && percentage <= 75){
responsiveWidth = `50%`
} else if(percentage > 75){
responsiveWidth = `100%`
} else {
responsiveWidth = `min(${percentage}%, 100%)`
}
} else {
responsiveWidth = `min(${width}, 100%)`;
}
} else {
responsiveWidth = `min(calc(${width} + ${_columnGap}), 100%)`;
}
const outerWrapperStyle: CSSProperties = {
minWidth,
maxWidth,
width: responsiveWidth,
paddingBottom: rowGap,
flex,
};
const isStarSizing = flex !== undefined;
if (isStarSizing) {
//star sizing
outerWrapperStyle.width = "100%";
outerWrapperStyle.minWidth = minWidth || "1px";
}
return (
<>
<div
style={{ ...outerWrapperStyle, paddingRight: _columnGap }}
className={classnames(styles.flowItem, {
[styles.starSized]: isStarSizing,
})}
ref={ref}
>
{children}
</div>
{isStarSizing && <FlowItemBreak />}
</>
);
});
type FlowLayoutProps = {
style?: CSSProperties;
className?: string;
columnGap: string | number;
rowGap: string | number;
children: ReactNode;
};
export const defaultProps: Pick<FlowLayoutProps, "columnGap" | "rowGap"> = {
columnGap: "$gap-normal",
rowGap: "$gap-normal",
};
export const FlowLayout = forwardRef(function FlowLayout(
{ style, className, columnGap = 0, rowGap = 0, children, ...rest }: FlowLayoutProps,
forwardedRef: ForwardedRef<HTMLDivElement>,
) {
const [numberOfChildren, setNumberOfChildren] = useState(0);
const safeColumnGap = numberOfChildren === 1 ? 0 : columnGap;
// --- Be smart about rowGap
const _rowGap = getSizeString(rowGap);
const _columnGap = getSizeString(safeColumnGap);
const innerStyle = useMemo(
() => ({
// We put a negative margin on the container to fill the space for the row's last columnGap
marginRight: `calc(-1 * ${_columnGap})`,
marginBottom: `calc(-1 * ${_rowGap})`,
}),
[_columnGap, _rowGap],
);
const flowLayoutContextValue = useMemo(() => {
return {
rowGap: _rowGap,
columnGap: _columnGap,
setNumberOfChildren,
};
}, [_columnGap, _rowGap]);
return (
<FlowLayoutContext.Provider value={flowLayoutContextValue}>
<div style={style} className={className} ref={forwardedRef} {...rest}>
<div className={styles.outer}>
<div className={classnames(styles.flowContainer, styles.horizontal)} style={innerStyle}>
{children}
</div>
</div>
</div>
</FlowLayoutContext.Provider>
);
});
```
--------------------------------------------------------------------------------
/packages/xmlui-devtools/src/devtools/ModalDialog.tsx:
--------------------------------------------------------------------------------
```typescript
import React, { type CSSProperties, type ReactNode, useEffect, useRef, useState } from "react";
import { composeRefs } from "@radix-ui/react-compose-refs";
import classnames from "classnames";
import * as Dialog from "@radix-ui/react-dialog";
import styles from "./ModalDialog.module.scss";
import { Button, Icon, useTheme } from "xmlui";
import { motion, AnimatePresence } from "framer-motion";
import { Tooltip } from "./Tooltip";
// =====================================================================================================================
// React component definition
type ModalProps = {
style?: CSSProperties;
className?: string;
children?: ReactNode;
isOpen: boolean;
setIsOpen: (isOpen: boolean) => void;
popupPlayground: () => void;
clickPosition: { x: number; y: number };
};
const overlayVariants = {
visible: { opacity: 1 },
hidden: { opacity: 0 },
};
const contentVariants = {
initial: (custom: { x: number; y: number }) => ({
opacity: 0,
scale: 0.2,
x: custom.x - window.innerWidth / 2,
y: custom.y - window.innerHeight / 2,
}),
animate: {
opacity: 1,
scale: 1,
x: 0,
y: 0,
},
exit: {
opacity: 0,
scale: 0.2,
transition: { duration: 0.2 },
},
};
function durationToSeconds(durationString?: string) {
if (!durationString) {
return undefined;
}
const trimmedString = durationString.trim();
if (trimmedString.endsWith("ms")) {
const milliseconds = parseFloat(trimmedString);
return milliseconds / 1000;
} else if (trimmedString.endsWith("s")) {
return parseFloat(trimmedString);
} else {
return parseFloat(trimmedString);
}
}
export const ModalDialog = React.forwardRef(
({ children, style, isOpen, setIsOpen, popupPlayground, clickPosition }: ModalProps, ref) => {
const { root, getThemeVar } = useTheme();
const modalRef = useRef<HTMLDivElement>(null);
const composedRef = ref ? composeRefs(ref, modalRef) : modalRef;
const [rendered, setRendered] = useState(true);
useEffect(() => {
if (isOpen) {
modalRef.current?.focus();
}
}, [isOpen]);
// https://github.com/radix-ui/primitives/issues/2122#issuecomment-2140827998
useEffect(() => {
if (isOpen) {
// Pushing the change to the end of the call stack
const timer = setTimeout(() => {
document.body.style.pointerEvents = "";
}, 0);
return () => clearTimeout(timer);
} else {
document.body.style.pointerEvents = "auto";
}
}, [isOpen]);
if (!root) {
return null;
}
const onExitComplete = () => {
setIsOpen(false);
};
return (
<Dialog.Root defaultOpen={false} open={isOpen} onOpenChange={setRendered}>
<Dialog.Portal container={root}>
<AnimatePresence onExitComplete={onExitComplete}>
{rendered && (
<Dialog.Overlay className={styles.overlay} forceMount>
<motion.div
key="overlay"
className={styles.overlayBg}
variants={overlayVariants}
initial="hidden"
animate="visible"
exit="hidden"
transition={{
duration: 0.2,
ease: [0.16, 1, 0.3, 1],
}}
/>
<motion.div
className={styles.contentWrapper}
variants={contentVariants}
custom={{ x: clickPosition.x, y: clickPosition.y }}
initial="initial"
animate="animate"
exit="exit"
transition={{
duration:
durationToSeconds(getThemeVar("duration-startAnimation-ModalDialog")) || 0.8,
ease: [0.16, 1, 0.3, 1],
}}
>
<Dialog.Content
className={classnames(styles.content)}
forceMount
onPointerDownOutside={(event) => {
if (
event.target instanceof Element &&
event.target.closest("._debug-inspect-button") !== null
) {
//we prevent the auto modal close on clicking the inspect button
event.preventDefault();
}
}}
ref={composedRef}
style={{ ...style, gap: undefined }}
>
<Dialog.Title style={{ marginTop: 0 }}>
<header id="dialogTitle" className={styles.dialogTitle}>
<div className={styles.header}>
<Button variant={"ghost"} size={"sm"}>
Code
</Button>
<div className={styles.actions}>
<Tooltip label={"View and edit in new full-width window"}>
<Button
onClick={popupPlayground}
size={"xs"}
variant={"ghost"}
icon={<Icon name={"new-window"} />}
/>
</Tooltip>
<Tooltip label={"Close DevTools"}>
<Button
onClick={() => setRendered(false)}
size={"xs"}
variant={"ghost"}
icon={<Icon name={"close"} />}
/>
</Tooltip>
</div>
</div>
</header>
</Dialog.Title>
<div className={styles.innerContent} style={{ gap: style?.gap }}>
{children}
</div>
</Dialog.Content>
</motion.div>
</Dialog.Overlay>
)}
</AnimatePresence>
</Dialog.Portal>
</Dialog.Root>
);
},
);
ModalDialog.displayName = "ModalDialog";
```
--------------------------------------------------------------------------------
/xmlui/src/components-core/rendering/ContainerWrapper.tsx:
--------------------------------------------------------------------------------
```typescript
import {
forwardRef,
memo,
type MutableRefObject,
type ReactNode,
type RefObject,
useMemo,
} from "react";
import type { ComponentDef, ParentRenderContext } from "../../abstractions/ComponentDefs";
import type { LayoutContext } from "../../abstractions/RendererDefs";
import type { ContainerDispatcher } from "../abstractions/ComponentRenderer";
import type { ProxyAction } from "../rendering/buildProxy";
import { ErrorBoundary } from "../rendering/ErrorBoundary";
import { StateContainer } from "./StateContainer";
/**
* This type is the internal representation of a container component, which manages the state of its children.
*/
export interface ContainerWrapperDef extends ComponentDef {
type: "Container";
// --- The unique identifier of the container
containerUid?: symbol;
// --- The context values this container provides to its children
contextVars?: Record<string, any>;
// --- If true, this is an API-bound container
apiBoundContainer?: boolean;
}
/**
* We store the state application state in a hierarchical structure of
* containers. This type represents the state within a single container
* stored as key and value pairs.
*/
export type ContainerState = Record<string | symbol, any>;
/**
* Components can provide an API that other components can invoke (using
* the host component ID). This type defines the shape of a hash object that
* stores the API endpoints.
*/
export type ComponentApi = Record<string, ((...args: any[]) => any) | boolean | number | string>;
/**
* This type declares that function's signature, which registers an exposed
* component method (API endpoint).
*/
export type RegisterComponentApiFnInner = (componentUid: symbol, api: ComponentApi) => void;
/**
* This type declares that function's signature, which runs a clean-up activity.
*/
export type ComponentCleanupFn = (uid: symbol) => void;
/**
* This function checks if a particular component needs a wrapping container to
* manage its internal state, which is closed from its external context but
* available to its children.
* @param node The component definition node to check
* @returns Tru, if the component needs a wrapping container
*/
export function isContainerLike(node: ComponentDef) {
if (node.type === "Container") {
return true;
}
// --- If any of the following properties have a value, we need a container
return !!(
node.loaders ||
node.vars ||
node.uses ||
node.contextVars ||
node.functions ||
node.scriptCollected
);
}
/**
* This type is that function's signature, which signs that an entire
* state variable or its nested part has been changed.
*/
export type StatePartChangedFn = (
// --- String representing the component path ("a.b", "a.b[3].c", etc.)
path: string[],
// --- The new value of the changed part
value: any,
// --- The target component that has changed (along the path)
target: string,
// --- Type of change
action: ProxyAction,
) => void;
// --- Properties of the ComponentContainer component
type Props = {
node: ContainerWrapperDef;
resolvedKey?: string;
parentState: ContainerState;
parentStatePartChanged: StatePartChangedFn;
parentRegisterComponentApi: RegisterComponentApiFnInner;
parentDispatch: ContainerDispatcher;
parentRenderContext?: ParentRenderContext;
layoutContextRef: MutableRefObject<LayoutContext | undefined>;
uidInfoRef?: RefObject<Record<string, any>>;
children?: ReactNode;
};
/**
* This component is a container that manages the state of its children. It
* provides a context for the children to access the state and the API of the
* parent component.
*/
export const ContainerWrapper = memo(
forwardRef(function ContainerWrapper(
{
node,
resolvedKey,
parentState,
parentStatePartChanged,
parentRegisterComponentApi,
parentDispatch,
parentRenderContext,
layoutContextRef,
uidInfoRef,
children,
...rest
}: Props,
ref,
) {
// --- Make sure the component node is wrapped with a container
const containerizedNode = useMemo(() => getWrappedWithContainer(node), [node]);
return (
<ErrorBoundary node={node} location={"container"}>
<StateContainer
node={containerizedNode as any}
resolvedKey={resolvedKey}
parentState={parentState}
parentStatePartChanged={parentStatePartChanged}
parentRegisterComponentApi={parentRegisterComponentApi}
parentDispatch={parentDispatch}
parentRenderContext={parentRenderContext}
layoutContextRef={layoutContextRef}
uidInfoRef={uidInfoRef}
isImplicit={node.type !== "Container" && containerizedNode.uses === undefined} //in this case it's an auto-wrapped component
ref={ref}
{...rest}
>
{children}
</StateContainer>
</ErrorBoundary>
);
}),
);
/**
* Wraps the specified component node with a container
* @param node The component node to wrap
* @returns A "Container" node
*/
const getWrappedWithContainer = (node: ContainerWrapperDef) => {
if (node.type === "Container") {
// --- Already wrapped
return node;
}
// --- Clone the node and remove the properties that will be moved to the container
// --- Note: we need the "when" property in the ModalDialog component, so we don't remove it
const wrappedNode = { ...node, props: { ...node.props } };
delete wrappedNode.loaders;
delete wrappedNode.vars;
delete wrappedNode.functions;
delete wrappedNode.script;
delete wrappedNode.scriptCollected;
delete wrappedNode.scriptError;
delete wrappedNode.uses;
delete (wrappedNode.props as any)?.uses;
delete wrappedNode.api;
delete wrappedNode.contextVars;
// --- Do the wrapping
return {
type: "Container",
uid: node.uid,
when: node.when,
loaders: node.loaders,
vars: node.vars,
functions: node.functions,
scriptCollected: node.scriptCollected,
scriptError: node.scriptError,
uses: node.uses,
api: node.api,
containerUid: node?.containerUid,
apiBoundContainer: node?.apiBoundContainer,
contextVars: node.contextVars,
props: {
debug: (node.props as any)?.debug,
},
children: [wrappedNode],
} as ContainerWrapperDef;
};
```
--------------------------------------------------------------------------------
/xmlui/src/language-server/server-common.ts:
--------------------------------------------------------------------------------
```typescript
import type {
Connection,
InitializeParams,
TextDocumentPositionParams,
InitializeResult,
HoverParams,
TextDocumentContentChangeEvent,
TextDocumentChangeEvent,
Diagnostic,
Position,
DocumentFormattingParams,
} from "vscode-languageserver";
import {
MarkupKind,
TextDocumentSyncKind,
DidChangeConfigurationNotification,
TextDocuments,
} from "vscode-languageserver";
import { TextDocument } from "vscode-languageserver-textdocument";
import collectedComponentMetadata from "./xmlui-metadata-generated.js";
import type { XmluiCompletionItem } from "./services/completion";
import { handleCompletion, handleCompletionResolve } from "./services/completion";
import { handleHover } from "./services/hover";
import { handleDocumentFormatting } from "./services/format";
import {
createXmlUiParser,
Error,
type GetText,
type ParseResult,
} from "../parsers/xmlui-parser/parser";
import {
MetadataProvider,
type ComponentMetadataCollection,
} from "./services/common/metadata-utils";
import { getDiagnostics } from "./services/diagnostic";
const metaByComp = collectedComponentMetadata as ComponentMetadataCollection;
const metadataProvider = new MetadataProvider(metaByComp);
export function start(connection: Connection) {
// Also include all preview / proposed LSP features.
// Create a simple text document manager.
const documents = new TextDocuments(TextDocument);
let hasConfigurationCapability = false;
let hasWorkspaceFolderCapability = false;
let hasDiagnosticRelatedInformationCapability = false;
connection.onInitialize((params: InitializeParams) => {
connection.console.log("Initializing!");
const capabilities = params.capabilities;
// Does the client support the `workspace/configuration` request?
// If not, we fall back using global settings.
hasConfigurationCapability = !!(
capabilities.workspace && !!capabilities.workspace.configuration
);
hasWorkspaceFolderCapability = !!(
capabilities.workspace && !!capabilities.workspace.workspaceFolders
);
hasDiagnosticRelatedInformationCapability = !!(
capabilities.textDocument &&
capabilities.textDocument.publishDiagnostics &&
capabilities.textDocument.publishDiagnostics.relatedInformation
);
const result: InitializeResult = {
capabilities: {
textDocumentSync: TextDocumentSyncKind.Incremental,
completionProvider: {
resolveProvider: true,
triggerCharacters: ["<", "/"],
},
hoverProvider: true,
documentFormattingProvider: true,
},
};
if (hasWorkspaceFolderCapability) {
result.capabilities.workspace = {
workspaceFolders: {
supported: true,
},
};
}
return result;
});
connection.onInitialized(() => {
if (hasConfigurationCapability) {
// Register for all configuration changes.
void connection.client.register(DidChangeConfigurationNotification.type, undefined);
}
if (hasWorkspaceFolderCapability) {
connection.workspace.onDidChangeWorkspaceFolders((_event) => {
connection.console.log("Workspace folder change event received.");
});
}
});
connection.onCompletion(({ position, textDocument }: TextDocumentPositionParams) => {
const document = documents.get(textDocument.uri);
if (!document) {
return [];
}
const parseResult = parseDocument(document);
return handleCompletion(
{
parseResult: parseResult.parseResult,
getText: parseResult.getText,
metaByComp: metadataProvider,
offsetToPos: (offset: number) => document.positionAt(offset),
},
document.offsetAt(position),
);
});
connection.onCompletionResolve((completionItem: XmluiCompletionItem) => {
return handleCompletionResolve({ metaByComp: metadataProvider, item: completionItem });
});
connection.onHover(({ position, textDocument }: HoverParams) => {
const document = documents.get(textDocument.uri);
if (!document) {
return null;
}
const { parseResult, getText } = parseDocument(document);
const ctx = {
node: parseResult.node,
getText,
metaByComp: metadataProvider,
offsetToPosition: (offset: number) => document.positionAt(offset),
};
return handleHover(ctx, document.offsetAt(position));
});
connection.onDocumentFormatting(({ textDocument, options }: DocumentFormattingParams) => {
const document = documents.get(textDocument.uri);
if (!document) {
return null;
}
const {
parseResult: { node },
getText,
} = parseDocument(document);
return handleDocumentFormatting({
node,
getText,
options,
offsetToPosition: (offset) => document.positionAt(offset),
});
});
const parsedDocuments = new Map();
function parseDocument(document: TextDocument): {
parseResult: ParseResult;
getText: GetText;
} {
const parseForDoc = parsedDocuments.get(document.uri);
if (parseForDoc !== undefined) {
if (parseForDoc.version === document.version) {
return {
parseResult: parseForDoc.parseResult,
getText: parseForDoc.getText,
};
}
}
const parser = createXmlUiParser(document.getText());
const parseResult = parser.parse();
parsedDocuments.set(document.uri, {
parseResult,
version: document.version,
getText: parser.getText,
});
return { parseResult, getText: parser.getText };
}
documents.onDidClose(({ document }) => {
parsedDocuments.delete(document.uri);
});
documents.onDidChangeContent(handleDocunentContentChange);
function handleDocunentContentChange({ document }: { document: TextDocument }) {
const { parseResult } = parseDocument(document);
const ctx = {
parseResult,
offsetToPos: (offset: number) => document.positionAt(offset),
};
const diagnostics = getDiagnostics(ctx);
void connection.sendDiagnostics({
version: document.version,
uri: document.uri,
diagnostics,
});
}
// Make the text document manager listen on the connection
// for open, change and close text document events
documents.listen(connection);
// Listen on the connection
console.log("starting to listen");
connection.listen();
}
```
--------------------------------------------------------------------------------
/xmlui/src/components/Spinner/Spinner.spec.ts:
--------------------------------------------------------------------------------
```typescript
import { SKIP_REASON } from "../../testing/component-test-helpers";
import { expect, test } from "../../testing/fixtures";
import { getFullRectangle } from "../../testing/themed-app-test-helpers";
test.describe("Basic Functionality", () => {
test("component renders with basic props", async ({ page, initTestBed }) => {
await initTestBed(`<Spinner />`);
const spinner = page.getByRole("status");
await expect(spinner).toBeVisible();
});
test("component renders with delay prop", async ({ page, initTestBed }) => {
await initTestBed(`<Spinner delay="0" />`);
const spinner = page.getByRole("status");
await expect(spinner).toBeVisible();
});
test("component renders with fullScreen prop", async ({ page, initTestBed }) => {
await initTestBed(`<Spinner fullScreen="true" />`);
const spinner = page.getByRole("status");
await expect(spinner).toBeVisible();
});
});
test.describe("Accessibility", () => {
test("component has correct accessibility attributes", async ({ page, initTestBed }) => {
await initTestBed(`<Spinner />`);
const spinner = page.getByRole("status");
await expect(spinner).toHaveAttribute("aria-label", "loading", { ignoreCase: true });
});
test("component maintains accessibility with fullScreen", async ({ page, initTestBed }) => {
await initTestBed(`<Spinner fullScreen="true" />`);
const spinner = page.getByRole("status");
await expect(spinner).toHaveAttribute("aria-label", "loading", { ignoreCase: true });
});
});
test.describe("Theme Variables", () => {
test("component applies theme variables", async ({ page, initTestBed }) => {
await initTestBed(`<Spinner delay="0" />`, {
testThemeVars: {
"size-Spinner": "60px",
"thickness-Spinner": "6px",
"borderColor-Spinner": "rgb(255, 0, 0)",
},
});
const spinnerRing = page.locator("[data-part-id='ring']").first();
await expect(spinnerRing).toHaveCSS("border-top-color", "rgb(255, 0, 0)");
});
});
test.describe("Delay Behavior", () => {
test("component respects delay prop", { tag: "@smoke" }, async ({ page, initTestBed }) => {
await initTestBed(`
<Fragment>
control-text
<Spinner delay="500" />
</Fragment>`);
await page.getByText("control-text").waitFor({ state: "visible" });
const spinner = page.getByRole("status");
await expect(spinner).not.toBeVisible({ timeout: 0 });
await expect(spinner).toBeVisible();
});
test("component shows immediately with zero delay", async ({ page, initTestBed }) => {
await initTestBed(`
<Fragment>
control-text
<Spinner delay="0" />
</Fragment>`);
await page.getByText("control-text").waitFor({ state: "visible" });
const spinner = page.getByRole("status");
await expect(spinner).toBeVisible({ timeout: 0 });
});
test("component shows immediately with null delay", async ({ page, initTestBed }) => {
await initTestBed(`
<Fragment>
control-text
<Spinner delay="{null}" />
</Fragment>`);
await page.getByText("control-text").waitFor({ state: "visible" });
const spinner = page.getByRole("status");
await expect(spinner).toBeVisible({ timeout: 0 });
});
test("component shows immediately with undefined delay", async ({ page, initTestBed }) => {
await initTestBed(`
<Fragment>
control-text
<Spinner delay="{undefined}" />
</Fragment>`);
await page.getByText("control-text").waitFor({ state: "visible" });
const spinner = page.getByRole("status");
await expect(spinner).toBeVisible({ timeout: 0 });
});
test("component handles negative delay values", async ({ page, initTestBed }) => {
await initTestBed(`
<Fragment>
control-text
<Spinner delay="-100" />
</Fragment>`);
await page.getByText("control-text").waitFor({ state: "visible" });
const spinner = page.getByRole("status");
await expect(spinner).toBeVisible({ timeout: 0 });
});
test("component respects numeric delay prop", async ({ page, initTestBed }) => {
await initTestBed(`
<Fragment>
control-text
<Spinner delay="{ 500 }" />
</Fragment>`);
await page.getByText("control-text").waitFor({ state: "visible" });
const spinner = page.getByRole("status");
await expect(spinner).not.toBeVisible({ timeout: 0 });
await expect(spinner).toBeVisible();
});
});
test.describe("Full Screen Mode", () => {
test("component renders in fullScreen mode", async ({ page, initTestBed }) => {
await initTestBed(`<Spinner fullScreen="true" />`);
const spinner = page.getByRole("status");
await expect(spinner).toBeVisible();
const { width } = await getFullRectangle(spinner);
expect(width).toEqual(page.viewportSize().width);
});
test("component renders normally without fullScreen", async ({ page, initTestBed }) => {
await initTestBed(`<Spinner fullScreen="false" />`);
const spinner = page.getByRole("status");
await expect(spinner).toBeVisible();
const { width } = await getFullRectangle(spinner);
expect(width).not.toEqual(page.viewportSize().width);
});
});
test.describe("Edge Cases", { tag: "@smoke" }, () => {
test("delayed fullScreen spinner spans viewport width", async ({ page, initTestBed }) => {
await initTestBed(`
<Fragment>
control-text
<Spinner fullScreen="true" delay="{ 500 }" />
</Fragment>`);
await page.getByText("control-text").waitFor({ state: "visible" });
const spinner = page.getByRole("status");
await expect(spinner).not.toBeVisible({ timeout: 0 });
await expect(spinner).toBeVisible();
const { width } = await getFullRectangle(spinner);
expect(width).toEqual(page.viewportSize().width);
});
test("button behind fullScreen spinner can't be clicked", async ({ page, initTestBed }) => {
const { testStateDriver } = await initTestBed(`
<Fragment>
<Button label="Click Me" onClick="testState = clicked" />
<Spinner fullScreen="true" />
</Fragment>`);
const spinner = page.getByRole("status");
await expect(spinner).toBeVisible();
await page.getByRole("button").click({ force: true });
await expect.poll(testStateDriver.testState).not.toEqual("clicked");
});
});
```
--------------------------------------------------------------------------------
/xmlui/src/components/Splitter/Splitter.tsx:
--------------------------------------------------------------------------------
```typescript
import type React from "react";
import styles from "./Splitter.module.scss";
import type { RenderChildFn } from "../../abstractions/RendererDefs";
import { type ComponentDef } from "../../abstractions/ComponentDefs";
import type { ValueExtractor, LookupEventHandlerFn } from "../../abstractions/RendererDefs";
import { createComponentRenderer } from "../../components-core/renderers";
import { isComponentDefChildren } from "../../components-core/utils/misc";
import { NotAComponentDefError } from "../../components-core/EngineError";
import { parseScssVar } from "../../components-core/theming/themeVars";
import type { OrientationOptions } from "../abstractions";
import { createMetadata, d, dComponent } from "../metadata-helpers";
import { Splitter, defaultProps } from "./SplitterNative";
const COMP = "Splitter";
const baseSplitterMd = createMetadata({
status: "stable",
description:
"`Splitter` component divides a container into two resizable sections. These " +
"are are identified by their names: primary and secondary. They have a " +
"draggable bar between them.",
props: {
swapped: {
description:
`This optional booelan property indicates whether the \`${COMP}\` sections are layed out as ` +
`primary and secondary (\`false\`) or secondary and primary (\`true\`) from left to right.`,
valueType: "boolean",
defaultValue: defaultProps.swapped,
},
splitterTemplate: dComponent(
`The divider can be customized using XMLUI components via this property.`,
),
initialPrimarySize: {
description:
`This optional number property sets the initial size of the primary section. The unit of ` +
`the size value is in pixels or percentages.`,
valueType: "string",
defaultValue: defaultProps.initialPrimarySize,
},
minPrimarySize: {
description:
`This property sets the minimum size the primary section can have. The unit of the size ` +
`value is in pixels or percentages.`,
valueType: "string",
defaultValue: defaultProps.minPrimarySize,
},
maxPrimarySize: {
description:
`This property sets the maximum size the primary section can have. The unit of the size ` +
`value is in pixels or percentages. Negative values are supported and calculate from the ` +
`end of the container (e.g., "-20%" means "80% of container", "-100px" means "container size - 100px").`,
valueType: "string",
defaultValue: defaultProps.maxPrimarySize,
},
floating: {
description:
`Toggles whether the resizer is visible (\`false\`) or not (\`true\`) when not hovered ` +
`or dragged. The default value is \`false\`, meaning the resizer is visible all the time.`,
valueType: "boolean",
defaultValue: defaultProps.floating,
},
orientation: {
description:
`Sets whether the \`Splitter\` divides the container horizontally and lays out the ` +
`section on top of each other (\`vertical\`), or vertically by placing the sections ` +
`next to each other (\`horizontal\`).`,
valueType: "string",
availableValues: ["horizontal", "vertical"],
defaultValue: defaultProps.orientation,
},
},
events: {
resize: d(`This event fires when the component is resized.`),
},
themeVars: parseScssVar(styles.themeVars),
defaultThemeVars: {
[`backgroundColor-resizer-${COMP}`]: "$color-surface-100",
[`thickness-resizer-${COMP}`]: "5px",
[`cursor-resizer-horizontal-${COMP}`]: "ew-resize",
[`cursor-resizer-vertical-${COMP}`]: "ns-resize",
},
});
export const SplitterMd = {
...baseSplitterMd,
props: {
...baseSplitterMd.props,
},
};
export const HSplitterMd = { ...baseSplitterMd, specializedFrom: COMP };
export const VSplitterMd = { ...baseSplitterMd, specializedFrom: COMP };
type SplitterComponentDef = ComponentDef<typeof SplitterMd>;
type VSplitterComponentDef = ComponentDef<typeof VSplitterMd>;
type HSplitterComponentDef = ComponentDef<typeof HSplitterMd>;
type RenderSplitterPars = {
node: SplitterComponentDef | VSplitterComponentDef | HSplitterComponentDef;
extractValue: ValueExtractor;
className: string | undefined;
renderChild: RenderChildFn;
orientation?: OrientationOptions;
lookupEventHandler: LookupEventHandlerFn<typeof SplitterMd>;
};
const DEFAULT_ORIENTATION = "vertical";
function renderSplitter({
node,
extractValue,
className,
renderChild,
lookupEventHandler,
orientation = extractValue(node.props.orientation) ?? DEFAULT_ORIENTATION,
}: RenderSplitterPars) {
if (!isComponentDefChildren(node.children)) {
throw new NotAComponentDefError();
}
return (
<Splitter
className={className}
swapped={extractValue.asOptionalBoolean(node.props?.swapped)}
orientation={orientation}
splitterTemplate={renderChild(node.props?.splitterTemplate)}
initialPrimarySize={extractValue(node.props?.initialPrimarySize)}
minPrimarySize={extractValue(node.props?.minPrimarySize)}
maxPrimarySize={extractValue(node.props?.maxPrimarySize)}
floating={extractValue.asOptionalBoolean(node.props?.floating)}
resize={lookupEventHandler("resize")}
>
{renderChild(node.children)}
</Splitter>
);
}
export const splitterComponentRenderer = createComponentRenderer(
COMP,
SplitterMd,
({ node, extractValue, renderChild, className, lookupEventHandler }) => {
return renderSplitter({
node,
extractValue,
className,
renderChild,
lookupEventHandler: lookupEventHandler as any,
});
},
);
export const vSplitterComponentRenderer = createComponentRenderer(
"VSplitter",
VSplitterMd,
({ node, extractValue, renderChild, className, lookupEventHandler }) => {
return renderSplitter({
node,
extractValue,
className,
renderChild,
orientation: "vertical",
lookupEventHandler: lookupEventHandler as any,
});
},
);
export const hSplitterComponentRenderer = createComponentRenderer(
"HSplitter",
HSplitterMd,
({ node, extractValue, renderChild, className, lookupEventHandler }) => {
return renderSplitter({
node,
extractValue,
className,
renderChild,
orientation: "horizontal",
lookupEventHandler: lookupEventHandler as any,
});
},
);
```
--------------------------------------------------------------------------------
/xmlui/src/components-core/rendering/reducer.ts:
--------------------------------------------------------------------------------
```typescript
import produce from "immer";
import { cloneDeep, isPlainObject, keyBy, setWith, unset } from "lodash-es";
import type { ContainerState } from "../../abstractions/ContainerDefs";
import type { ContainerAction} from "./containers";
import { ContainerActionKind } from "./containers";
import type { IDebugViewContext } from "../DebugViewProvider";
const MAX_STATE_TRANSITION_LENGTH = 100;
/**
* This function creates a reducer for the container state. For diagnostics, it may
* log the state transitions.
* @param debugView This debug view determines if the state transitions should be logged.
*/
export function createContainerReducer(debugView: IDebugViewContext) {
const allowLogging = debugView.collectStateTransitions;
let prevState: any = undefined;
let nextState: any = undefined;
// --- The reducer function
return produce((state: ContainerState, action: ContainerAction) => {
// --- Check if the action has an appropriate uid
const { uid } = action.payload;
if (uid === undefined && action.type !== ContainerActionKind.STATE_PART_CHANGED) {
console.error("uid not provided for control component", {
state,
action,
});
return state;
}
// --- Store the previous state for logging
if (allowLogging) {
try {
prevState = cloneDeep(state[uid]);
} catch (e) {
console.error("Error while cloning previous value", e);
}
}
// --- Apply the action
switch (action.type) {
case ContainerActionKind.LOADER_IN_PROGRESS_CHANGED: {
state[uid] = { ...state[uid], inProgress: action.payload.inProgress };
storeNextValue(state[uid]);
break;
}
case ContainerActionKind.LOADER_IS_REFETCHING_CHANGED: {
state[uid] = { ...state[uid], isRefetching: action.payload.isRefetching };
storeNextValue(state[uid]);
break;
}
case ContainerActionKind.LOADER_LOADED: {
const { data, pageInfo } = action.payload;
state[uid] = {
value: data,
byId: Array.isArray(data) ? keyBy(data, (item) => item.$id) : undefined,
inProgress: false,
loaded: data !== undefined,
pageInfo,
};
storeNextValue(state[uid]);
break;
}
case ContainerActionKind.LOADER_ERROR: {
const { error } = action.payload;
state[uid] = { ...state[uid], error, inProgress: false, loaded: true };
storeNextValue(state[uid]);
break;
}
case ContainerActionKind.EVENT_HANDLER_STARTED: {
const { eventName } = action.payload;
const inProgressFlagName = `${eventName}InProgress`;
// Preserve existing state or use empty object
state[uid] = state[uid] ? { ...state[uid], [inProgressFlagName]: true } : { [inProgressFlagName]: true };
storeNextValue(state[uid]);
break;
}
case ContainerActionKind.EVENT_HANDLER_COMPLETED: {
const { eventName } = action.payload;
const inProgressFlagName = `${eventName}InProgress`;
// Preserve existing state or use empty object
state[uid] = state[uid] ? { ...state[uid], [inProgressFlagName]: false } : { [inProgressFlagName]: false };
storeNextValue(state[uid]);
break;
}
case ContainerActionKind.EVENT_HANDLER_ERROR: {
const { eventName } = action.payload;
const inProgressFlagName = `${eventName}InProgress`;
// Preserve existing state or use empty object
state[uid] = state[uid] ? { ...state[uid], [inProgressFlagName]: false } : { [inProgressFlagName]: false };
storeNextValue(state[uid]);
break;
}
case ContainerActionKind.COMPONENT_STATE_CHANGED: {
const { state: newState } = action.payload;
state[uid] = {
...state[uid],
...newState,
};
storeNextValue(state[uid]);
break;
}
case ContainerActionKind.STATE_PART_CHANGED: {
const { path, value, target, actionType, localVars } = action.payload;
if (actionType === "unset") {
unset(state, path);
} else {
let tempValueInLocalVars = localVars;
setWith(state, path, value, (nsValue, key, nsObject) => {
tempValueInLocalVars = tempValueInLocalVars?.[key];
if (
nsValue === undefined &&
isPlainObject(tempValueInLocalVars) &&
isPlainObject(target)
) {
// if we are setting a new object's key, lodash defaults it to an array, if the key is a number.
// This way we can force it to be an object.
// (example: we have an empty object in vars called usersTyped: {}, we set usersTyped[1] = Date.now().
// During the first state setting, we don't have a previous value for usersTyped, because it was defined
// in vars, and wasn't updated yet. In the first update, it's value is undefined, and because the key is
// a number (an id in our case), lodash thinks it has to create an array after this 'set'. This way we
// can force it, because in the target we have the target object value (given by the proxy change),so if
// it's an object, it should be an object. Otherwise, we let lodash decide)
const next = Object(nsValue);
return next;
}
});
storeNextValue(state);
}
break;
}
default:
throw new Error();
}
// --- Log the transition
if (allowLogging) {
const loggedTransition = {
action: action.type,
uid,
prevState,
nextState,
};
// TODO: Logging to the console is a temporary solution. We should use a proper
// logging mechanism. Nonetheless, this works only with state transition logging
// enabled (which is disabled by default).
if (debugView.stateTransitions) {
if (debugView.stateTransitions.length >= MAX_STATE_TRANSITION_LENGTH) {
debugView.stateTransitions.shift();
}
debugView.stateTransitions.push(loggedTransition);
}
}
function storeNextValue(nextValue: any) {
if (allowLogging) {
try {
nextState = cloneDeep(nextValue);
} catch (e) {
console.error("Error while cloning next value", e);
}
}
}
});
}
```
--------------------------------------------------------------------------------
/docs/content/components/BarChart.md:
--------------------------------------------------------------------------------
```markdown
# BarChart [#barchart]
`BarChart` displays data as horizontal or vertical bars, supporting both grouped and stacked layouts. It's ideal for comparing values across categories, showing revenue trends, or displaying any quantitative data over time or categories.
The BarChart component accommodates the size of its parent unless you set it explicitly:
```xmlui-pg copy display height="300px" name="Example: dimension determined by the parent" /Card height="240px" width="75%"/
<Card height="240px" width="75%">
<BarChart
orientation="horizontal"
data="{[
{ 'sprint': 'Sprint 1', 'A': 44 },
{ 'sprint': 'Sprint 2', 'A': 32 },
{ 'sprint': 'Sprint 3', 'A': 48 },
{ 'sprint': 'Sprint 4', 'A': 72 }
]}"
yKeys="{['A']}"
xKey="sprint"
/>
</Card>
```
```xmlui-pg copy display height="300px" name="Example: dimension overwritten by BarChart" /height="240px"/ /height="200px"/
<Card height="240px">
<BarChart
orientation="horizontal"
height="200px"
data="{[
{ 'sprint': 'Sprint 1', 'A': 44 },
{ 'sprint': 'Sprint 2', 'A': 32 },
{ 'sprint': 'Sprint 3', 'A': 48 },
{ 'sprint': 'Sprint 4', 'A': 72 }
]}"
yKeys="{['A']}"
xKey="sprint"
/>
</Card>
```
**Key features:**
- **Flexible orientation**: Choose horizontal or vertical bar layouts
- **Multiple data series**: Display several metrics on the same chart with different colored bars
- **Stacked vs grouped**: Stack bars on top of each other or place them side by side
- **Custom formatting**: Use `tickFormatter` to format axis labels and [`LabelList`](/components/LabelList) for data labels
## Properties [#properties]
### `data` [#data]
This property is used to provide the component with data to display.The data needs to be an array of objects.
### `hideTickX` (default: false) [#hidetickx-default-false]
Controls the visibility of the X-axis ticks. If set to `true`, tick labels on the X-axis will be hidden.
### `hideTickY` (default: false) [#hideticky-default-false]
Controls the visibility of the Y-axis ticks. If set to `true`, tick labels on the Y-axis will be hidden.
### `hideTooltip` (default: false) [#hidetooltip-default-false]
Determines whether the tooltip should be hidden. If set to `true`, tooltips will not appear on hover.
### `hideX` (default: false) [#hidex-default-false]
Determines whether the X-axis should be hidden. If set to `true`, the axis will not be rendered.
### `hideY` (default: false) [#hidey-default-false]
Determines whether the Y-axis should be hidden. If set to `true`, the axis will not be rendered.
### `orientation` (default: "vertical") [#orientation-default-vertical]
This property determines the orientation of the bar chart. The `vertical` variant specifies the horizontal axis as the primary and lays out the bars from left to right. The `horizontal` variant specifies the vertical axis as the primary and has the bars spread from top to bottom.
Available values: `horizontal`, `vertical` **(default)**
### `showLegend` (default: false) [#showlegend-default-false]
Determines whether the legend should be displayed.
### `stacked` (default: false) [#stacked-default-false]
This property determines how the bars are laid out.If set to `true`, bars with the same category will be stacked on top of each other rather than placed side by side.
### `tickFormatterX` [#tickformatterx]
A function that formats the tick labels on the X-axis.
```xmlui-pg copy display height="320px" name="Example: tickFormatterX" /tickFormatterX/
<App>
<BarChart
orientation="horizontal"
height="240px"
data="{[
{ 'sprint': 'Sprint 1', 'A': 44 },
{ 'sprint': 'Sprint 2', 'A': 32 },
{ 'sprint': 'Sprint 3', 'A': 48 },
{ 'sprint': 'Sprint 4', 'A': 72 }
]}"
yKeys="{['A']}"
xKey="sprint"
tickFormatterX="{(value) => '(' + value + ')'}"
/>
</App>
```
### `tickFormatterY` [#tickformattery]
A function that formats the tick labels on the Y-axis.
```xmlui-pg copy display height="320px" name="Example: tickFormatterY" /tickFormatterY/
<App>
<BarChart
orientation="horizontal"
height="240px"
data="{[
{ 'sprint': 'Sprint 1', 'A': 44 },
{ 'sprint': 'Sprint 2', 'A': 32 },
{ 'sprint': 'Sprint 3', 'A': 48 },
{ 'sprint': 'Sprint 4', 'A': 72 }
]}"
yKeys="{['A']}"
xKey="sprint"
tickFormatterY="{(value) => '$' + value}"
/>
</App>
```
### `tooltipTemplate` [#tooltiptemplate]
This property allows replacing the default template to display a tooltip.
```xmlui-pg copy display height="320px" name="Example: tooltipTemplate" /tooltipTemplate/
<App>
<BarChart
orientation="horizontal"
height="240px"
data="{[
{ 'sprint': 'Sprint 1', 'A': 44, 'B': 28 },
{ 'sprint': 'Sprint 2', 'A': 32, 'B': 41 },
{ 'sprint': 'Sprint 3', 'A': 48, 'B': 35 },
{ 'sprint': 'Sprint 4', 'A': 72, 'B': 58 }
]}"
yKeys="{['A', 'B']}"
xKey="sprint"
>
<property name="tooltipTemplate">
<VStack backgroundColor='white' padding="$space-2">
<Text fontWeight='bold'>{$tooltip.label}</Text>
<Items data="{$tooltip.payload}">
<HStack gap="$space-2" verticalAlignment="center">
<Stack
width="8px"
height="8px"
backgroundColor="{$item.color}" />
<Text>{$item.label}: {$item.value}</Text>
</HStack>
</Items>
</VStack>
</property>
</BarChart>
</App>
```
The `tooltipTemplate` prop allows you to customize the appearance and content of chart tooltips. The template receives a `$tooltip` context variable containing:
- `$tooltip.label`: The label for the data point (typically the yKey value)
- `$tooltip.payload`: An object containing all data values for the hovered point
- `$tooltip.active`: Boolean indicating if the tooltip is currently active
### `xKey` [#xkey]
This property specifies the keys in the data objects that should be used for rendering the bars.E.g. 'id' or 'key'.
### `yKeys` [#ykeys]
Specifies the key in the data objects that will be used to label the different data series.
## Events [#events]
This component does not have any events.
## Exposed Methods [#exposed-methods]
This component does not expose any methods.
## Styling [#styling]
This component does not have any styles.
```
--------------------------------------------------------------------------------
/xmlui/src/components/Backdrop/Backdrop.spec.ts:
--------------------------------------------------------------------------------
```typescript
import { create } from "domain";
import { test, expect } from "../../testing/fixtures";
// =============================================================================
// BASIC FUNCTIONALITY TESTS
// =============================================================================
test("component renders with default props", async ({ initTestBed, createBackdropDriver }) => {
// TODO: review these Copilot-created tests
await initTestBed(`<Backdrop>Content</Backdrop>`, {});
const backdropDriver = await createBackdropDriver();
const backdropElement = backdropDriver.getBackdrop();
await expect(backdropElement).toBeVisible();
await expect(backdropElement).toHaveCSS(
"background-color",
backdropDriver.getDefaultBackgroundColor(),
);
await expect(backdropElement).toHaveCSS("opacity", backdropDriver.getDefaultOpacity());
await expect(backdropDriver.component).toHaveText("Content");
});
test("component renders with custom background color", async ({
initTestBed,
createBackdropDriver,
}) => {
const EXPECTED_COLOR = "rgb(255, 0, 0)"; // Red color
await initTestBed(`<Backdrop backgroundColor="${EXPECTED_COLOR}">Content</Backdrop>`, {});
const backdropDriver = await createBackdropDriver();
const backdropElement = backdropDriver.getBackdrop();
await expect(backdropElement).toHaveCSS("background-color", EXPECTED_COLOR);
});
test("component renders with custom opacity", async ({ initTestBed, createBackdropDriver }) => {
const EXPECTED_OPACITY = "0.5";
await initTestBed(`<Backdrop opacity="${EXPECTED_OPACITY}">Content</Backdrop>`, {});
const backdropDriver = await createBackdropDriver();
const backdropElement = backdropDriver.getBackdrop();
await expect(backdropElement).toHaveCSS("opacity", EXPECTED_OPACITY);
});
test("component renders with overlay template", async ({ initTestBed, createBackdropDriver }) => {
await initTestBed(
`
<Backdrop>
<property name="overlayTemplate">
<H1>Overlay</H1>
</property>
Content
</Backdrop>
`,
{},
);
const backdropDriver = await createBackdropDriver();
const backdropElement = backdropDriver.getBackdrop();
await expect(backdropElement).toBeVisible();
await expect(backdropElement).toHaveCSS(
"background-color",
backdropDriver.getDefaultBackgroundColor(),
);
await expect(backdropElement).toHaveCSS("opacity", backdropDriver.getDefaultOpacity());
await expect(backdropDriver.component).toContainText("Content");
await expect(backdropDriver.getOverlay()).toContainText("Overlay");
});
// =============================================================================
// ACCESSIBILITY TESTS
// =============================================================================
// TODO: Implement accessibility tests
// =============================================================================
// VISUAL STATE TESTS
// =============================================================================
test("component handles different background colors", async ({
initTestBed,
createBackdropDriver,
}) => {
await initTestBed(`<Backdrop backgroundColor="blue">Content</Backdrop>`, {});
const EXPECTED_COLOR = "rgb(0, 0, 255)"; // Blue color
const backdropDriver = await createBackdropDriver();
const backdropElement = backdropDriver.getBackdrop();
await expect(backdropElement).toHaveCSS("background-color", EXPECTED_COLOR);
});
test("component handles theme variables", async ({
initTestBed,
createBackdropDriver,
}) => {
await initTestBed(`<Backdrop>Content</Backdrop>`, {
testThemeVars: {
"backgroundColor-Backdrop": "rgb(255, 0, 0)", // Red color
"opacity-Backdrop": "0.75",
},
});
const backdropDriver = await createBackdropDriver();
const backdropElement = backdropDriver.getBackdrop();
await expect(backdropElement).toHaveCSS("background-color", "rgb(255, 0, 0)"); // Red color
await expect(backdropElement).toHaveCSS("opacity", "0.75");
});
// =============================================================================
// EDGE CASE TESTS
// =============================================================================
test("component handles empty string props gracefully", async ({
initTestBed,
createBackdropDriver,
}) => {
await initTestBed(`<Backdrop backgroundColor="">Content</Backdrop>`, {});
const backdropDriver = await createBackdropDriver();
const backdropElement = backdropDriver.getBackdrop();
await expect(backdropElement).toHaveCSS("background-color", "rgb(0, 0, 0)"); // Default transparent color
await expect(backdropElement).toHaveCSS("opacity", "0.1");
});
test("component handles special characters in content correctly", async ({
initTestBed,
createBackdropDriver,
}) => {
await initTestBed(`<Backdrop>Content with special characters: !@#$%^&*()</Backdrop>`, {});
const backdropDriver = await createBackdropDriver();
const backdropElement = backdropDriver.getBackdrop();
await expect(backdropElement).toBeVisible();
await expect(backdropDriver.component).toContainText("Content with special characters: !@#$%^&*()");
});
// =============================================================================
// INTEGRATION TESTS
// =============================================================================
test("component works correctly with nested content", async ({ initTestBed, createBackdropDriver }) => {
// TODO: review these Copilot-created tests
await initTestBed(
`
<Backdrop>
<VStack>
<Text>Line 1</Text>
<Text>Line 2</Text>
</VStack>
</Backdrop>
`,
{},
);
const backdropDriver = await createBackdropDriver();
// Test that nested content renders correctly
await expect(backdropDriver.component).toContainText("Line 1");
await expect(backdropDriver.component).toContainText("Line 2");
// Test that backdrop is rendered over the content
await expect(backdropDriver.getBackdrop()).toBeVisible();
});
test("component works correctly in different layout contexts", async ({
initTestBed,
createBackdropDriver,
}) => {
await initTestBed(
`
<HStack>
<Text>Before</Text>
<Backdrop backgroundColor="blue" opacity="0.3">Backdrop Content</Backdrop>
<Text>After</Text>
</HStack>
`,
{},
);
const backdropDriver = await createBackdropDriver();
// Test that backdrop is rendered over the content
await expect(backdropDriver.getBackdrop()).toBeVisible();
});
```
--------------------------------------------------------------------------------
/xmlui/src/components/AutoComplete/AutoComplete.module.scss:
--------------------------------------------------------------------------------
```scss
@use "../../components-core/theming/themes" as t;
// --- This code snippet is required to collect the theme variables used in this module
$themeVars: (
);
@function createThemeVar($componentVariable) {
$themeVars: t.appendThemeVar($themeVars, $componentVariable) !global;
@return t.getThemeVar($themeVars, $componentVariable);
}
$component: "AutoComplete";
$themeVars: t.composeBorderVars($themeVars, $component);
$themeVars: t.composePaddingVars($themeVars, $component);
$themeVars: t.composePaddingVars($themeVars, "item-#{$component}");
// --- CSS properties of a particular AutoComplete variant
@mixin variant($variantName) {
border-radius: createThemeVar("Input:borderRadius-#{$component}-#{$variantName}");
border-color: createThemeVar("Input:borderColor-#{$component}-#{$variantName}");
border-width: createThemeVar("Input:borderWidth-#{$component}-#{$variantName}");
border-style: createThemeVar("Input:borderStyle-#{$component}-#{$variantName}");
font-size: createThemeVar("Input:fontSize-#{$component}-#{$variantName}");
background-color: createThemeVar("Input:backgroundColor-#{$component}-#{$variantName}");
box-shadow: createThemeVar("Input:boxShadow-#{$component}-#{$variantName}");
color: createThemeVar("Input:textColor-#{$component}-#{$variantName}");
&:hover {
border-color: createThemeVar("Input:borderColor-#{$component}-#{$variantName}--hover");
background-color: createThemeVar("Input:backgroundColor-#{$component}-#{$variantName}--hover");
box-shadow: createThemeVar("Input:boxShadow-#{$component}-#{$variantName}--hover");
color: createThemeVar("Input:textColor-#{$component}-#{$variantName}--hover");
}
&::placeholder {
color: createThemeVar("Input:textColor-placeholder-#{$component}-#{$variantName}");
font-size: createThemeVar("Input:fontSize-placeholder-#{$component}-#{$variantName}");
}
}
@layer components {
.command {
width: 100%;
height: auto;
overflow: visible;
background-color: transparent;
}
.badgeListWrapper {
outline: none;
width: 100%;
gap: t.$space-1;
display: flex;
flex-direction: row;
flex-wrap: wrap;
@include t.paddingVars($themeVars, $component);
@include variant("default");
&.error {
@include variant("error");
}
&.warning {
@include variant("warning");
}
&.valid {
@include variant("success");
}
&.disabled {
cursor: not-allowed;
opacity: 0.5;
background-color: createThemeVar("Input:backgroundColor-#{$component}--disabled");
color: createThemeVar("Input:textColor-#{$component}--disabled");
border-color: createThemeVar("Input:borderColor-#{$component}--disabled");
}
&.focused {
outline-width: createThemeVar("Input:outlineWidth-#{$component}--focus");
outline-color: createThemeVar("Input:outlineColor-#{$component}--focus");
outline-style: createThemeVar("Input:outlineStyle-#{$component}--focus");
outline-offset: createThemeVar("Input:outlineOffset-#{$component}--focus");
}
}
.badgeList {
position: relative;
align-items: center;
display: flex;
flex-wrap: wrap;
gap: 0.25rem;
}
.inputWrapper {
display: flex;
align-items: center;
min-width: fit-content;
flex: 1;
gap: t.$space-1;
}
.badge {
width: fit-content;
height: fit-content;
min-width: 0;
padding: createThemeVar("paddingVertical-#{$component}-badge") createThemeVar("paddingHorizontal-#{$component}-badge");
border-radius: createThemeVar("borderRadius-#{$component}-badge");
transition:
color 0.2s,
background-color 0.2s;
user-select: none;
cursor: pointer;
display: flex;
gap: t.$space-1;
justify-content: center;
align-items: center;
font-size: createThemeVar("Input:fontSize-#{$component}-badge");
background-color: createThemeVar("Input:backgroundColor-#{$component}-badge");
color: createThemeVar("Input:textColor-#{$component}-badge");
&:hover {
background-color: createThemeVar("Input:backgroundColor-#{$component}-badge--hover");
color: createThemeVar("Input:textColor-#{$component}-badge--hover");
}
&:active {
background-color: createThemeVar("Input:backgroundColor-#{$component}-badge--active");
color: createThemeVar("Input:textColor-#{$component}-badge--active");
}
}
.commandInput {
width: 100%;
background-color: transparent;
outline: none;
&:disabled {
cursor: not-allowed;
opacity: 0.5;
}
&::placeholder {
color: createThemeVar("Input:textColor-placeholder-#{$component}");
}
&:focus-within {
outline: none;
}
}
.actions {
display: flex;
align-items: center;
flex-shrink: 0;
}
.action {
cursor: pointer;
}
.autoCompleteEmpty {
display: flex;
gap: 0.2rem;
padding: 10px 0;
justify-content: center;
align-items: center;
font-size: 14px;
width: 100%;
}
.commandList {
width: 100%;
position: absolute;
overflow: hidden;
top: t.$space-1;
z-index: 999;
outline: none;
border-radius: createThemeVar("Input:borderRadius-menu-#{$component}");
border: createThemeVar("Input:borderWidth-menu-#{$component}") solid createThemeVar("Input:borderColor-menu-#{$component}");
background-color: createThemeVar("Input:backgroundColor-menu-#{$component}");
box-shadow: createThemeVar("Input:boxShadow-menu-#{$component}");
animation: fade-in 0.3s ease-in-out;
}
.autoCompleteOption {
@include t.paddingVars($themeVars, "item-#{$component}");
display: flex;
gap: t.$space-1;
align-items: center;
justify-content: space-between;
cursor: pointer;
transition: background-color 0.2s ease;
background-color: createThemeVar("backgroundColor-item-#{$component}");
&[aria-selected="true"] {
background-color: createThemeVar("backgroundColor-item-#{$component}--active");
}
&:hover,
&.highlighted {
background-color: createThemeVar("backgroundColor-item-#{$component}--hover");
}
&[aria-disabled="true"],
&.disabledOption {
pointer-events: none;
cursor: not-allowed;
color: createThemeVar("textColor-item-#{$component}--disabled");
font-style: italic;
}
}
}
// --- We export the theme variables to add them to the component renderer
:export {
themeVars: t.json-stringify($themeVars);
}
```