This is page 13 of 140. Use http://codebase.md/xmlui-org/xmlui/%7Bimage%7D?lines=false&page={x} to view the full context.
# Directory Structure
```
├── .changeset
│ ├── config.json
│ ├── lovely-coats-rescue.md
│ └── sad-feet-say.md
├── .eslintrc.cjs
├── .github
│ ├── build-checklist.png
│ ├── ISSUE_TEMPLATE
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ └── workflows
│ ├── deploy-blog.yml
│ ├── deploy-docs-optimized.yml
│ ├── deploy-docs.yml
│ ├── prepare-versions.yml
│ ├── release-packages.yml
│ ├── run-all-tests.yml
│ └── run-smoke-tests.yml
├── .gitignore
├── .prettierrc.js
├── .vscode
│ ├── launch.json
│ └── settings.json
├── blog
│ ├── .gitignore
│ ├── .gitkeep
│ ├── CHANGELOG.md
│ ├── extensions.ts
│ ├── index.html
│ ├── index.ts
│ ├── package.json
│ ├── public
│ │ ├── blog
│ │ │ ├── images
│ │ │ │ ├── blog-page-component.png
│ │ │ │ ├── blog-scrabble.png
│ │ │ │ ├── integrated-blog-search.png
│ │ │ │ └── lorem-ipsum.png
│ │ │ ├── lorem-ipsum.md
│ │ │ ├── newest-post.md
│ │ │ ├── older-post.md
│ │ │ └── welcome-to-the-xmlui-blog.md
│ │ ├── mockServiceWorker.js
│ │ ├── resources
│ │ │ ├── favicon.ico
│ │ │ ├── files
│ │ │ │ └── for-download
│ │ │ │ └── xmlui
│ │ │ │ └── xmlui-standalone.umd.js
│ │ │ ├── github.svg
│ │ │ ├── llms.txt
│ │ │ ├── logo-dark.svg
│ │ │ ├── logo.svg
│ │ │ ├── pg-popout.svg
│ │ │ ├── rss.svg
│ │ │ └── xmlui-logo.svg
│ │ ├── serve.json
│ │ └── web.config
│ ├── scripts
│ │ ├── download-latest-xmlui.js
│ │ ├── generate-rss.js
│ │ ├── get-releases.js
│ │ └── utils.js
│ ├── src
│ │ ├── components
│ │ │ ├── BlogOverview.xmlui
│ │ │ ├── BlogPage.xmlui
│ │ │ └── PageNotFound.xmlui
│ │ ├── config.ts
│ │ ├── Main.xmlui
│ │ └── themes
│ │ └── blog-theme.ts
│ └── tsconfig.json
├── CONTRIBUTING.md
├── docs
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── ComponentRefLinks.txt
│ ├── content
│ │ ├── _meta.json
│ │ ├── components
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── APICall.md
│ │ │ ├── App.md
│ │ │ ├── AppHeader.md
│ │ │ ├── AppState.md
│ │ │ ├── AutoComplete.md
│ │ │ ├── Avatar.md
│ │ │ ├── Backdrop.md
│ │ │ ├── Badge.md
│ │ │ ├── BarChart.md
│ │ │ ├── Bookmark.md
│ │ │ ├── Breakout.md
│ │ │ ├── Button.md
│ │ │ ├── Card.md
│ │ │ ├── Carousel.md
│ │ │ ├── ChangeListener.md
│ │ │ ├── Checkbox.md
│ │ │ ├── CHStack.md
│ │ │ ├── ColorPicker.md
│ │ │ ├── Column.md
│ │ │ ├── ContentSeparator.md
│ │ │ ├── CVStack.md
│ │ │ ├── DataSource.md
│ │ │ ├── DateInput.md
│ │ │ ├── DatePicker.md
│ │ │ ├── DonutChart.md
│ │ │ ├── DropdownMenu.md
│ │ │ ├── EmojiSelector.md
│ │ │ ├── ExpandableItem.md
│ │ │ ├── FileInput.md
│ │ │ ├── FileUploadDropZone.md
│ │ │ ├── FlowLayout.md
│ │ │ ├── Footer.md
│ │ │ ├── Form.md
│ │ │ ├── FormItem.md
│ │ │ ├── FormSection.md
│ │ │ ├── Fragment.md
│ │ │ ├── H1.md
│ │ │ ├── H2.md
│ │ │ ├── H3.md
│ │ │ ├── H4.md
│ │ │ ├── H5.md
│ │ │ ├── H6.md
│ │ │ ├── Heading.md
│ │ │ ├── HSplitter.md
│ │ │ ├── HStack.md
│ │ │ ├── Icon.md
│ │ │ ├── IFrame.md
│ │ │ ├── Image.md
│ │ │ ├── Items.md
│ │ │ ├── LabelList.md
│ │ │ ├── Legend.md
│ │ │ ├── LineChart.md
│ │ │ ├── Link.md
│ │ │ ├── List.md
│ │ │ ├── Logo.md
│ │ │ ├── Markdown.md
│ │ │ ├── MenuItem.md
│ │ │ ├── MenuSeparator.md
│ │ │ ├── ModalDialog.md
│ │ │ ├── NavGroup.md
│ │ │ ├── NavLink.md
│ │ │ ├── NavPanel.md
│ │ │ ├── NoResult.md
│ │ │ ├── NumberBox.md
│ │ │ ├── Option.md
│ │ │ ├── Page.md
│ │ │ ├── PageMetaTitle.md
│ │ │ ├── Pages.md
│ │ │ ├── Pagination.md
│ │ │ ├── PasswordInput.md
│ │ │ ├── PieChart.md
│ │ │ ├── ProgressBar.md
│ │ │ ├── Queue.md
│ │ │ ├── RadioGroup.md
│ │ │ ├── RealTimeAdapter.md
│ │ │ ├── Redirect.md
│ │ │ ├── Select.md
│ │ │ ├── Slider.md
│ │ │ ├── Slot.md
│ │ │ ├── SpaceFiller.md
│ │ │ ├── Spinner.md
│ │ │ ├── Splitter.md
│ │ │ ├── Stack.md
│ │ │ ├── StickyBox.md
│ │ │ ├── SubMenuItem.md
│ │ │ ├── Switch.md
│ │ │ ├── TabItem.md
│ │ │ ├── Table.md
│ │ │ ├── TableOfContents.md
│ │ │ ├── Tabs.md
│ │ │ ├── Text.md
│ │ │ ├── TextArea.md
│ │ │ ├── TextBox.md
│ │ │ ├── Theme.md
│ │ │ ├── TimeInput.md
│ │ │ ├── Timer.md
│ │ │ ├── ToneChangerButton.md
│ │ │ ├── ToneSwitch.md
│ │ │ ├── Tooltip.md
│ │ │ ├── Tree.md
│ │ │ ├── VSplitter.md
│ │ │ ├── VStack.md
│ │ │ ├── xmlui-animations
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── Animation.md
│ │ │ │ ├── FadeAnimation.md
│ │ │ │ ├── FadeInAnimation.md
│ │ │ │ ├── FadeOutAnimation.md
│ │ │ │ ├── ScaleAnimation.md
│ │ │ │ └── SlideInAnimation.md
│ │ │ ├── xmlui-pdf
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Pdf.md
│ │ │ ├── xmlui-spreadsheet
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Spreadsheet.md
│ │ │ └── xmlui-website-blocks
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── Carousel.md
│ │ │ ├── HelloMd.md
│ │ │ ├── HeroSection.md
│ │ │ └── ScrollToTop.md
│ │ └── extensions
│ │ ├── _meta.json
│ │ ├── xmlui-animations
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── Animation.md
│ │ │ ├── FadeAnimation.md
│ │ │ ├── FadeInAnimation.md
│ │ │ ├── FadeOutAnimation.md
│ │ │ ├── ScaleAnimation.md
│ │ │ └── SlideInAnimation.md
│ │ └── xmlui-website-blocks
│ │ ├── _meta.json
│ │ ├── _overview.md
│ │ ├── Carousel.md
│ │ ├── HelloMd.md
│ │ ├── HeroSection.md
│ │ └── ScrollToTop.md
│ ├── extensions.ts
│ ├── index.html
│ ├── index.ts
│ ├── package.json
│ ├── public
│ │ ├── feed.rss
│ │ ├── mockServiceWorker.js
│ │ ├── pages
│ │ │ ├── _meta.json
│ │ │ ├── app-structure.md
│ │ │ ├── build-editor-component.md
│ │ │ ├── build-hello-world-component.md
│ │ │ ├── components-intro.md
│ │ │ ├── context-variables.md
│ │ │ ├── forms.md
│ │ │ ├── globals.md
│ │ │ ├── glossary.md
│ │ │ ├── helper-tags.md
│ │ │ ├── hosted-deployment.md
│ │ │ ├── howto
│ │ │ │ ├── assign-a-complex-json-literal-to-a-component-variable.md
│ │ │ │ ├── chain-a-refetch.md
│ │ │ │ ├── control-cache-invalidation.md
│ │ │ │ ├── debug-a-component.md
│ │ │ │ ├── delay-a-datasource-until-another-datasource-is-ready.md
│ │ │ │ ├── delegate-a-method.md
│ │ │ │ ├── do-custom-form-validation.md
│ │ │ │ ├── expose-a-method-from-a-component.md
│ │ │ │ ├── filter-and-transform-data-from-an-api.md
│ │ │ │ ├── group-items-in-list-by-a-property.md
│ │ │ │ ├── handle-background-operations.md
│ │ │ │ ├── hide-an-element-until-its-datasource-is-ready.md
│ │ │ │ ├── make-a-set-of-equal-width-cards.md
│ │ │ │ ├── make-a-table-responsive.md
│ │ │ │ ├── make-navpanel-width-responsive.md
│ │ │ │ ├── modify-a-value-reported-in-a-column.md
│ │ │ │ ├── paginate-a-list.md
│ │ │ │ ├── pass-data-to-a-modal-dialog.md
│ │ │ │ ├── react-to-button-click-not-keystrokes.md
│ │ │ │ ├── set-the-initial-value-of-a-select-from-fetched-data.md
│ │ │ │ ├── share-a-modaldialog-across-components.md
│ │ │ │ ├── sync-selections-between-table-and-list-views.md
│ │ │ │ ├── update-ui-optimistically.md
│ │ │ │ ├── use-built-in-form-validation.md
│ │ │ │ └── use-the-same-modaldialog-to-add-or-edit.md
│ │ │ ├── howto.md
│ │ │ ├── intro.md
│ │ │ ├── layout.md
│ │ │ ├── markup.md
│ │ │ ├── mcp.md
│ │ │ ├── modal-dialogs.md
│ │ │ ├── news-and-reviews.md
│ │ │ ├── reactive-intro.md
│ │ │ ├── refactoring.md
│ │ │ ├── routing-and-links.md
│ │ │ ├── samples
│ │ │ │ ├── color-palette.xmlui
│ │ │ │ ├── color-values.xmlui
│ │ │ │ ├── shadow-sizes.xmlui
│ │ │ │ ├── spacing-sizes.xmlui
│ │ │ │ ├── swatch.xmlui
│ │ │ │ ├── theme-gallery-brief.xmlui
│ │ │ │ └── theme-gallery.xmlui
│ │ │ ├── scoping.md
│ │ │ ├── scripting.md
│ │ │ ├── styles-and-themes
│ │ │ │ ├── common-units.md
│ │ │ │ ├── layout-props.md
│ │ │ │ ├── theme-variable-defaults.md
│ │ │ │ ├── theme-variables.md
│ │ │ │ └── themes.md
│ │ │ ├── template-properties.md
│ │ │ ├── test.md
│ │ │ ├── tutorial-01.md
│ │ │ ├── tutorial-02.md
│ │ │ ├── tutorial-03.md
│ │ │ ├── tutorial-04.md
│ │ │ ├── tutorial-05.md
│ │ │ ├── tutorial-06.md
│ │ │ ├── tutorial-07.md
│ │ │ ├── tutorial-08.md
│ │ │ ├── tutorial-09.md
│ │ │ ├── tutorial-10.md
│ │ │ ├── tutorial-11.md
│ │ │ ├── tutorial-12.md
│ │ │ ├── universal-properties.md
│ │ │ ├── user-defined-components.md
│ │ │ ├── vscode.md
│ │ │ ├── working-with-markdown.md
│ │ │ ├── working-with-text.md
│ │ │ ├── xmlui-animations
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── Animation.md
│ │ │ │ ├── FadeAnimation.md
│ │ │ │ ├── FadeInAnimation.md
│ │ │ │ ├── FadeOutAnimation.md
│ │ │ │ ├── ScaleAnimation.md
│ │ │ │ └── SlideInAnimation.md
│ │ │ ├── xmlui-charts
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── BarChart.md
│ │ │ │ ├── DonutChart.md
│ │ │ │ ├── LabelList.md
│ │ │ │ ├── Legend.md
│ │ │ │ ├── LineChart.md
│ │ │ │ └── PieChart.md
│ │ │ ├── xmlui-pdf
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Pdf.md
│ │ │ └── xmlui-spreadsheet
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ └── Spreadsheet.md
│ │ ├── resources
│ │ │ ├── devdocs
│ │ │ │ ├── debug-proxy-object-2.png
│ │ │ │ ├── debug-proxy-object.png
│ │ │ │ ├── table_editor_01.png
│ │ │ │ ├── table_editor_02.png
│ │ │ │ ├── table_editor_03.png
│ │ │ │ ├── table_editor_04.png
│ │ │ │ ├── table_editor_05.png
│ │ │ │ ├── table_editor_06.png
│ │ │ │ ├── table_editor_07.png
│ │ │ │ ├── table_editor_08.png
│ │ │ │ ├── table_editor_09.png
│ │ │ │ ├── table_editor_10.png
│ │ │ │ ├── table_editor_11.png
│ │ │ │ ├── table-editor-01.png
│ │ │ │ ├── table-editor-02.png
│ │ │ │ ├── table-editor-03.png
│ │ │ │ ├── table-editor-04.png
│ │ │ │ ├── table-editor-06.png
│ │ │ │ ├── table-editor-07.png
│ │ │ │ ├── table-editor-08.png
│ │ │ │ ├── table-editor-09.png
│ │ │ │ └── xmlui-rendering-of-tiptap-markdown.png
│ │ │ ├── favicon.ico
│ │ │ ├── files
│ │ │ │ ├── clients.json
│ │ │ │ ├── daily-revenue.json
│ │ │ │ ├── dashboard-stats.json
│ │ │ │ ├── demo.xmlui
│ │ │ │ ├── demo.xmlui.xs
│ │ │ │ ├── downloads
│ │ │ │ │ └── downloads.json
│ │ │ │ ├── for-download
│ │ │ │ │ ├── index-with-api.html
│ │ │ │ │ ├── index.html
│ │ │ │ │ ├── mockApi.js
│ │ │ │ │ ├── start-darwin.sh
│ │ │ │ │ ├── start-linux.sh
│ │ │ │ │ ├── start.bat
│ │ │ │ │ └── xmlui
│ │ │ │ │ └── xmlui-standalone.umd.js
│ │ │ │ ├── getting-started
│ │ │ │ │ ├── cl-tutorial-final.zip
│ │ │ │ │ ├── cl-tutorial.zip
│ │ │ │ │ ├── cl-tutorial2.zip
│ │ │ │ │ ├── cl-tutorial3.zip
│ │ │ │ │ ├── cl-tutorial4.zip
│ │ │ │ │ ├── cl-tutorial5.zip
│ │ │ │ │ ├── cl-tutorial6.zip
│ │ │ │ │ ├── getting-started.zip
│ │ │ │ │ ├── hello-xmlui.zip
│ │ │ │ │ ├── xmlui-empty.zip
│ │ │ │ │ └── xmlui-starter.zip
│ │ │ │ ├── howto
│ │ │ │ │ └── component-icons
│ │ │ │ │ └── up-arrow.svg
│ │ │ │ ├── invoices.json
│ │ │ │ ├── monthly-status.json
│ │ │ │ ├── news-and-reviews.json
│ │ │ │ ├── products.json
│ │ │ │ ├── releases.json
│ │ │ │ ├── tutorials
│ │ │ │ │ ├── datasource
│ │ │ │ │ │ └── api.ts
│ │ │ │ │ └── p2do
│ │ │ │ │ ├── api.ts
│ │ │ │ │ └── todo-logo.svg
│ │ │ │ └── xmlui.json
│ │ │ ├── github.svg
│ │ │ ├── images
│ │ │ │ ├── apiaction-tutorial
│ │ │ │ │ ├── add-success.png
│ │ │ │ │ ├── apiaction-param.png
│ │ │ │ │ ├── change-completed.png
│ │ │ │ │ ├── change-in-progress.png
│ │ │ │ │ ├── confirm-delete.png
│ │ │ │ │ ├── data-error.png
│ │ │ │ │ ├── data-progress.png
│ │ │ │ │ ├── data-success.png
│ │ │ │ │ ├── display-1.png
│ │ │ │ │ ├── item-deleted.png
│ │ │ │ │ ├── item-updated.png
│ │ │ │ │ ├── missing-api-key.png
│ │ │ │ │ ├── new-item-added.png
│ │ │ │ │ └── test-message.png
│ │ │ │ ├── chat-api
│ │ │ │ │ └── domain-model.svg
│ │ │ │ ├── components
│ │ │ │ │ ├── image
│ │ │ │ │ │ └── breakfast.jpg
│ │ │ │ │ ├── markdown
│ │ │ │ │ │ └── colors.png
│ │ │ │ │ └── modal
│ │ │ │ │ ├── deep_link_dialog_1.jpg
│ │ │ │ │ └── deep_link_dialog_2.jpg
│ │ │ │ ├── create-apps
│ │ │ │ │ ├── collapsed-vertical.png
│ │ │ │ │ ├── using-forms-warning-dialog.png
│ │ │ │ │ └── using-forms.png
│ │ │ │ ├── datasource-tutorial
│ │ │ │ │ ├── data-with-header.png
│ │ │ │ │ ├── filtered-data.png
│ │ │ │ │ ├── filtered-items.png
│ │ │ │ │ ├── initial-page-items.png
│ │ │ │ │ ├── list-items.png
│ │ │ │ │ ├── next-page-items.png
│ │ │ │ │ ├── no-data.png
│ │ │ │ │ ├── pagination-1.jpg
│ │ │ │ │ ├── pagination-1.png
│ │ │ │ │ ├── polling-1.png
│ │ │ │ │ ├── refetch-data.png
│ │ │ │ │ ├── slow-loading.png
│ │ │ │ │ ├── test-message.png
│ │ │ │ │ ├── Thumbs.db
│ │ │ │ │ ├── unconventional-data.png
│ │ │ │ │ └── unfiltered-items.png
│ │ │ │ ├── flower.jpg
│ │ │ │ ├── get-started
│ │ │ │ │ ├── add-new-contact.png
│ │ │ │ │ ├── app-modified.png
│ │ │ │ │ ├── app-start.png
│ │ │ │ │ ├── app-with-boxes.png
│ │ │ │ │ ├── app-with-toast.png
│ │ │ │ │ ├── boilerplate-structure.png
│ │ │ │ │ ├── cl-initial.png
│ │ │ │ │ ├── cl-start.png
│ │ │ │ │ ├── contact-counts.png
│ │ │ │ │ ├── contact-dialog-title.png
│ │ │ │ │ ├── contact-dialog.png
│ │ │ │ │ ├── contact-menus.png
│ │ │ │ │ ├── contact-predicates.png
│ │ │ │ │ ├── context-menu.png
│ │ │ │ │ ├── dashboard-numbers.png
│ │ │ │ │ ├── default-contact-list.png
│ │ │ │ │ ├── delete-contact.png
│ │ │ │ │ ├── delete-task.png
│ │ │ │ │ ├── detailed-template.png
│ │ │ │ │ ├── edit-contact-details.png
│ │ │ │ │ ├── edited-contact-saved.png
│ │ │ │ │ ├── empty-sections.png
│ │ │ │ │ ├── filter-completed.png
│ │ │ │ │ ├── fullwidth-desktop.png
│ │ │ │ │ ├── fullwidth-mobile.png
│ │ │ │ │ ├── initial-table.png
│ │ │ │ │ ├── items-and-badges.png
│ │ │ │ │ ├── loading-message.png
│ │ │ │ │ ├── new-contact-button.png
│ │ │ │ │ ├── new-contact-saved.png
│ │ │ │ │ ├── no-empty-sections.png
│ │ │ │ │ ├── personal-todo-initial.png
│ │ │ │ │ ├── piechart.png
│ │ │ │ │ ├── review-today.png
│ │ │ │ │ ├── rudimentary-dashboard.png
│ │ │ │ │ ├── section-collapsed.png
│ │ │ │ │ ├── sectioned-items.png
│ │ │ │ │ ├── sections-ordered.png
│ │ │ │ │ ├── spacex-list-with-links.png
│ │ │ │ │ ├── spacex-list.png
│ │ │ │ │ ├── start-personal-todo-1.png
│ │ │ │ │ ├── submit-new-contact.png
│ │ │ │ │ ├── submit-new-task.png
│ │ │ │ │ ├── syntax-highlighting.png
│ │ │ │ │ ├── table-with-badge.png
│ │ │ │ │ ├── template-with-card.png
│ │ │ │ │ ├── test-emulated-api.png
│ │ │ │ │ ├── Thumbs.db
│ │ │ │ │ ├── todo-logo.png
│ │ │ │ │ └── xmlui-tools.png
│ │ │ │ ├── HelloApp.png
│ │ │ │ ├── HelloApp2.png
│ │ │ │ ├── logos
│ │ │ │ │ ├── xmlui1.svg
│ │ │ │ │ ├── xmlui2.svg
│ │ │ │ │ ├── xmlui3.svg
│ │ │ │ │ ├── xmlui4.svg
│ │ │ │ │ ├── xmlui5.svg
│ │ │ │ │ ├── xmlui6.svg
│ │ │ │ │ └── xmlui7.svg
│ │ │ │ ├── pdf
│ │ │ │ │ └── dummy-pdf.jpg
│ │ │ │ ├── rendering-engine
│ │ │ │ │ ├── AppEngine-flow.svg
│ │ │ │ │ ├── Component.svg
│ │ │ │ │ ├── CompoundComponent.svg
│ │ │ │ │ ├── RootComponent.svg
│ │ │ │ │ └── tree-with-containers.svg
│ │ │ │ ├── reviewers-guide
│ │ │ │ │ ├── AppEngine-flow.svg
│ │ │ │ │ └── incbutton-in-action.png
│ │ │ │ ├── tools
│ │ │ │ │ └── boilerplate-structure.png
│ │ │ │ ├── try.svg
│ │ │ │ ├── tutorial
│ │ │ │ │ ├── app-chat-history.png
│ │ │ │ │ ├── app-content-placeholder.png
│ │ │ │ │ ├── app-header-and-content.png
│ │ │ │ │ ├── app-links-channel-selected.png
│ │ │ │ │ ├── app-links-click.png
│ │ │ │ │ ├── app-navigation.png
│ │ │ │ │ ├── finished-ex01.png
│ │ │ │ │ ├── finished-ex02.png
│ │ │ │ │ ├── hello.png
│ │ │ │ │ ├── splash-screen-advanced.png
│ │ │ │ │ ├── splash-screen-after-click.png
│ │ │ │ │ ├── splash-screen-centered.png
│ │ │ │ │ ├── splash-screen-events.png
│ │ │ │ │ ├── splash-screen-expression.png
│ │ │ │ │ ├── splash-screen-reuse-after.png
│ │ │ │ │ ├── splash-screen-reuse-before.png
│ │ │ │ │ └── splash-screen.png
│ │ │ │ └── tutorial-01.png
│ │ │ ├── llms.txt
│ │ │ ├── logo-dark.svg
│ │ │ ├── logo.svg
│ │ │ ├── pg-popout.svg
│ │ │ └── xmlui-logo.svg
│ │ ├── serve.json
│ │ └── web.config
│ ├── scripts
│ │ ├── download-latest-xmlui.js
│ │ ├── generate-rss.js
│ │ ├── get-releases.js
│ │ └── utils.js
│ ├── src
│ │ ├── components
│ │ │ ├── BlogOverview.xmlui
│ │ │ ├── BlogPage.xmlui
│ │ │ ├── Boxes.xmlui
│ │ │ ├── Breadcrumb.xmlui
│ │ │ ├── ChangeLog.xmlui
│ │ │ ├── ColorPalette.xmlui
│ │ │ ├── DocumentLinks.xmlui
│ │ │ ├── DocumentPage.xmlui
│ │ │ ├── DocumentPageNoTOC.xmlui
│ │ │ ├── Icons.xmlui
│ │ │ ├── IncButton.xmlui
│ │ │ ├── IncButton2.xmlui
│ │ │ ├── NameValue.xmlui
│ │ │ ├── PageNotFound.xmlui
│ │ │ ├── PaletteItem.xmlui
│ │ │ ├── Palettes.xmlui
│ │ │ ├── SectionHeader.xmlui
│ │ │ ├── TBD.xmlui
│ │ │ ├── Test.xmlui
│ │ │ ├── ThemesIntro.xmlui
│ │ │ ├── ThousandThemes.xmlui
│ │ │ ├── TubeStops.xmlui
│ │ │ ├── TubeStops.xmlui.xs
│ │ │ └── TwoColumnCode.xmlui
│ │ ├── config.ts
│ │ ├── Main.xmlui
│ │ └── themes
│ │ ├── docs-theme.ts
│ │ ├── earthtone.ts
│ │ ├── xmlui-gray-on-default.ts
│ │ ├── xmlui-green-on-default.ts
│ │ └── xmlui-orange-on-default.ts
│ └── tsconfig.json
├── LICENSE
├── package-lock.json
├── package.json
├── packages
│ ├── 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
│ │ └── tsconfig.json
│ ├── 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
│ │ ├── tsconfig.json
│ │ └── 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
│ │ └── tsconfig.json
│ ├── 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
│ │ └── tsconfig.json
│ ├── 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
│ │ └── tsconfig.json
│ ├── 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
│ │ └── tsconfig.json
│ ├── 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
│ │ └── tsconfig.json
│ ├── xmlui-spreadsheet
│ │ ├── .gitignore
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ ├── src
│ │ │ ├── index.tsx
│ │ │ ├── Spreadsheet.tsx
│ │ │ └── SpreadsheetNative.tsx
│ │ └── tsconfig.json
│ └── 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.tsx
│ │ │ └── HeroSectionNative.tsx
│ │ ├── index.tsx
│ │ ├── ScrollToTop
│ │ │ ├── ScrollToTop.module.scss
│ │ │ ├── ScrollToTop.tsx
│ │ │ └── ScrollToTopNative.tsx
│ │ └── vite-env.d.ts
│ └── tsconfig.json
├── 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.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
├── playwright.config.ts
├── 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.mjs
│ ├── 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.mjs
│ ├── 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
│ │ │ ├── ModalDialogDriver.ts
│ │ │ ├── NumberBoxDriver.ts
│ │ │ ├── TextBoxDriver.ts
│ │ │ ├── TimeInputDriver.ts
│ │ │ ├── TimerDriver.ts
│ │ │ └── TreeDriver.ts
│ │ ├── fixtures.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
--------------------------------------------------------------------------------
/docs/public/pages/app-structure.md:
--------------------------------------------------------------------------------
```markdown
# Structure of an XMLUI app
The [XMLUI Invoice demo app](https://github.com/xmlui-org/xmlui-invoice/releases) exhibits the typical structure of an XMLUI app.
```xmlui-tree
<root>
index.html
Main.xmlui
config.json
components
ClientDetails.xmlui
Clients.xmlui
...
MonthlyRevenue.xmlui
WeeklyRevenue.xmlui
resources
favicon.ico
xmlui-logo-inverted.svg
xmlui-logo.svg
themes
invoice.json
xmlui
0.9.23.js
charts-0.1.21.js
start.bat
start.sh
api.json
data.db
xmlui-test-server
```
> [!INFO] The `xmlui` folder contains the xmlui engine with a version number, specifically `0.9.23.js`. We recommend this practice in order to know when/whether to upgrade.
| file| description |
|---|---|
| **`index.html`** | The default webpage to display |
| **`Main.xmlui`** | The XMLUI app's entry point |
| **`config.json`** | The XMLUI app's configuration file |
| **`components`** | The folder with your custom components |
| **`resources`** | The folder with static app resources |
| **`themes`** | The folder with your custom themes |
| **`xmlui`** | The folder with the XMLUI core framework and extensions |
| **`start.bat`** | The batch file to start the test server on Windows |
| **`start.sh`** | The bash script file to start the test server on Mac, Linux, or WSL |
| **`api.json`** | *Optional*: API description file for use with xmlui-test-server |
| **`data.db`** | *Optional*: SQLite database for use with xmlui-test-server|
| **`xmlui-test-server`** | *Optional*: server, you can use any static web server|
You can deploy this tree structure (minus the optional `api.json`, `data.db`, and `xmlui-test-server`) to any static webserver that's configured to serve `index.html`. Consider this minimal app.
```xmlui-tree
xmlui-minimal
index.html
Main.xmlui
components
Home.xmlui
resources
favicon.ico
xmlui-logo-inverted.svg
xmlui-logo.svg
xmlui
0.9.23.js
```
## index.html
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="xmlui/0.9.23.js"></script>
</head>
<body>
</body>
</html>
```
## Main.xmlui
```xmlui
<App name="XMLUI Minimal">
<NavPanel>
<NavLink label="Home" to="/Home" />
</NavPanel>
<Pages>
<Page url="/Home">
<Home />
</Page>
</Pages>
</App>
```
## Home.xmlui
```xmlui
<Component name="Home" >
A minimal XMLUI app
</Component>
```
## Local deployment
If you are working locally, in a folder at the root of this tree, here are some ways you can serve the app.
If you have node.js and npm:
```
npx -y http-server
$ npx -y http-server
Starting up http-server, serving ./
Available on:
http://127.0.0.1:8080
```
If you have python:
```
$ python -m http.server 8080
Serving HTTP on :: port 8080 (http://[::]:8080/) ...
```
In either case, visit http://localhost:8080 to view the app.
See also [Hosted deployment](/hosted-deployment).
```
--------------------------------------------------------------------------------
/xmlui/src/components/Charts/DonutChart/DonutChart.tsx:
--------------------------------------------------------------------------------
```typescript
import { createComponentRenderer } from "../../../components-core/renderers";
import styles from "../PieChart/PieChartNative.module.scss";
import { defaultProps, PieChart } from "../PieChart/PieChartNative";
import { parseScssVar } from "../../../components-core/theming/themeVars";
import { createMetadata } from "../../metadata-helpers";
const COMP = "DonutChart";
const defaultPropsDonut = {
...defaultProps,
innerRadius: 60,
};
export const DonutChartMd = createMetadata({
status: "experimental",
description: "A derivative of [PieChart](/components/PieChart) with a hollow center. " +
"Note that the height of the component or its parent needs to be set explicitly.",
props: {
data: {
description: "The data to be displayed in the chart. Needs to be an array of objects.",
},
nameKey: {
description:
"Specifies the key in the data objects that will be used to label the different data series.",
valueType: "string",
},
dataKey: {
description:
"This property specifies the key in the data objects that will be used to render the chart.",
valueType: "string",
},
showLabel: {
description: "Toggles whether to show labels (\`true\`) or not (\`false\`).",
valueType: "boolean",
defaultValue: defaultPropsDonut.showLabel,
},
innerRadius: {
description: "Sets the inner radius of the donut chart.",
valueType: "number",
defaultValue: defaultPropsDonut.innerRadius,
},
showLabelList: {
description: "Whether to show labels in a list (\`true\`) or not (\`false\`).",
valueType: "boolean",
defaultValue: defaultPropsDonut.showLabelList,
},
showLegend: {
description: "Whether to show a legend (\`true\`) or not (\`false\`).",
valueType: "boolean",
defaultValue: defaultPropsDonut.showLegend,
},
},
themeVars: parseScssVar(styles.themeVars),
defaultThemeVars: {
"textColor-labelList-PieChart": "$textColor-primary",
},
});
export const donutChartComponentRenderer = createComponentRenderer(
COMP,
DonutChartMd,
({ extractValue, node, className, renderChild }) => {
return (
<PieChart
showLabelList={extractValue.asOptionalBoolean(
node.props?.showLabelList,
defaultPropsDonut.showLabelList,
)}
innerRadius={extractValue.asOptionalNumber(
node.props?.innerRadius,
defaultPropsDonut.innerRadius,
)}
data={extractValue(node.props?.data)}
className={className}
showLabel={extractValue.asOptionalBoolean(
node.props?.showLabel,
defaultPropsDonut.showLabel,
)}
dataKey={extractValue(node.props?.dataKey)}
nameKey={extractValue(node.props?.nameKey)}
showLegend={extractValue.asOptionalBoolean(
node.props?.showLegend,
defaultPropsDonut.showLegend,
)}
>
{renderChild(node.children)}
</PieChart>
);
},
);
```
--------------------------------------------------------------------------------
/xmlui/src/components/List/List.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);
}
@layer components {
.outerWrapper {
overflow: auto;
//max-height: 100%;
overflow-anchor: none;
&.hasOutsideScroll {
overflow: initial;
}
}
.innerWrapper {
visibility: hidden;
display: flex;
flex-direction: column;
min-height: 100%;
&.reverse {
justify-content: flex-end;
}
}
.infoWrapper {
width: 100%;
margin: 0 auto;
}
.loadingWrapper {
display: flex;
flex-direction: row;
justify-content: center;
padding-top: t.$space-2;
padding-bottom: t.$space-2;
}
.noRows {
width: 100%;
text-align: center;
margin-top: t.$space-4;
}
// --- We export the theme variables to add them to the component renderer
:export {
themeVars: t.json-stringify($themeVars);
}
.borderCollapse {
&:not(.sectioned) {
.row {
//first row in the list (but not the last)
&:first-child:not(:last-child) {
& > * {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
border-bottom: 0;
}
}
//last row in the list (but not the first)
&:last-child:not(:first-child) {
& > * {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
}
//rows in the middle of the list
&:not(:first-child):not(:last-child) {
& > * {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
border-top-left-radius: 0;
border-top-right-radius: 0;
border-bottom: 0;
}
}
}
}
&.sectioned {
//.row after the section header (first row in a section)
.section + .row:not(:has(+ .sectionFooter)) {
& > * {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
border-bottom: 0;
}
}
//.row before the sectionFooter (last row in a section)
.row:has(+ .sectionFooter):not(.section + .row) {
& > * {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
}
//we select the rows that has .row as direct sibling, but not the first row of a section (rows in the middle of the section)
.row:has(+ .row):not(.section + .row) {
& > * {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
border-top-left-radius: 0;
border-top-right-radius: 0;
border-bottom: 0;
}
}
}
}
}
// --- We export the theme variables to add them to the component renderer
:export {
themeVars: t.json-stringify($themeVars);
}
```
--------------------------------------------------------------------------------
/xmlui/src/components/ToneSwitch/ToneSwitch.spec.ts:
--------------------------------------------------------------------------------
```typescript
import { expect, test } from "../../testing/fixtures";
// =============================================================================
// BASIC FUNCTIONALITY TESTS
// =============================================================================
test.describe("Basic Functionality", () => {
test("renders in light mode by default", async ({ initTestBed, page }) => {
await initTestBed(`
<App>
<ToneSwitch />
<Text>{activeThemeTone}</Text>
</App>
`);
const toggle = page.getByRole("switch");
await expect(toggle).toBeVisible();
await expect(page.getByText("light")).toBeVisible();
});
test("toggles to dark mode when clicked", async ({ initTestBed, page }) => {
await initTestBed(`
<App>
<ToneSwitch />
<Text>{activeThemeTone}</Text>
</App>
`);
const toggle = page.getByRole("switch");
await expect(toggle).toBeVisible();
await toggle.click({ force: true });
await expect(page.getByText("dark")).toBeVisible();
await expect(toggle).toBeChecked();
});
test("toggles back to light mode when clicked again", async ({ initTestBed, page }) => {
await initTestBed(`
<App>
<ToneSwitch />
<Text>{activeThemeTone}</Text>
</App>
`);
const toggle = page.getByRole("switch");
await expect(toggle).toBeVisible();
await toggle.click({ force: true });
await expect(page.getByText("dark")).toBeVisible();
await expect(toggle).toBeChecked();
await toggle.click({ force: true });
await expect(page.getByText("light")).toBeVisible();
await expect(toggle).not.toBeChecked();
});
});
// =============================================================================
// ACCESSIBILITY TESTS
// =============================================================================
test.describe("Accessibility", () => {
test("has switch role", async ({ initTestBed, page }) => {
await initTestBed(`<ToneSwitch />`);
const toggle = page.getByRole("switch");
await expect(toggle).toBeVisible();
});
test("is keyboard accessible with Space key", async ({ initTestBed, page }) => {
await initTestBed(`<ToneSwitch />`);
const toggle = page.getByRole("switch");
await toggle.focus();
await expect(toggle).toBeFocused();
await page.keyboard.press("Space");
await expect(toggle).toBeChecked();
});
test("maintains focus during interactions", async ({ initTestBed, page }) => {
await initTestBed(`<ToneSwitch />`);
const toggle = page.getByRole("switch");
await toggle.focus();
await toggle.click({ force: true });
await expect(toggle).toBeFocused();
});
test("has appropriate aria-checked state", async ({ initTestBed, page }) => {
await initTestBed(`<ToneSwitch />`);
const toggle = page.getByRole("switch");
await expect(toggle).toHaveAttribute("aria-checked", "false");
await toggle.click({ force: true });
await expect(toggle).toHaveAttribute("aria-checked", "true");
});
});
```
--------------------------------------------------------------------------------
/xmlui/src/components/Charts/PieChart/PieChart.tsx:
--------------------------------------------------------------------------------
```typescript
import { defaultProps, PieChart } from "./PieChartNative";
import styles from "./PieChartNative.module.scss";
import { LabelPositionValues } from "../utils/abstractions";
import { parseScssVar } from "../../../components-core/theming/themeVars";
import { createComponentRenderer } from "../../../components-core/renderers";
import type { LabelPosition } from "recharts/types/component/Label";
import { createMetadata, d } from "../../metadata-helpers";
const COMP = "PieChart";
export const PieChartMd = createMetadata({
status: "experimental",
description:
"`PieChart` visualizes proportional data as circular segments; each slice " +
"represents a percentage of the whole. Note that the height of the component or " +
"its parent needs to be set explicitly.",
docFolder: "Charts/PieChart",
props: {
data: {
description: "The data to be displayed in the chart. Needs to be an array of objects.",
},
nameKey: {
description:
"Specifies the key in the data objects that will be used to label the different data series.",
valueType: "string",
},
dataKey: {
description:
"This property specifies the key in the data objects that will be used to render the chart.",
valueType: "string",
},
showLabel: {
description: "Toggles whether to show labels (\`true\`) or not (\`false\`).",
valueType: "boolean",
defaultValue: defaultProps.showLabel,
},
showLabelList: {
description: "Whether to show labels in a list (\`true\`) or not (\`false\`).",
valueType: "boolean",
defaultValue: defaultProps.showLabelList,
},
labelListPosition: {
description: "The position of the label list.",
valueType: "string",
defaultValue: defaultProps.labelListPosition,
availableValues: LabelPositionValues,
},
outerRadius: d(
"The outer radius of the pie chart, can be a number or a string (e.g., '100%').",
),
showLegend: {
description: "Toggles whether to show legend (\`true\`) or not (\`false\`).",
valueType: "boolean",
defaultValue: defaultProps.showLegend,
}
},
themeVars: parseScssVar(styles.themeVars),
defaultThemeVars: {
"textColor-labelList-PieChart": "$textColor-primary",
},
});
export const pieChartComponentRenderer = createComponentRenderer(
COMP,
PieChartMd,
({ extractValue, node, className, renderChild }) => {
return (
<PieChart
showLabelList={extractValue.asOptionalBoolean(node.props?.showLabelList)}
labelListPosition={extractValue.asOptionalString(node.props?.labelListPosition) as LabelPosition}
data={extractValue(node.props?.data)}
className={className}
showLabel={extractValue.asOptionalBoolean(node.props?.showLabel)}
showLegend={extractValue.asOptionalBoolean(node.props?.showLegend)}
dataKey={extractValue(node.props?.dataKey)}
nameKey={extractValue(node.props?.nameKey)}
>
{renderChild(node.children)}
</PieChart>
);
},
);
```
--------------------------------------------------------------------------------
/xmlui/src/components/Tooltip/Tooltip.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: "Tooltip";
$themeVars: t.composePaddingVars($themeVars, $component);
$themeVars: t.composeBorderVars($themeVars, $component);
// --- Theme variables for Tooltip component
$backgroundColor-Tooltip: createThemeVar("backgroundColor-#{$component}");
$textColor-Tooltip: createThemeVar("textColor-#{$component}");
$fontSize-Tooltip: createThemeVar("fontSize-#{$component}");
$lineHeight-Tooltip: createThemeVar("lineHeight-#{$component}");
$boxShadow-Tooltip: createThemeVar("boxShadow-#{$component}");
$fill-arrow-Tooltip: createThemeVar("fill-arrow-#{$component}");
$stroke-arrow-Tooltip: createThemeVar("stroke-arrow-#{$component}");
$strokeWidth-arrow-Tooltip: createThemeVar("strokeWidth-arrow-#{$component}");
$animationDuration-Tooltip: createThemeVar("animationDuration-#{$component}");
$animation-Tooltip: createThemeVar("animation-#{$component}");
/* Tooltip styles */
.hiddenTrigger {
position: fixed;
left: 0;
top: 0;
width: 1px;
height: 1px;
pointer-events: none;
z-index: -1;
opacity: 0;
}
.content {
@include t.paddingVars($themeVars, $component);
@include t.borderVars($themeVars, $component);
font-size: $fontSize-Tooltip;
line-height: $lineHeight-Tooltip;
color: $textColor-Tooltip;
background-color: $backgroundColor-Tooltip;
box-shadow: $boxShadow-Tooltip;
user-select: none;
animation-duration: $animationDuration-Tooltip;
animation-timing-function: $animation-Tooltip;
will-change: transform, opacity;
z-index: 9999;
}
.content[data-state='delayed-open'][data-side='top'] {
animation-name: slideDownAndFade;
}
.content[data-state='delayed-open'][data-side='right'] {
animation-name: slideLeftAndFade;
}
.content[data-state='delayed-open'][data-side='bottom'] {
animation-name: slideUpAndFade;
}
.content[data-state='delayed-open'][data-side='left'] {
animation-name: slideRightAndFade;
}
.arrow {
fill: $fill-arrow-Tooltip;
stroke: $stroke-arrow-Tooltip;
stroke-width: $strokeWidth-arrow-Tooltip;
}
@keyframes slideUpAndFade {
from {
opacity: 0;
transform: translateY(2px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideRightAndFade {
from {
opacity: 0;
transform: translateX(-2px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes slideDownAndFade {
from {
opacity: 0;
transform: translateY(-2px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideLeftAndFade {
from {
opacity: 0;
transform: translateX(2px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
// --- We export the theme variables to add them to the component renderer
:export {
themeVars: t.json-stringify($themeVars);
}
```
--------------------------------------------------------------------------------
/xmlui/tests/parsers/scripting/eval-tree-arrow.test.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, expect, it } from "vitest";
import { evalBindingExpression } from "../../../src/components-core/script-runner/eval-tree-sync";
import { createEvalContext } from "./test-helpers";
describe("Evaluate arrow expressions", () => {
it("Arrow #1", () => {
// --- Arrange
const source = "(x => 2 * x)(4)";
const context = createEvalContext({});
// --- Act
const value = evalBindingExpression(source, context);
// --- Arrange
expect(value).equal(8);
});
it("Arrow #2", () => {
// --- Arrange
const source = "((x, y) => x + y)(1, 2)";
const context = createEvalContext({});
// --- Act
const value = evalBindingExpression(source, context);
// --- Arrange
expect(value).equal(3);
});
it("Arrow #3", () => {
// --- Arrange
const source = "((x, y) => { return x + y })(1, 2)";
const context = createEvalContext({});
// --- Act
const value = evalBindingExpression(source, context);
// --- Arrange
expect(value).equal(3);
});
it("Arrow #4", () => {
// --- Arrange
const source = "(x => (++x.h))(count)";
const context = createEvalContext({
localContext: {
count: { h: 3 },
},
});
// --- Act
const value = evalBindingExpression(source, context);
// --- Arrange
expect(value).equal(4);
});
it("Arrow #5", () => {
// --- Arrange
const source = "(x => x += 2)(count)";
const context = createEvalContext({
localContext: {
count: 3,
},
});
// --- Act
const value = evalBindingExpression(source, context);
// --- Arrange
expect(value).equal(5);
});
it("Arrow #6", () => {
// --- Arrange
const source = "(x => x += 2)(count + 4)";
const context = createEvalContext({
localContext: {
count: 3,
},
});
// --- Act
const value = evalBindingExpression(source, context);
// --- Arrange
expect(value).equal(9);
});
it("Arrow #7", () => {
// --- Arrange
const source = "[1,2,3,4,5].filter(x => x % 2 === 0)[1]";
const context = createEvalContext({
localContext: {
count: 3,
},
});
// --- Act
const value = evalBindingExpression(source, context);
// --- Arrange
expect(value).equal(4);
});
it("Arrow #8", () => {
// --- Arrange
const source = "containsArray.array.filter(item => item % 2 === 0)[1]";
const context = createEvalContext({
localContext: {
containsArray: {
array: [5, 4, 3, 2, 1],
},
},
});
// --- Act
const value = evalBindingExpression(source, context);
// --- Arrange
expect(value).equal(2);
});
it("Arrow #9", () => {
// --- Arrange
const source = "array.reduce((acc, item) => acc + item, 0)";
const context = createEvalContext({
localContext: {
array: [5, 4, 3, 2, 1],
},
});
// --- Act
const value = evalBindingExpression(source, context);
// --- Arrange
expect(value).equal(15);
});
});
```
--------------------------------------------------------------------------------
/xmlui/src/components-core/script-runner/asyncProxy.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Gets a proxy function for one that does not support async operations
* @param fn Function to replace with a proxy
* @param origArgs Original function arguments
* @param context Function context ("this" of the function invocation)
* @return The proxy, if found; otherwise the original function
*/
export function getAsyncProxy(fn: Function, origArgs: any[], context: any): Function {
const proxyFn = asyncProxies.get(fn);
if (!proxyFn) return fn;
origArgs.unshift(context);
return proxyFn;
}
// Async implementations for JavaScript functions that do not support async arguments
const asyncProxies = new Map<Function, Function>();
asyncProxies.set(Array.prototype.filter, asyncFilter);
asyncProxies.set(Array.prototype.forEach, asyncForEach);
asyncProxies.set(Array.prototype.map, asyncMap);
asyncProxies.set(Array.prototype.every, asyncEvery);
asyncProxies.set(Array.prototype.findIndex, asyncFindIndex);
asyncProxies.set(Array.prototype.find, asyncFind);
asyncProxies.set(Array.prototype.flatMap, asyncFlatMap);
asyncProxies.set(Array.prototype.some, asyncSome);
// The async implementation of Array.prototype.some
async function asyncSome(arr: any[], predicate: (...args: any[]) => boolean) {
const results = await Promise.all(arr.map(predicate));
return arr.some((_v, index) => results[index]);
}
// The async implementation of Array.prototype.filter
async function asyncFilter(arr: any[], predicate: (...args: any[]) => boolean) {
const results = await Promise.all(arr.map(predicate));
return arr.filter((_v, index) => results[index]);
}
// The async implementation of Array.prototype.forEach
async function asyncForEach(arr: any[], predicate: (...args: any[]) => void) {
for (let i = 0; i < arr.length; i++) {
await predicate(arr[i], i, arr);
}
}
// The async implementation of Array.prototype.map
async function asyncMap(arr: any[], predicate: (...args: any[]) => Promise<any[]>) {
const result = [];
for (let i = 0; i < arr.length; i++) {
result.push(await predicate(arr[i], i, arr));
}
return result;
}
// The async implementation of Array.prototype.asyncEvery
async function asyncEvery(arr: any[], callback: (...args: any[]) => any) {
const results = await Promise.all(arr.map(callback));
return results.every((_v, index) => results[index]);
}
// The async implementation of Array.prototype.asyncFind
async function asyncFind(arr: any[], predicate: (...args: any[]) => boolean) {
const results = await Promise.all(arr.map(predicate));
return arr.find((_v, index) => results[index]);
}
// The async implementation of Array.prototype.asyncFindIndex
async function asyncFindIndex(arr: any[], predicate: (...args: any[]) => boolean) {
const results = await Promise.all(arr.map(predicate));
return arr.findIndex((_v, index) => results[index]);
}
// The async implementation of Array.prototype.asyncFlatMap
async function asyncFlatMap(arr: any[], predicate: (...args: any[]) => boolean) {
const results = await Promise.all(arr.map(predicate));
return arr.flatMap((_v, index) => results[index]);
}
```
--------------------------------------------------------------------------------
/docs/public/pages/user-defined-components.md:
--------------------------------------------------------------------------------
```markdown
# User-defined components
You can define your own components, pass properties to them, and use them interchangeably with core components. When you find yourself writing a component with more than a few dozen lines of XMLUI markup, consider refactoring in order to name and package the key blocks of code. This strategy not only enables reuse but, just as importantly, ensures that the refactored component will be easy to read and maintain.
Such refactoring requires you to create and name new `.xmlui` files, identify inline elements that need to be passed as properties from a refactored component, and use those properties in newly-created subcomponents. Historically that kind of overhead has been a disincentive to refactoring in any programming environment. Now you can often outsource that gruntwork to AI assistants. It works particularly well with XMLUI, we use this strategy extensively, and we highly recommend it.
Here's a simple component to package a name/value pair.
```xmlui-pg display noHeader
---app display
<App>
<NameValue name="Mary" value="123" />
</App>
---comp display
<Component name="NameValue">
<Card width="20%">
<Text>Name: { $props.name} </Text>
<Text>Value: { $props.value} </Text>
</Card>
</Component>
```
The component's name must start with an uppercase letter followed by letters, digits, the underscore (`_`), or the dollar sign (`$`) character. Components must be placed into separate files in the `components` folder within the app's root folder. The component's name must match its filename.
Here's how you can define default values for properties.
```xmlui
<Component name="NameValue">
<Card width="20%">
<Text>Name: { $props.name ?? '[no name]' } </Text>
<Text>Value: { $props.value ?? '[no value]' } </Text>
</Card>
</Component>
```
## Events
The `<IncButton>` component increments its value for every click, and notifies its environment by firing an event. The event's handler receives the current counter as an event parameter.
```xmlui-pg noHeader
---app display
<App>
<Card width="30%">
<variable name="text" value=""/>
<IncButton onIncremented="(clickCount) => text += ' ' + clickCount" />
<Text value="{text}" />
</Card>
</App>
---comp display
<Component name="IncButton">
<variable name="count" value="{0}" />
<Button
label="Click to increment: {count}"
onClick="count++; emitEvent('incremented', count)"
/>
</Component>
```
## Methods
The `<UsingInternalModal>` component exports the `open` method of the `ModalDialog` that it defines.
```xmlui-pg noHeader
---app display
<App height="300px" >
<UsingInternalModal id="component"/>
<Button label="Open the internal dialog" onClick="component.openDialog()" />
</App>
---comp display
<Component name="UsingInternalModal">
<ModalDialog id="dialog" title="Example Dialog">
<Button label="Close Dialog" onClick="dialog.close()" />
</ModalDialog>
<H1>Using an Internal Modal Dialog</H1>
<method name="openDialog">
console.log('internal method called')
dialog.open();
</method>
</Component>
```
```
--------------------------------------------------------------------------------
/xmlui/src/components/Accordion/Accordion.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: "Accordion";
$themeVars: t.composeBorderVars($themeVars, $component);
$themeVars: t.composePaddingVars($themeVars, $component);
$borderRadius: createThemeVar("borderRadius-#{$component}");
@layer components {
.root {
overflow: hidden;
width: 100%;
//box-shadow: 0 2px 10px var(--black-a4);
@include t.borderVars($themeVars, $component);
@include t.paddingVars($themeVars, $component);
}
.item {
overflow: hidden;
margin-top: 1px;
box-sizing: border-box;
&:first-child {
margin-top: 0;
border-top-left-radius: $borderRadius;
border-top-right-radius: $borderRadius;
}
&:last-child {
border-bottom-left-radius: $borderRadius;
border-bottom-right-radius: $borderRadius;
}
&:focus-within {
position: relative;
z-index: 1;
}
}
.header {
display: flex;
margin: 0;
}
.trigger {
font-family: inherit;
flex: 1;
display: inline-flex;
align-items: createThemeVar("verticalAlignment-header-#{$component}");
justify-content: space-between;
line-height: 1;
box-sizing: border-box;
font-size: createThemeVar("fontSize-header-#{$component}");
font-weight: createThemeVar("fontWeight-header-#{$component}");
font-style: createThemeVar("fontStyle-header-#{$component}");
padding: createThemeVar("paddingVertical-header-#{$component}") createThemeVar("paddingHorizontal-header-#{$component}");
background-color: createThemeVar("backgroundColor-header-#{$component}");
color: createThemeVar("color-header-#{$component}");
&:hover {
background-color: createThemeVar("backgroundColor-header-#{$component}-hover");
}
&.triggerStart {
flex-direction: row-reverse;
}
}
.contentWrapper {
overflow: hidden;
color: createThemeVar("color-content-#{$component}");
background-color: createThemeVar("backgroundColor-content-#{$component}");
&[data-state='open'] {
animation: slideDown 300ms cubic-bezier(0.87, 0, 0.13, 1);
}
&[data-state='closed'] {
animation: slideUp 300ms cubic-bezier(0.87, 0, 0.13, 1);
}
}
.content {
padding: t.$space-2;
}
.chevron {
width: createThemeVar("width-icon-#{$component}");
height: createThemeVar("height-icon-#{$component}");
color: createThemeVar("color-icon-#{$component}");
}
@keyframes slideDown {
from {
height: 0;
}
to {
height: var(--radix-accordion-content-height);
}
}
@keyframes slideUp {
from {
height: var(--radix-accordion-content-height);
}
to {
height: 0;
}
}
}
// --- We export the theme variables to add them to the component renderer
:export {
themeVars: t.json-stringify($themeVars);
}
```
--------------------------------------------------------------------------------
/packages/xmlui-animations/src/FadeAnimation.tsx:
--------------------------------------------------------------------------------
```typescript
import { createComponentRenderer, createMetadata } from "xmlui";
import { Animation, defaultProps } from "./AnimationNative";
const COMP = "FadeAnimation";
const defaultAnimationValues = {
from: 0,
to: 1,
};
export const FadeAnimationMd = createMetadata({
status: "experimental",
description: `The \`${COMP}\` component represents an animation that fades the content with adjustable opacity values.`,
docFolder: "src",
props: {
from: {
description:
"Sets the initial opacity of the content." +
"If the `to` property is not set, the initial opacity set here will be used as the final opacity.",
valueType: "number",
defaultValue: defaultAnimationValues.from,
},
to: {
description:
"Sets the final opacity of the content." +
"If the `from` property is not set, the initial opacity set here will be used as the final opacity.",
valueType: "number",
defaultValue: defaultAnimationValues.to,
},
animateWhenInView: {
description: `Indicates whether the animation should start when the component is in view`,
valueType: "boolean",
},
duration: {
description: `The duration of the animation in milliseconds`,
valueType: "number",
},
reverse: {
description: `Indicates whether the animation should run in reverse`,
defaultValue: defaultProps.reverse,
valueType: "boolean",
},
loop: {
description: `Indicates whether the animation should loop`,
defaultValue: defaultProps.loop,
valueType: "boolean",
},
delay: {
description: `The delay before the animation starts in milliseconds`,
defaultValue: defaultProps.delay,
valueType: "number",
},
},
events: {
started: { description: `Event fired when the animation starts` },
stopped: { description: `Event fired when the animation stops` },
},
apis: {
start: { description: `Starts the animation` },
stop: { description: `Stops the animation` },
},
});
export const fadeAnimationRenderer = createComponentRenderer(
"FadeAnimation",
FadeAnimationMd,
({ node, renderChild, extractValue, registerComponentApi, lookupEventHandler }) => {
return (
<Animation
registerComponentApi={registerComponentApi}
animation={{
from: {
opacity: extractValue.asOptionalNumber(node.props.from, defaultAnimationValues.from),
},
to: {
opacity: extractValue.asOptionalNumber(node.props.to, defaultAnimationValues.to),
},
}}
duration={extractValue.asOptionalNumber(node.props.duration)}
onStop={lookupEventHandler("stopped")}
onStart={lookupEventHandler("started")}
animateWhenInView={extractValue.asOptionalBoolean(node.props.animateWhenInView)}
reverse={extractValue.asOptionalBoolean(node.props.reverse)}
loop={extractValue.asOptionalBoolean(node.props.loop)}
delay={extractValue.asOptionalNumber(node.props.delay)}
>
{renderChild(node.children)}
</Animation>
);
},
);
```
--------------------------------------------------------------------------------
/xmlui/tests/parsers/xmlui/transform.escape.test.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, expect, it } from "vitest";
import { ComponentDef } from "../../../src/abstractions/ComponentDefs";
import { transformSource } from "./xmlui";
describe("Xmlui transform - no backslash escape", () => {
it("backslash", () => {
const cd = transformSource("<Stack a='\\\\' />") as ComponentDef;
expect((cd.props as any).a).equal("\\\\");
});
it("newline", () => {
const cd = transformSource("<Stack a='\\n' />") as ComponentDef;
expect((cd.props as any).a).equal("\\n");
});
});
describe("Xmlui transform - entity escape", () => {
it("&", () => {
const cd = transformSource("<Stack>&</Stack>") as ComponentDef;
expect((cd.children![0].props as any).value).toEqual("&");
});
it(">", () => {
const cd = transformSource("<Stack>></Stack>") as ComponentDef;
expect((cd.children![0].props as any).value).toEqual(">");
});
it("<", () => {
const cd = transformSource("<Stack><</Stack>") as ComponentDef;
expect((cd.children![0].props as any).value).toEqual("<");
});
it("'", () => {
const cd = transformSource("<Stack>'</Stack>") as ComponentDef;
expect((cd.children![0].props as any).value).toEqual("'");
});
it(""", () => {
const cd = transformSource("<Stack>"</Stack>") as ComponentDef;
expect((cd.children![0].props as any).value).toEqual('"');
});
it(" ", () => {
const cd = transformSource("<Stack> </Stack>") as ComponentDef;
expect((cd.children![0].props as any).value).toEqual("\xa0\xa0\xa0");
});
it("combined", () => {
const cd = transformSource("<Stack>"&abc><</Stack>") as ComponentDef;
expect((cd.children![0].props as any).value).toEqual('"&abc><');
});
it("& ignored inside CData", () => {
const cd = transformSource("<Stack><![CDATA[&]]></Stack>") as ComponentDef;
expect((cd.children![0].props as any).value).toEqual("&");
});
it("attr - &", () => {
const cd = transformSource("<H1 value='&' />") as ComponentDef;
expect((cd.props as any).value).toEqual("&");
});
it("attr - >", () => {
const cd = transformSource("<H1 value='>' />") as ComponentDef;
expect((cd.props as any).value).toEqual(">");
});
it("attr - ", () => {
const cd = transformSource("<H1 value=' ' />") as ComponentDef;
expect((cd.props as any).value).toEqual("\xa0\xa0\xa0");
});
it("attr - <", () => {
const cd = transformSource("<H1 value='<' />") as ComponentDef;
expect((cd.props as any).value).toEqual("<");
});
it("attr - '", () => {
const cd = transformSource("<H1 value=''' />") as ComponentDef;
expect((cd.props as any).value).toEqual("'");
});
it("attr - "", () => {
const cd = transformSource("<H1 value='"' />") as ComponentDef;
expect((cd.props as any).value).toEqual('"');
});
it("attr - combined", () => {
const cd = transformSource("<H1 value='"&abc>' />") as ComponentDef;
expect((cd.props as any).value).toEqual('"&abc>');
});
});
```
--------------------------------------------------------------------------------
/docs/public/resources/files/products.json:
--------------------------------------------------------------------------------
```json
[{"id":14,"name":"API Integration","description":"Third-party API integration services","price":105,"created_at":"2022-08-10T14:06:49.463Z"},
{"id":22,"name":"Brand Strategy Consulting","description":"Strategic brand positioning and messaging","price":115,"created_at":"2025-06-06 00:02:08"},
{"id":8,"name":"Cloud Migration","description":"Migrate systems to cloud infrastructure","price":130,"created_at":"2022-10-20T02:24:30.991Z"},
{"id":6,"name":"Content Creation","description":"Blog posts and content marketing","price":65,"created_at":"2023-02-27T14:54:28.774Z"},
{"id":13,"name":"Data Analysis","description":"Data analytics and reporting","price":90,"created_at":"2022-05-10T21:05:04.425Z"},
{"id":7,"name":"Database Design","description":"Database architecture and optimization","price":110,"created_at":"2022-02-06T12:47:47.397Z"},
{"id":19,"name":"Domain Registration (Yearly)","description":"Domain name registration and renewal","price":15,"created_at":"2023-03-08T06:10:10.884Z"},
{"id":21,"name":"E-commerce Development","description":"Online store development and integration","price":135,"created_at":"2025-06-06 00:02:08"},
{"id":12,"name":"Email Marketing","description":"Email campaign setup and management","price":60,"created_at":"2022-03-07T00:58:53.758Z"},
{"id":11,"name":"Logo Design","description":"Professional logo design services","price":80,"created_at":"2022-06-29T23:21:01.184Z"},
{"id":4,"name":"Mobile App Development","description":"Native mobile application development","price":145,"created_at":"2022-04-27T01:31:14.995Z"},
{"id":15,"name":"Project Management","description":"Professional project management services","price":95,"created_at":"2022-07-29T06:59:59.756Z"},
{"id":16,"name":"QA Testing","description":"Software quality assurance testing","price":80,"created_at":"2022-05-19T05:35:07.028Z"},
{"id":5,"name":"SEO Optimization","description":"Search engine optimization services","price":85,"created_at":"2022-09-27T13:12:58.057Z"},
{"id":9,"name":"Security Audit","description":"Comprehensive security assessment","price":150,"created_at":"2022-02-26T22:17:19.018Z"},
{"id":3,"name":"Server Maintenance","description":"Monthly server maintenance and updates","price":75,"created_at":"2022-02-27T00:11:24.402Z"},
{"id":10,"name":"Social Media Management","description":"Monthly social media account management","price":70,"created_at":"2022-03-17T19:11:09.894Z"},
{"id":17,"name":"Tech Consultation","description":"Technology consultation services (hourly)","price":125,"created_at":"2022-07-09T22:04:19.424Z"},
{"id":2,"name":"UI/UX Design","description":"User interface and experience design","price":95,"created_at":"2022-02-08T19:22:16.697Z"},
{"id":20,"name":"Video Production","description":"Professional video production services","price":175,"created_at":"2023-05-07T22:52:06.967Z"},
{"id":1,"name":"Web Development","description":"Full stack web application development","price":120,"created_at":"2022-12-24T10:09:20.570Z"},
{"id":18,"name":"Website Hosting (Monthly)","description":"Managed website hosting services","price":45,"created_at":"2022-02-28T15:52:17.105Z"}]
```
--------------------------------------------------------------------------------
/xmlui/src/components/FlowLayout/FlowLayout.tsx:
--------------------------------------------------------------------------------
```typescript
import styles from "./FlowLayout.module.scss";
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 { FlowItemBreak, FlowItemWrapper, FlowLayout, defaultProps } from "./FlowLayoutNative";
import { createMetadata } from "../metadata-helpers";
const COMP = "FlowLayout";
export const FlowLayoutMd = createMetadata({
status: "stable",
description:
"`FlowLayout` positions content in rows with automatic wrapping. When items " +
"exceed the available horizontal space, they automatically wrap to a new line.",
props: {
gap: {
description:
`This property defines the gap between items in the same row and between rows. The ${COMP} ` +
`component creates a new row when an item is about to overflow the current row.`,
type: "string",
defaultValue: "$gap-normal",
},
columnGap: {
description:
"The \`columnGap\` property specifies the space between items in a single row; it overrides " +
"the \`gap\` value.",
defaultValue: defaultProps.columnGap,
},
rowGap: {
description:
`The \`rowGap\` property specifies the space between the ${COMP} rows; it overrides ` +
`the \`gap\` value.`,
defaultValue: defaultProps.rowGap,
},
},
themeVars: parseScssVar(styles.themeVars),
});
export const flowLayoutComponentRenderer = createComponentRenderer(
COMP,
FlowLayoutMd,
({ node, renderChild, className, extractValue }) => {
if (!isComponentDefChildren(node.children)) {
throw new NotAComponentDefError();
}
const columnGap =
extractValue.asSize(node.props?.columnGap) ||
extractValue.asSize(node.props?.gap) ||
extractValue.asSize("$space-4");
const rowGap =
extractValue.asSize(node.props?.rowGap) || extractValue.asSize(node.props?.gap) || extractValue.asSize("$space-4");
return (
<FlowLayout className={className} columnGap={columnGap} rowGap={rowGap}>
{renderChild(node.children, {
wrapChild: ({ node, extractValue }, renderedChild, hints) => {
if (hints?.opaque) {
return renderedChild;
}
// Handle SpaceFiller as flow item break
if (node.type === "SpaceFiller") {
return <FlowItemBreak force={true} />;
}
const width = extractValue((node.props as any)?.width);
const minWidth = extractValue((node.props as any)?.minWidth);
const maxWidth = extractValue((node.props as any)?.maxWidth);
return (
<FlowItemWrapper
width={width}
minWidth={minWidth}
maxWidth={maxWidth}
forceBreak={node.type === "SpaceFiller"}
>
{renderedChild}
</FlowItemWrapper>
);
},
})}
</FlowLayout>
);
},
);
```
--------------------------------------------------------------------------------
/xmlui/src/components/Markdown/CodeText.module.scss:
--------------------------------------------------------------------------------
```scss
@use "../../components-core/theming/themes" as t;
$themeVars: ();
@function createThemeVar($componentVariable) {
$themeVars: t.appendThemeVar($themeVars, $componentVariable) !global;
@return t.getThemeVar($themeVars, $componentVariable);
}
$component: "Text";
@mixin textVariant($variant) {
$variantName: if($variant == "", "#{$component}", "#{$component}-#{$variant}");
$themeVars: t.composePaddingVars($themeVars, $variantName);
$themeVars: t.composeBorderVars($themeVars, $variantName);
$themeVars: t.composeTextVars($themeVars, $variantName, $component);
@include t.paddingVars($themeVars, $variantName);
@include t.borderVars($themeVars, $variantName);
@include t.textVars($themeVars, $variantName);
margin-top: createThemeVar("marginTop-#{$variantName}");
margin-bottom: createThemeVar("marginBottom-#{$variantName}");
margin-left: createThemeVar("marginLeft-#{$variantName}");
margin-right: createThemeVar("marginRight-#{$variantName}");
vertical-align: createThemeVar("verticalAlignment-#{$variantName}");
}
@layer components {
.codeText {
overflow: hidden;
text-overflow: ellipsis;
margin: 0;
padding: 0;
min-height: fit-content;
// --- Styling <pre><code> elements
// codefence/<pre> elements are block level by default, even in another text element
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/pre
.text.codefence {
display: block;
margin-top: createThemeVar("marginTop-#{$component}-codefence");
margin-bottom: createThemeVar("marginBottom-#{$component}-codefence");
height: 100%;
}
// For inline code
:not(.text.codefence) > &.code,
:not(.text.codefence) > &code {
@include textVariant("code");
}
// For code blocks using <![CDATA[ + <code> tags
&.codefence:has(> .text.code),
&.codefence:has(> code) {
@include textVariant("codefence");
// TEMP: solution for a demo - need to re-evaluate how to truncate/wrap code blocks
overflow-x: auto;
text-overflow: initial;
}
// TEMP: experimental - may be removed
&.codefence:has(> code > span) {
padding-inline: 0;
}
&.codefence > code > span:not([class*="numbered"]) {
padding-inline: t.$space-3;
}
&.codefence > code {
font-family: createThemeVar("fontFamily-#{$component}-codefence");
//white-space: pre-wrap;
font-family: createThemeVar("fontFamily-#{$component}-codefence");
//display: inline-block;
width: 100%;
}
&.codefence > code::before {
counter-reset: listing;
}
&.codefence > code > span {
display: inline-block;
width: 100%;
}
&.codefence > code > span[class*="numbered"] {
counter-increment: listing;
}
&.codefence > code > span[class*="numbered"]::before {
content: counter(listing);
display: inline-block;
width: t.$space-12;
padding-left: auto;
padding-right: t.$space-5;
margin-left: auto;
text-align: right;
color: t.$textColor-secondary;
}
}
}
:export {
themeVars: t.json-stringify($themeVars);
}
```
--------------------------------------------------------------------------------
/packages/xmlui-website-blocks/src/Carousel/Carousel.module.scss:
--------------------------------------------------------------------------------
```scss
@use "xmlui/themes.scss" 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: "Carousel";
@layer components {
.carousel {
position: relative;
overflow: hidden;
width: createThemeVar("width-#{$component}");
height: createThemeVar("height-#{$component}");
justify-content: center;
align-items: center;
}
.innerWrapper {
display: flex;
width: 100%;
height: 100%;
flex-direction: row;
justify-content: center;
}
.carouselContentWrapper {
overflow: hidden;
height: 100%;
}
.carouselContent {
display: flex;
height: 100%;
}
.vertical {
flex-direction: column;
}
.carouselItem {
overflow: hidden;
min-width: 0;
flex-shrink: 0;
flex-grow: 0;
flex-basis: 100%;
}
.controls {
z-index: 99;
display: flex;
right: 1rem;
bottom: 1rem;
position: absolute;
gap: .6rem;
}
.controlButton {
transition: all .3s;
justify-content: center;
align-items: center;
display: inline-flex;
height: createThemeVar("height-control-#{$component}");
width: createThemeVar("width-control-#{$component}");
color: createThemeVar("textColor-control-#{$component}");
background-color: createThemeVar("backgroundColor-control-#{$component}");
;
border-radius: createThemeVar("borderRadius-control-#{$component}");
}
.controlButton:hover {
background-color: createThemeVar("backgroundColor-control-hover-#{$component}");
color: createThemeVar("textColor-control-hover-#{$component}");
}
.controlButton:active {
background-color: createThemeVar("backgroundColor-control-active-#{$component}");
color: createThemeVar("textColor-control-active-#{$component}");
}
.controlButton:disabled {
color: createThemeVar("textColor-control-disabled-#{$component}");
background-color: createThemeVar("backgroundColor-control-disabled-#{$component}");
}
.indicators {
position: absolute;
bottom: 1rem;
left: 50%;
transform: translateX(-50%);
}
.indicator {
transition: all .3s;
width: createThemeVar("width-indicator-#{$component}");
height: createThemeVar("height-indicator-#{$component}");
border-radius: 2px;
margin: 0 1px;
color: createThemeVar("textColor-indicator-#{$component}");
background-color: createThemeVar("backgroundColor-indicator-#{$component}");
&:hover {
background-color: createThemeVar("backgroundColor-indicator-hover-#{$component}");
color: createThemeVar("textColor-indicator-hover-#{$component}");
}
&.active {
background-color: createThemeVar("backgroundColor-indicator-active-#{$component}");
color: createThemeVar("textColor-indicator-active-#{$component}");
}
}
}
// --- We export the theme variables to add them to the component renderer
:export {
themeVars: t.json-stringify($themeVars);
}
```
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
```json
{
"$schema": "https://turborepo.com/schema.json",
"globalEnv": ["CI"],
"ui": "tui",
"envMode": "loose",
"tasks": {
"build:bin": {
"outputs": ["dist/scripts/**"]
},
"build:xmlui": {
"outputs": ["dist/lib/**"]
},
"build:xmlui-standalone": {
"outputs": ["dist/standalone/**"]
},
"build:xmlui-metadata": {
"inputs": ["$TURBO_DEFAULT$", "!src/language-server/xmlui-metadata-generated.mjs"],
"outputs": ["dist/metadata/**"]
},
"gen:langserver-metadata": {
"dependsOn": ["build:xmlui-metadata"],
"outputs": ["src/language-server/xmlui-metadata-generated.mjs"]
},
"build:extension": {
"dependsOn": ["^build:extension"],
"outputs": ["dist/**"]
},
"build:meta": {
"outputs": ["dist/**"]
},
"xmlui-vscode#build:vsix": {
"dependsOn": ["xmlui-vscode#build"],
"outputs": ["xmlui-vscode-*.vsix"]
},
"xmlui-vscode#build": {
"dependsOn": ["^gen:langserver-metadata"],
"outputs": ["dist/**"]
},
"build:xmlui-all": {
"dependsOn": [
"build:bin",
"build:xmlui-metadata",
"build:xmlui",
"build:xmlui-standalone",
"build:extension"
],
"outputs": ["dist/**"]
},
"gen:releases": {
"env": ["GITHUB_TOKEN", "DOCS_XMLUI_MAX_RELEASES_LENGTH"],
"outputs": ["public/resources/files/releases.json"]
},
"gen:download-latest-xmlui-release": {
"outputs": ["public/resources/files/for-download/xmlui/xmlui-standalone.umd.js"]
},
"generate-docs": {
"outputs": ["scripts/generate-docs/metadata/**", "$TURBO_ROOT$/docs/content/**"],
"inputs": ["$TURBO_DEFAULT$"],
"dependsOn": ["build:xmlui-metadata", "build:meta"]
},
"generate-docs-summaries": {
"outputs": ["$TURBO_ROOT$/docs/content/**"],
"inputs": ["$TURBO_DEFAULT$", "dist/metadata/**"],
"dependsOn": ["generate-docs"]
},
"build:docs": {
"dependsOn": [
"^build:extension",
"^build:xmlui",
"gen:releases",
"gen:download-latest-xmlui-release",
"generate-docs-summaries"
],
"outputs": ["dist/**"]
},
"build:blog": {
"dependsOn": [
"^build:extension",
"^build:xmlui"
],
"outputs": ["dist/**"]
},
"build:xmlui-test-bed": {
"dependsOn": ["build:extension"],
"outputs": ["src/testing/infrastructure/dist/**"]
},
"build:test-bed": {
"dependsOn": ["build:extension"],
"outputs": ["dist/**"]
},
"test:unit": {},
"test:e2e-smoke": {
"dependsOn": ["build:test-bed", "build:xmlui-test-bed"],
"outputs": ["playwright-report/**"]
},
"test:e2e-non-smoke": {
"dependsOn": ["build:test-bed", "build:xmlui-test-bed", "test:e2e-smoke"],
"outputs": ["playwright-report/**"]
},
"test:xmlui-all": {
"dependsOn": ["test:unit", "test:e2e-non-smoke"],
"outputs": ["playwright-report/**"]
},
"test:xmlui-smoke": {
"dependsOn": ["test:unit", "test:e2e-smoke"],
"outputs": ["playwright-report/**"]
}
}
}
```
--------------------------------------------------------------------------------
/xmlui/src/components/Splitter/Splitter.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: "Splitter";
$boxShadow-Splitter: createThemeVar("boxShadow-#{$component}");
$backgroundColor-Splitter: createThemeVar("backgroundColor-#{$component}");
$borderRadius-Splitter: createThemeVar("borderRadius-#{$component}");
$borderColor-Splitter: createThemeVar("borderColor-#{$component}");
$borderWidth-Splitter: createThemeVar("borderWidth-#{$component}");
$borderStyle-Splitter: createThemeVar("borderStyle-#{$component}");
$border-Splitter: createThemeVar("border-#{$component}");
$backgroundColor-resizer-Splitter: createThemeVar("backgroundColor-resizer-#{$component}");
$thickness-resizer-Splitter: createThemeVar("thickness-resizer-#{$component}");
$cursor-resizer-horizontal-Splitter: createThemeVar("cursor-resizer-horizontal-#{$component}");
$cursor-resizer-vertical-Splitter: createThemeVar("cursor-resizer-vertical-#{$component}");
@layer components {
.splitter {
overflow: hidden;
position: relative;
flex-wrap: nowrap;
display: flex;
height: 100%;
width: 100%;
box-shadow: $boxShadow-Splitter;
background-color: $backgroundColor-Splitter;
border-radius: $borderRadius-Splitter;
border: $border-Splitter $borderStyle-Splitter $borderColor-Splitter;
user-select: none;
&.vertical {
flex-direction: column;
}
&.horizontal {
flex-direction: row;
}
}
.panel {
overflow: hidden;
display: flex;
flex-direction: column;
flex: 1;
}
.primaryPanel {
flex-shrink: 0;
flex-grow: 0;
overflow: auto;
order: 1;
}
.secondaryPanel {
flex: 1;
overflow: auto;
order: 3;
}
.resizer {
order: 2;
overflow: hidden;
background-color: t.useVar($backgroundColor-resizer-Splitter, transparent);
&.vertical {
cursor: t.useVar($cursor-resizer-vertical-Splitter, ns-resize);
height: t.useVar($thickness-resizer-Splitter, 5px);
}
&.horizontal {
cursor: t.useVar($cursor-resizer-horizontal-Splitter, ew-resize);
width: t.useVar($thickness-resizer-Splitter, 5px);
}
}
.floatingResizer {
position: absolute;
z-index: 1000;
opacity: 0;
overflow: hidden;
transition: opacity 0.2s;
background-color: t.useVar($backgroundColor-resizer-Splitter, transparent);
&.vertical {
cursor: t.useVar($cursor-resizer-vertical-Splitter, ns-resize);
width: 100%;
height: t.useVar($thickness-resizer-Splitter, 5px);
transform: translateY(-50%);
}
&.horizontal {
cursor: t.useVar($cursor-resizer-horizontal-Splitter, ew-resize);
width: t.useVar($thickness-resizer-Splitter, 5px);
height: 100%;
transform: translateX(-50%);
}
}
}
// --- We export the theme variables to add them to the component renderer
:export {
themeVars: t.json-stringify($themeVars);
}
```
--------------------------------------------------------------------------------
/xmlui/src/components/Carousel/Carousel.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: "Carousel";
@layer components {
.carousel {
position: relative;
overflow: hidden;
width: createThemeVar("width-#{$component}");
height: createThemeVar("height-#{$component}");
justify-content: center;
align-items: center;
}
.innerWrapper {
display: flex;
width: 100%;
height: 100%;
flex-direction: row;
justify-content: center;
}
.carouselContentWrapper {
overflow: hidden;
height: 100%;
}
.carouselContent {
display: flex;
height: 100%;
}
.vertical {
flex-direction: column;
}
.carouselItem {
overflow: hidden;
min-width: 0;
flex-shrink: 0;
flex-grow: 0;
flex-basis: 100%;
}
.controls {
z-index: 99;
display: flex;
right: 1rem;
bottom: 1rem;
position: absolute;
gap: .6rem;
}
.controlButton {
transition: all .3s;
justify-content: center;
align-items: center;
display: inline-flex;
height: createThemeVar("height-control-#{$component}");
width: createThemeVar("width-control-#{$component}");
color: createThemeVar("textColor-control-#{$component}");
background-color: createThemeVar("backgroundColor-control-#{$component}");;
border-radius: createThemeVar("borderRadius-control-#{$component}");
}
.controlButton:hover {
background-color: createThemeVar("backgroundColor-control-hover-#{$component}");
color: createThemeVar("textColor-control-hover-#{$component}");
}
.controlButton:active {
background-color: createThemeVar("backgroundColor-control-active-#{$component}");
color: createThemeVar("textColor-control-active-#{$component}");
}
.controlButton:disabled {
color: createThemeVar("textColor-control-disabled-#{$component}");
background-color: createThemeVar("backgroundColor-control-disabled-#{$component}");
}
.indicators {
position: absolute;
bottom: 1rem;
left: 50%;
transform: translateX(-50%);
}
.indicator {
transition: all .3s;
width: createThemeVar("width-indicator-#{$component}");
height: createThemeVar("height-indicator-#{$component}");
border-radius: 2px;
margin: 0 1px;
color: createThemeVar("textColor-indicator-#{$component}");
background-color: createThemeVar("backgroundColor-indicator-#{$component}");
&:hover {
background-color: createThemeVar("backgroundColor-indicator-hover-#{$component}");
color: createThemeVar("textColor-indicator-hover-#{$component}");
}
&.active {
background-color: createThemeVar("backgroundColor-indicator-active-#{$component}");
color: createThemeVar("textColor-indicator-active-#{$component}");
}
}
}
// --- We export the theme variables to add them to the component renderer
:export {
themeVars: t.json-stringify($themeVars);
}
```
--------------------------------------------------------------------------------
/xmlui/tests/parsers/scripting/eval-tree-pre-post.test.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, expect, it } from "vitest";
import { evalBindingExpression } from "../../../src/components-core/script-runner/eval-tree-sync";
import { createEvalContext } from "./test-helpers";
describe("Evaluate prefix/postfix expressions", () => {
const prePostCases1 = [
{ src: "++x", ctx: { x: 0 }, exp: 1, varExp: 1 },
{ src: "--x", ctx: { x: 0 }, exp: -1, varExp: -1 },
{ src: "x++", ctx: { x: 0 }, exp: 0, varExp: 1 },
{ src: "x--", ctx: { x: 0 }, exp: 0, varExp: -1 }
];
prePostCases1.forEach(c => {
it(`Prefix/postfix '${c.src}'`, () => {
// --- Arrange
const context = createEvalContext({
localContext: c.ctx
});
// --- Act
const value = evalBindingExpression(c.src, context);
// --- Arrange
expect(value).equal(c.exp);
expect(context.localContext["x"]).equal(c.varExp);
});
});
const prePostCases2 = [
{ src: "++j[2]", exp: 4, varExp: 4 },
{ src: "--j[2]", exp: 2, varExp: 2 },
{ src: "j[1+1]++", exp: 3, varExp: 4 },
{ src: "j[4-2]--", exp: 3, varExp: 2 }
];
prePostCases2.forEach(c => {
it(`Prefix/postfix '${c.src}'`, () => {
// --- Arrange
const jArr = [1, 2, 3];
const context = createEvalContext({
localContext: {
j: jArr
}
});
// --- Act
const value = evalBindingExpression(c.src, context);
// --- Arrange
expect(value).equal(c.exp);
expect(context.localContext.j[2]).equal(c.varExp);
});
});
const prePostCases3 = [
{ src: "++j[2].h", exp: 4, varExp: 4 },
{ src: "--j[2].h", exp: 2, varExp: 2 },
{ src: "j[1+1].h++", exp: 3, varExp: 4 },
{ src: "j[4-2].h--", exp: 3, varExp: 2 }
];
prePostCases3.forEach(c => {
it(`Prefix/postfix '${c.src}'`, () => {
// --- Arrange
const jArr = [{ h: 1 }, { h: 2 }, { h: 3 }];
const context = createEvalContext({
localContext: {
j: jArr
}
});
// --- Act
const value = evalBindingExpression(c.src, context);
// --- Arrange
expect(value).equal(c.exp);
expect(context.localContext.j[2].h).equal(c.varExp);
});
});
const prePostCases4 = [
{ src: "++j.h[2]", exp: 4, varExp: 4 },
{ src: "--j.h[2]", exp: 2, varExp: 2 },
{ src: "j.h[1+1]++", exp: 3, varExp: 4 },
{ src: "j.h[4-2]--", exp: 3, varExp: 2 }
];
prePostCases4.forEach(c => {
it(`Prefix/postfix '${c.src}'`, () => {
// --- Arrange
const jArr = [1, 2, 3];
const context = createEvalContext({
localContext: {
j: { h: jArr }
}
});
// --- Act
const value = evalBindingExpression(c.src, context);
// --- Arrange
expect(value).equal(c.exp);
expect(context.localContext.j.h[2]).equal(c.varExp);
});
});
it("Postfix with arrow", () => {
// --- Arrange
const source = "(x => ++x)(count)";
const context = createEvalContext({
localContext: {
count: 6
}
});
// --- Act
const value = evalBindingExpression(source, context);
// --- Arrange
expect(value).equal(7);
});
});
```
--------------------------------------------------------------------------------
/packages/xmlui-animations/src/SlideInAnimation.tsx:
--------------------------------------------------------------------------------
```typescript
import { createComponentRenderer, createMetadata } from "xmlui";
import { Animation, defaultProps } from "./AnimationNative";
const COMP = "SlideInAnimation";
const defaultAnimationValues = {
direction: "left",
};
export const SlideInAnimationMd = createMetadata({
status: "experimental",
description: `The \`${COMP}\` component represents an animation that slides in the content from the left.`,
docFolder: "src",
props: {
direction: {
description: "The direction of the animation.",
valueType: "string",
options: ["left", "right", "top", "bottom"],
defaultValue: defaultAnimationValues.direction,
},
duration: {
description: "The duration of the animation in milliseconds.",
valueType: "number",
},
animateWhenInView: {
description: "Indicates whether the animation should start when the component is in view.",
valueType: "boolean",
},
reverse: {
description: `Indicates whether the animation should run in reverse`,
defaultValue: defaultProps.reverse,
valueType: "boolean",
},
loop: {
description: `Indicates whether the animation should loop`,
defaultValue: defaultProps.loop,
valueType: "boolean",
},
delay: {
description: `The delay before the animation starts in milliseconds`,
defaultValue: defaultProps.delay,
valueType: "number",
},
},
events: {
started: { description: `Event fired when the animation starts` },
stopped: { description: `Event fired when the animation stops` },
},
apis: {
start: { description: `Starts the animation` },
stop: { description: `Stops the animation` },
},
});
export const slideInAnimationRenderer = createComponentRenderer(
"SlideInAnimation",
SlideInAnimationMd,
({ node, renderChild, extractValue, registerComponentApi, lookupEventHandler }) => {
const direction = extractValue.asOptionalString(node.props.direction, defaultAnimationValues.direction);
const animation = {
from: {
transform:
direction === "right"
? "translateX(100%)"
: direction === "left"
? "translateX(-100%)"
: direction === "top"
? "translateY(-100%)"
: "translateY(100%)",
},
to: {
transform:
direction === "right" || direction === "left" ? "translateX(0)" : "translateY(0)",
},
};
return (
<Animation
registerComponentApi={registerComponentApi}
animation={animation}
duration={extractValue.asOptionalNumber(node.props.duration)}
onStop={lookupEventHandler("stopped")}
onStart={lookupEventHandler("started")}
animateWhenInView={extractValue.asOptionalBoolean(node.props.animateWhenInView)}
reverse={extractValue.asOptionalBoolean(node.props.reverse)}
loop={extractValue.asOptionalBoolean(node.props.loop)}
delay={extractValue.asOptionalNumber(node.props.delay)}
>
{renderChild(node.children)}
</Animation>
);
},
);
```
--------------------------------------------------------------------------------
/xmlui/tests/components-core/scripts-runner/eval-tree-pre-post.test.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, expect, it } from "vitest";
import { evalBindingExpression } from "../../../src/components-core/script-runner/eval-tree-sync";
import { createEvalContext } from "./test-helpers";
describe("Evaluate prefix/postfix expressions (exp)", () => {
const prePostCases1 = [
{ src: "++x", ctx: { x: 0 }, exp: 1, varExp: 1 },
{ src: "--x", ctx: { x: 0 }, exp: -1, varExp: -1 },
{ src: "x++", ctx: { x: 0 }, exp: 0, varExp: 1 },
{ src: "x--", ctx: { x: 0 }, exp: 0, varExp: -1 }
];
prePostCases1.forEach(c => {
it(`Prefix/postfix '${c.src}'`, () => {
// --- Arrange
const context = createEvalContext({
localContext: c.ctx
});
// --- Act
const value = evalBindingExpression(c.src, context);
// --- Arrange
expect(value).equal(c.exp);
expect(context.localContext["x"]).equal(c.varExp);
});
});
const prePostCases2 = [
{ src: "++j[2]", exp: 4, varExp: 4 },
{ src: "--j[2]", exp: 2, varExp: 2 },
{ src: "j[1+1]++", exp: 3, varExp: 4 },
{ src: "j[4-2]--", exp: 3, varExp: 2 }
];
prePostCases2.forEach(c => {
it(`Prefix/postfix '${c.src}'`, () => {
// --- Arrange
const jArr = [1, 2, 3];
const context = createEvalContext({
localContext: {
j: jArr
}
});
// --- Act
const value = evalBindingExpression(c.src, context);
// --- Arrange
expect(value).equal(c.exp);
expect(context.localContext.j[2]).equal(c.varExp);
});
});
const prePostCases3 = [
{ src: "++j[2].h", exp: 4, varExp: 4 },
{ src: "--j[2].h", exp: 2, varExp: 2 },
{ src: "j[1+1].h++", exp: 3, varExp: 4 },
{ src: "j[4-2].h--", exp: 3, varExp: 2 }
];
prePostCases3.forEach(c => {
it(`Prefix/postfix '${c.src}'`, () => {
// --- Arrange
const jArr = [{ h: 1 }, { h: 2 }, { h: 3 }];
const context = createEvalContext({
localContext: {
j: jArr
}
});
// --- Act
const value = evalBindingExpression(c.src, context);
// --- Arrange
expect(value).equal(c.exp);
expect(context.localContext.j[2].h).equal(c.varExp);
});
});
const prePostCases4 = [
{ src: "++j.h[2]", exp: 4, varExp: 4 },
{ src: "--j.h[2]", exp: 2, varExp: 2 },
{ src: "j.h[1+1]++", exp: 3, varExp: 4 },
{ src: "j.h[4-2]--", exp: 3, varExp: 2 }
];
prePostCases4.forEach(c => {
it(`Prefix/postfix '${c.src}'`, () => {
// --- Arrange
const jArr = [1, 2, 3];
const context = createEvalContext({
localContext: {
j: { h: jArr }
}
});
// --- Act
const value = evalBindingExpression(c.src, context);
// --- Arrange
expect(value).equal(c.exp);
expect(context.localContext.j.h[2]).equal(c.varExp);
});
});
it("Postfix with arrow", () => {
// --- Arrange
const source = "(x => ++x)(count)";
const context = createEvalContext({
localContext: {
count: 6
}
});
// --- Act
const value = evalBindingExpression(source, context);
// --- Arrange
expect(value).equal(7);
});
});
```
--------------------------------------------------------------------------------
/xmlui/src/components-core/script-runner/statement-queue.ts:
--------------------------------------------------------------------------------
```typescript
import type { Statement } from "./ScriptingSourceTree";
export type StatementRunTimeInfo = {
guard?: boolean;
keys?: string[];
keyIndex?: number;
iterator?: any;
removeBlockScope?: boolean;
};
export type StatementWithInfo = {
// --- The description of the statement to execute
statement: Statement;
// --- Execution information for the statement
execInfo?: StatementRunTimeInfo;
};
// An item of the queue we use for processing a statement
export type StatementQueueItem = StatementWithInfo & {
// --- The label of the item. We need this information for "break" and "continue" statements
label: number;
};
// The outcome of processing a statement of the queue
export type ProcessOutcome = {
// --- New statements to unshift into the queue (optional)
toUnshift?: StatementQueueItem[];
// --- Label from which statements should be preserved in the queue
clearToLabel?: number;
};
// --- Implements the statement queue
export class StatementQueue {
private items: StatementQueueItem[] = [];
// --- The current queue length
get length(): number {
return this.items.length;
}
// --- Get the subsequent item in the queue without dequeuing it
peek(): StatementQueueItem | undefined {
return this.items[0];
}
// --- Remove and return the subsequent item from the queue
dequeue(): StatementQueueItem | undefined {
return this.items.shift();
}
// --- Push the specified items into the queue and return the corresponding queue items
push(statements: StatementQueueItem[]): void {
this.items.push(...statements);
}
// --- Unshift the specified items into the queue in reverse order (starting from the last action) and
// --- return the corresponding queue items
unshift(statements: StatementQueueItem[]): void {
this.items.unshift(...statements);
}
// --- Remove all items starting from the beginning of the queue, while not reaching the item with the
// --- specified label. If the label is found, that remains in the queue; otherwise the queue is emptied
clearToLabel(label: number): void {
while (this.items.length > 0 && this.items[0].label !== label) {
this.items.shift();
}
}
}
// --- Queue information for test and diagnostics purposes
export type QueueInfo = {
processedStatements: number;
maxQueueLength: number;
unshiftedItems: number;
clearToLabels: number;
maxBlocks: number;
maxLoops: number;
};
// --- The ID of the last used label
let nextLabelValue = 1;
// --- Map a single item to a queue statement list
export function mapToItem(statement: Statement): StatementQueueItem[] {
return [
{
label: nextLabelValue++,
statement: { ...statement },
} as StatementQueueItem,
];
}
// --- Map a single statement into a queue item
function mapStatementToQueueItem(stmt: StatementWithInfo): StatementQueueItem {
return {
label: nextLabelValue++,
statement: stmt.statement,
execInfo: { ...stmt.execInfo },
};
}
// --- Map a list of statements into queue items
export function mapStatementsToQueueItems(statements: StatementWithInfo[]): StatementQueueItem[] {
return statements.map(mapStatementToQueueItem);
}
```
--------------------------------------------------------------------------------
/xmlui/src/components-core/interception/abstractions.ts:
--------------------------------------------------------------------------------
```typescript
import type { DelayMode } from "msw";
export type AuthDefinition = {
defaultLoggedInUser?: any
};
export type TableDescriptor = {
name: string,
fields?: Record<string, any>,
pk: Array<string>;
indexes?: Array<string>;
}
export type SchemaDescriptor = {
tables: Array<TableDescriptor>,
relationships?: any;
dtos?: any;
}
// Represents an interceptor between API consumers (UI in the browser) and their backend
export type ApiInterceptorDefinition = {
// Type of backend interceptor
type?: string;
// Interceptor configuration
config?: Record<string, any>;
artificialDelay?: number | DelayMode;
// Information for code generators
schemaDescriptor?: SchemaDescriptor
// URL to particular interceptor is bound to
apiUrl?: string;
// Initial data to set up (initialize) the interceptor
initialData?: Record<string, any[]> | (() => Promise<Record<string, any[]>>);
helpers?: Record<string, any>;
initialize?: string;
// Available operations
operations?: Record<string, InterceptorOperationDef>;
auth?: AuthDefinition;
useWorker?: boolean;
};
// Represents a single operation of the interceptor
export type InterceptorOperationDef = {
// Operation method (can be extended in the future)
method: "get" | "post" | "put" | "delete" | "patch" | "head" | "options";
// Operation URL(s)
url: string | Array<string>;
// The source code of the operation's handler. This code is parsed and executed
handler: string;
// Shape of the request this operation expects
requestShape?: any;
// Shape of the response this operation returns
responseShape?: any;
// Type mappings of path parameters
pathParamTypes?: Record<string, string>
// Type mappings of query parameters
queryParamTypes?: Record<string, string>;
successStatusCode?: number;
};
// Describes the backend environment (change it for mocked tests)
export type BackendEnvironment = {
getDate: () => Date;
};
// Represents the persistence service (database) of the backend
// TODO: Review this interface
export interface IDatabase {
[key: string]: unknown;
getItems(collectionName: string): Promise<Array<any>>;
getItem(collectionName: string, predicate: (item: any) => Promise<boolean>): Promise<any>;
getItemById(collectionName: string, id: any): Promise<any>;
deleteItems(collectionName: string, predicate: (item: any) => Promise<boolean>): Promise<void>;
insertItem(collectionName: string, item: any): Promise<any>;
updateItem(collectionName: string, item: any): Promise<any>;
updateItem(collectionName: string, item: any): Promise<any>;
}
// Represents the request parameters the backed translates to operations
export type RequestParams = {
// URL path parameters
pathParams: Record<string, any>;
// URL query parameters
queryParams: Record<string, any>;
// The body of a request
requestBody: any;
cookies: any;
requestHeaders: Record<string, string>;
};
// Represents the definition of a particular backed
export type BackendDefinition = {
initialData?: Record<string, any[]>;
initialize?: string;
helpers?: Record<string, any>;
operations: Record<string, string>;
};
```
--------------------------------------------------------------------------------
/docs/public/pages/howto/sync-selections-between-table-and-list-views.md:
--------------------------------------------------------------------------------
```markdown
# Sync selections between a Table view and a List view
The Table view syncs selections automatically via `syncWithAppState`, while the Tiles (List) view uses checkboxes that manually coordinate with AppState.
```xmlui-pg
---app
<App>
<Files />
</App>
---api
{
"apiUrl": "/api",
"initialize": "$state.files = [
{ id: 1, name: 'document.pdf', size: '2.3 MB', type: 'PDF' },
{ id: 2, name: 'image.jpg', size: '1.1 MB', type: 'Image' },
{ id: 3, name: 'spreadsheet.xlsx', size: '856 KB', type: 'Excel' },
{ id: 4, name: 'presentation.pptx', size: '4.2 MB', type: 'PowerPoint' },
{ id: 5, name: 'text.txt', size: '12 KB', type: 'Text' }
]",
"operations": {
"get-files": {
"url": "/files",
"method": "get",
"handler": "return $state.files"
}
}
}
---comp display /selections/ /sharedSelections/ /selectedIds/
<Component name="Files">
<AppState id="selections" bucket="sharedSelections" initialValue="{{ selectedIds: [] }}" />
<DataSource id="files" url="/api/files" />
<Text>Selected: {selections.value.selectedIds.length} items</Text>
<Tabs>
<TabItem label="Table View">
<TableView data="{files}" />
</TabItem>
<TabItem label="Tiles View">
<TilesView data="{files}" />
</TabItem>
<TabItem label="Debug">
<DebugView />
</TabItem>
</Tabs>
</Component>
---comp display /selections/ /sharedSelections/ /selectedIds/
<Component name="TableView">
<AppState id="selections" bucket="sharedSelections" />
<Card>
<Table
data="{$props.data}"
rowsSelectable="true"
syncWithAppState="{selections}"
loading="{!$props.data}"
>
<Column bindTo="name" />
<Column bindTo="size" />
<Column bindTo="type" />
</Table>
</Card>
</Component>
---comp display /selections/ /sharedSelections/ /selectedIds/
<Component name="TilesView">
<AppState id="selections" bucket="sharedSelections" />
<List data="{$props.data}">
<Card>
<HStack verticalAlignment="center">
<Checkbox
initialValue="{selections.value.selectedIds?.includes($item.id) || false}"
onDidChange="(checked) => {
const ids = selections.value.selectedIds || [];
if (checked) {
selections.update({ selectedIds: [...ids, $item.id] });
} else {
selections.update({ selectedIds: ids.filter(id => id !== $item.id) });
}
}"
/>
<VStack>
<Text>{$item.name}</Text>
<Text variant="caption">{$item.size} - {$item.type}</Text>
</VStack>
</HStack>
</Card>
</List>
</Component>
---desc
The spread operator `...ids` takes all existing IDs from the array, and creates a new array with those IDs plus the new one.
The filter method creates a new array with only items that meet a condition.
---comp display /selections/ /sharedSelections/ /selectedIds/
<Component name="DebugView">
<AppState id="selections" bucket="sharedSelections" />
<Card>
<VStack>
<Text>selections.value</Text>
<Text variant="code">{JSON.stringify(selections.value, null, 2)}</Text>
</VStack>
</Card>
</Component>
```
```
--------------------------------------------------------------------------------
/xmlui/src/components/PageMetaTitle/PageMetaTitle.spec.ts:
--------------------------------------------------------------------------------
```typescript
import { expect, test } from "../../testing/fixtures";
// =============================================================================
// BASIC FUNCTIONALITY TESTS
// =============================================================================
test.describe("Basic Functionality", () => {
test("component renders with default props", async ({ initTestBed, page }) => {
await initTestBed(`<PageMetaTitle />`);
// PageMetaTitle doesn't render visible DOM elements, it only affects page title
// Default title should be set in browser with test bed suffix
await expect(page).toHaveTitle("XMLUI Application | test bed app");
});
test("component renders with value property", async ({ initTestBed, page }) => {
await initTestBed(`<PageMetaTitle value="Custom Page Title" />`);
// Custom title should be set in browser with test bed suffix
await expect(page).toHaveTitle("Custom Page Title | test bed app");
});
test("component handles dynamic value changes", async ({ initTestBed, page }) => {
const { testStateDriver } = await initTestBed(`
<VStack>
<PageMetaTitle value="{testState || 'Initial Title'}" />
<Button onClick="testState = 'Updated Title'">Update Title</Button>
</VStack>
`);
// Initial title should be set
await expect(page).toHaveTitle("Initial Title | test bed app");
// Click button to update title
await page.getByRole("button").click();
await expect.poll(testStateDriver.testState).toEqual("Updated Title");
// Title should be updated in browser
await expect(page).toHaveTitle("Updated Title | test bed app");
});
test("component handles child content as title", async ({ initTestBed, page }) => {
await initTestBed(`
<PageMetaTitle>
Child Content Title
</PageMetaTitle>
`);
// Child content should be used as title
await expect(page).toHaveTitle("Child Content Title | test bed app");
});
test("value property takes precedence over child content", async ({ initTestBed, page }) => {
await initTestBed(`
<PageMetaTitle value="Value Property Title">
Child Content That Should Be Ignored
</PageMetaTitle>
`);
// Value property should take precedence
await expect(page).toHaveTitle("Value Property Title | test bed app");
});
test("multiple PageMetaTitle components - last one wins", async ({ initTestBed, page }) => {
await initTestBed(`
<VStack>
<PageMetaTitle value="First Title" />
<PageMetaTitle value="Second Title" />
<PageMetaTitle value="Final Title" />
</VStack>
`);
// Last component should set the final title
await expect(page).toHaveTitle("Final Title | test bed app");
});
test("component updates existing page title", async ({ initTestBed, page }) => {
// Set initial title
await initTestBed(`<PageMetaTitle value="Initial Title" />`);
await expect(page).toHaveTitle("Initial Title | test bed app");
// Update with new component
await initTestBed(`<PageMetaTitle value="Updated Title" />`);
await expect(page).toHaveTitle("Updated Title | test bed app");
});
});
```
--------------------------------------------------------------------------------
/xmlui/src/components-core/action/FileUploadAction.tsx:
--------------------------------------------------------------------------------
```typescript
import type { ActionExecutionContext } from "../../abstractions/ActionDefs";
import type { ComponentDef } from "../../abstractions/ComponentDefs";
import { extractParam } from "../utils/extractParam";
import { invalidateQueries } from "../utils/actionUtils";
import type { ApiActionOptions, UploadOperationDef } from "../RestApiProxy";
import RestApiProxy from "../RestApiProxy";
import { createAction } from "./actions";
export interface UploadActionComponent extends ComponentDef {
props: {
invalidates?: string | string[];
} & UploadOperationDef;
events?: {
error?: string;
success?: string;
};
}
export type UploadActionParams = {
invalidates?: string | string[];
params: any;
chunkSizeInBytes?: number;
onError?: string;
onProgress?: (...args: any) => void;
} & UploadOperationDef;
async function uploadFile(
{ appContext, state, lookupAction, uid }: ActionExecutionContext,
{
params,
invalidates,
onError,
queryParams,
asForm,
file,
headers,
url,
method,
formParams,
rawBody,
body,
chunkSizeInBytes,
onProgress,
}: UploadActionParams,
{ resolveBindingExpressions }: ApiActionOptions = {},
) {
const stateContext = { ...params, ...state };
const api = new RestApiProxy(appContext);
const operation: UploadOperationDef = {
file,
body,
asForm,
formParams,
rawBody,
method,
url,
queryParams,
headers,
};
let result = null;
try {
const _chunkSizeInBytes = extractParam(stateContext, chunkSizeInBytes, appContext);
const _onProgress = extractParam(stateContext, onProgress, appContext);
if (_chunkSizeInBytes !== undefined) {
const _file = extractParam(stateContext, file, appContext);
const numberOfChunks = Math.ceil(_file.size / _chunkSizeInBytes);
for (let i = 0; i < numberOfChunks; i++) {
const start = i * _chunkSizeInBytes;
const chunkEnd = Math.min(start + _chunkSizeInBytes, _file.size);
const chunk = _file.slice(start, chunkEnd);
result = await api.upload({
operation,
chunk: {
blob: chunk,
chunkStart: start,
chunkEnd: chunkEnd,
},
params: stateContext,
onUploadProgress: (progressEvent) => {
const overallTotal = _file.size;
const overallLoaded = start + progressEvent.loaded;
const overallProgressEvent = {
total: overallTotal,
loaded: overallLoaded,
progress: overallLoaded / overallTotal,
};
_onProgress?.(overallProgressEvent);
},
resolveBindingExpressions,
});
}
} else {
result = await api.upload({
operation,
params: stateContext,
onUploadProgress: _onProgress,
resolveBindingExpressions,
});
}
} catch (e) {
const onErrorFn = lookupAction(onError, uid);
const result = await onErrorFn?.(e, stateContext["$param"]);
if (result !== false) {
throw e;
}
}
void invalidateQueries(invalidates, appContext, state);
return result;
}
export const uploadAction = createAction("upload", uploadFile);
```
--------------------------------------------------------------------------------
/xmlui/src/components/FileUploadDropZone/FileUploadDropZone.tsx:
--------------------------------------------------------------------------------
```typescript
import styles from "./FileUploadDropZone.module.scss";
import { createComponentRenderer } from "../../components-core/renderers";
import { parseScssVar } from "../../components-core/theming/themeVars";
import { FileUploadDropZone, defaultProps } from "./FileUploadDropZoneNative";
import { createMetadata, d } from "../metadata-helpers";
const COMP = "FileUploadDropZone";
export const FileUploadDropZoneMd = createMetadata({
status: "stable",
description:
"`FileUploadDropZone` enables users to upload files by dragging and dropping " +
"files from their local file system onto a designated area within the UI.",
props: {
text: {
description:
"With this property, you can change the default text to display when files " +
"are dragged over the drop zone.",
defaultValue: defaultProps.text,
type: "string",
},
allowPaste: {
description:
"This property indicates if the drop zone accepts files pasted from the " +
"clipboard (\`true\`) or " +
"only dragged files (\`false\`).",
type: "boolean",
defaultValue: defaultProps.allowPaste,
},
enabled: d(
`If set to \`false\`, the drop zone will be disabled and users will not be able to upload files.`,
null,
"boolean",
true,
),
acceptedFileTypes: d(`Accepted file MIME types, separated by commas. For example: 'image/*,application/pdf'.`),
maxFiles: d(`The maximum number of files that can be selected.`),
},
events: {
upload: d(
`This component accepts files for upload but does not perform the actual operation. It fires ` +
`the \`upload\` event and passes the list files to upload in the method's argument. You can ` +
`use the passed file information to implement the upload (according to the protocol your ` +
`backend supports).`,
),
},
themeVars: parseScssVar(styles.themeVars),
defaultThemeVars: {
"backgroundColor-FileUploadDropZone": "$backgroundColor",
"backgroundColor-dropping-FileUploadDropZone": "$backgroundColor--selected",
"opacity-dropping-FileUploadDropZone": "0.5",
"textColor-FileUploadDropZone": "$textColor",
light: {
// --- No light-specific theme vars
},
dark: {
// --- No dark-specific theme vars
},
},
});
export const fileUploadDropZoneComponentRenderer = createComponentRenderer(
COMP,
FileUploadDropZoneMd,
({ node, extractValue, updateState, renderChild, lookupEventHandler, registerComponentApi, className }) => {
return (
<FileUploadDropZone
onUpload={lookupEventHandler("upload")!}
uid={extractValue(node.uid)}
registerComponentApi={registerComponentApi}
className={className}
allowPaste={extractValue(node.props.allowPaste)}
text={extractValue(node.props.text)}
disabled={!extractValue.asOptionalBoolean(node.props.enabled, true)}
updateState={updateState}
acceptedFileTypes={extractValue.asOptionalString(node.props.acceptedFileTypes)}
maxFiles={extractValue.asOptionalNumber(node.props.maxFiles)}
>
{renderChild(node.children, { type: "Stack" })}
</FileUploadDropZone>
);
},
);
```
--------------------------------------------------------------------------------
/xmlui/src/components-core/utils/audio-utils.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Audio utility functions
*/
/**
* Generates a beep sound using the Web Audio API
* @param frequency - The frequency of the beep in Hz (default: 800)
* @param durationInMs - The duration of the beep in milliseconds (default: 200)
*/
export function beep(frequency: number = 800, durationInMs: number = 200): void {
// Check if Web Audio API is supported
if (typeof window === 'undefined' || !window.AudioContext && !(window as any).webkitAudioContext) {
console.warn('Web Audio API is not supported in this environment');
return;
}
try {
// Create audio context (handle both standard and webkit prefixed versions)
const AudioContextClass = window.AudioContext || (window as any).webkitAudioContext;
const audioContext = new AudioContextClass();
// Create oscillator for the tone
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
// Connect nodes: oscillator -> gain -> destination
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
// Configure oscillator
oscillator.frequency.setValueAtTime(frequency, audioContext.currentTime);
oscillator.type = 'sine'; // Use sine wave for a clean tone
// Configure gain (volume) with fade in/out to avoid clicking
const currentTime = audioContext.currentTime;
const endTime = currentTime + durationInMs / 1000;
const fadeTime = Math.min(0.01, durationInMs / 1000 / 4); // Fade time is 1/4 of duration or 10ms, whichever is smaller
gainNode.gain.setValueAtTime(0, currentTime);
gainNode.gain.linearRampToValueAtTime(0.3, currentTime + fadeTime); // Fade in
gainNode.gain.setValueAtTime(0.3, endTime - fadeTime);
gainNode.gain.linearRampToValueAtTime(0, endTime); // Fade out
// Start and stop the oscillator
oscillator.start(currentTime);
oscillator.stop(endTime);
// Clean up after the beep is done
oscillator.addEventListener('ended', () => {
oscillator.disconnect();
gainNode.disconnect();
audioContext.close();
});
} catch (error) {
console.warn('Failed to generate beep:', error);
}
}
/**
* Generates a sequence of beeps
* @param count - Number of beeps
* @param frequency - The frequency of each beep in Hz (default: 800)
* @param durationInMs - The duration of each beep in milliseconds (default: 200)
* @param intervalInMs - The interval between beeps in milliseconds (default: 300)
*/
export function beepSequence(
count: number,
frequency: number = 800,
durationInMs: number = 200,
intervalInMs: number = 300
): void {
for (let i = 0; i < count; i++) {
setTimeout(() => {
beep(frequency, durationInMs);
}, i * intervalInMs);
}
}
/**
* Predefined beep patterns for common use cases
*/
export const BeepPatterns = {
/** Short, high-pitched beep for success */
success: () => beep(1000, 150),
/** Lower frequency beep for errors */
error: () => beep(400, 300),
/** Quick beep for notifications */
notification: () => beep(800, 100),
/** Double beep for warnings */
warning: () => beepSequence(2, 600, 150, 200),
/** Triple beep for alerts */
alert: () => beepSequence(3, 900, 100, 150),
} as const;
```