This is page 46 of 143. Use http://codebase.md/xmlui-org/xmlui/xmlui/mockApiDef.js?lines=false&page={x} to view the full context.
# Directory Structure
```
├── .changeset
│ ├── config.json
│ └── cyan-tools-design.md
├── .eslintrc.cjs
├── .github
│ ├── build-checklist.png
│ ├── ISSUE_TEMPLATE
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ └── workflows
│ ├── deploy-blog-optimized.yml
│ ├── deploy-blog-swa.yml
│ ├── deploy-blog.yml
│ ├── deploy-docs-optimized.yml
│ ├── deploy-docs-swa.yml
│ ├── deploy-docs.yml
│ ├── prepare-versions.yml
│ ├── release-packages.yml
│ ├── run-all-tests.yml
│ └── run-smoke-tests.yml
├── .gitignore
├── .prettierrc.js
├── .vscode
│ ├── launch.json
│ └── settings.json
├── blog
│ ├── .gitignore
│ ├── .gitkeep
│ ├── CHANGELOG.md
│ ├── extensions.ts
│ ├── index.html
│ ├── index.ts
│ ├── package.json
│ ├── public
│ │ ├── blog
│ │ │ ├── images
│ │ │ │ ├── an-advanced-codefence.gif
│ │ │ │ ├── an-advanced-codefence.mp4
│ │ │ │ ├── blog-page-component.png
│ │ │ │ ├── blog-scrabble.png
│ │ │ │ ├── codefence-runner.png
│ │ │ │ ├── integrated-blog-search.png
│ │ │ │ ├── lorem-ipsum.png
│ │ │ │ ├── playground-checkbox-source.png
│ │ │ │ ├── playground.png
│ │ │ │ ├── use-xmlui-mcp-to-find-a-howto.png
│ │ │ │ └── xmlui-demo-gallery.png
│ │ │ ├── introducing-xmlui.md
│ │ │ ├── lorem-ipsum.md
│ │ │ ├── newest-post.md
│ │ │ ├── older-post.md
│ │ │ ├── xmlui-playground.md
│ │ │ └── xmlui-powered-blog.md
│ │ ├── mockServiceWorker.js
│ │ ├── resources
│ │ │ ├── favicon.ico
│ │ │ ├── files
│ │ │ │ └── for-download
│ │ │ │ └── xmlui
│ │ │ │ └── xmlui-standalone.umd.js
│ │ │ ├── github.svg
│ │ │ ├── llms.txt
│ │ │ ├── logo-dark.svg
│ │ │ ├── logo.svg
│ │ │ ├── pg-popout.svg
│ │ │ ├── rss.svg
│ │ │ └── xmlui-logo.svg
│ │ ├── serve.json
│ │ ├── staticwebapp.config.json
│ │ └── web.config
│ ├── scripts
│ │ ├── download-latest-xmlui.js
│ │ ├── generate-rss.js
│ │ ├── get-releases.js
│ │ └── utils.js
│ ├── src
│ │ ├── components
│ │ │ ├── BlogOverview.xmlui
│ │ │ ├── BlogPage.xmlui
│ │ │ └── PageNotFound.xmlui
│ │ ├── config.ts
│ │ ├── Main.xmlui
│ │ └── themes
│ │ └── blog-theme.ts
│ └── tsconfig.json
├── CONTRIBUTING.md
├── docs
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── ComponentRefLinks.txt
│ ├── content
│ │ ├── _meta.json
│ │ ├── components
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── APICall.md
│ │ │ ├── App.md
│ │ │ ├── AppHeader.md
│ │ │ ├── AppState.md
│ │ │ ├── AutoComplete.md
│ │ │ ├── Avatar.md
│ │ │ ├── Backdrop.md
│ │ │ ├── Badge.md
│ │ │ ├── BarChart.md
│ │ │ ├── Bookmark.md
│ │ │ ├── Breakout.md
│ │ │ ├── Button.md
│ │ │ ├── Card.md
│ │ │ ├── Carousel.md
│ │ │ ├── ChangeListener.md
│ │ │ ├── Checkbox.md
│ │ │ ├── CHStack.md
│ │ │ ├── ColorPicker.md
│ │ │ ├── Column.md
│ │ │ ├── ContentSeparator.md
│ │ │ ├── CVStack.md
│ │ │ ├── DataSource.md
│ │ │ ├── DateInput.md
│ │ │ ├── DatePicker.md
│ │ │ ├── DonutChart.md
│ │ │ ├── DropdownMenu.md
│ │ │ ├── EmojiSelector.md
│ │ │ ├── ExpandableItem.md
│ │ │ ├── FileInput.md
│ │ │ ├── FileUploadDropZone.md
│ │ │ ├── FlowLayout.md
│ │ │ ├── Footer.md
│ │ │ ├── Form.md
│ │ │ ├── FormItem.md
│ │ │ ├── FormSection.md
│ │ │ ├── Fragment.md
│ │ │ ├── H1.md
│ │ │ ├── H2.md
│ │ │ ├── H3.md
│ │ │ ├── H4.md
│ │ │ ├── H5.md
│ │ │ ├── H6.md
│ │ │ ├── Heading.md
│ │ │ ├── HSplitter.md
│ │ │ ├── HStack.md
│ │ │ ├── Icon.md
│ │ │ ├── IFrame.md
│ │ │ ├── Image.md
│ │ │ ├── Items.md
│ │ │ ├── LabelList.md
│ │ │ ├── Legend.md
│ │ │ ├── LineChart.md
│ │ │ ├── Link.md
│ │ │ ├── List.md
│ │ │ ├── Logo.md
│ │ │ ├── Markdown.md
│ │ │ ├── MenuItem.md
│ │ │ ├── MenuSeparator.md
│ │ │ ├── ModalDialog.md
│ │ │ ├── NavGroup.md
│ │ │ ├── NavLink.md
│ │ │ ├── NavPanel.md
│ │ │ ├── NoResult.md
│ │ │ ├── NumberBox.md
│ │ │ ├── Option.md
│ │ │ ├── Page.md
│ │ │ ├── PageMetaTitle.md
│ │ │ ├── Pages.md
│ │ │ ├── Pagination.md
│ │ │ ├── PasswordInput.md
│ │ │ ├── PieChart.md
│ │ │ ├── ProgressBar.md
│ │ │ ├── Queue.md
│ │ │ ├── RadioGroup.md
│ │ │ ├── RealTimeAdapter.md
│ │ │ ├── Redirect.md
│ │ │ ├── Select.md
│ │ │ ├── Slider.md
│ │ │ ├── Slot.md
│ │ │ ├── SpaceFiller.md
│ │ │ ├── Spinner.md
│ │ │ ├── Splitter.md
│ │ │ ├── Stack.md
│ │ │ ├── StickyBox.md
│ │ │ ├── SubMenuItem.md
│ │ │ ├── Switch.md
│ │ │ ├── TabItem.md
│ │ │ ├── Table.md
│ │ │ ├── TableOfContents.md
│ │ │ ├── Tabs.md
│ │ │ ├── Text.md
│ │ │ ├── TextArea.md
│ │ │ ├── TextBox.md
│ │ │ ├── Theme.md
│ │ │ ├── TimeInput.md
│ │ │ ├── Timer.md
│ │ │ ├── ToneChangerButton.md
│ │ │ ├── ToneSwitch.md
│ │ │ ├── Tooltip.md
│ │ │ ├── Tree.md
│ │ │ ├── VSplitter.md
│ │ │ ├── VStack.md
│ │ │ ├── xmlui-animations
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── Animation.md
│ │ │ │ ├── FadeAnimation.md
│ │ │ │ ├── FadeInAnimation.md
│ │ │ │ ├── FadeOutAnimation.md
│ │ │ │ ├── ScaleAnimation.md
│ │ │ │ └── SlideInAnimation.md
│ │ │ ├── xmlui-pdf
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Pdf.md
│ │ │ ├── xmlui-spreadsheet
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Spreadsheet.md
│ │ │ └── xmlui-website-blocks
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── Carousel.md
│ │ │ ├── HelloMd.md
│ │ │ ├── HeroSection.md
│ │ │ └── ScrollToTop.md
│ │ └── extensions
│ │ ├── _meta.json
│ │ ├── xmlui-animations
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── Animation.md
│ │ │ ├── FadeAnimation.md
│ │ │ ├── FadeInAnimation.md
│ │ │ ├── FadeOutAnimation.md
│ │ │ ├── ScaleAnimation.md
│ │ │ └── SlideInAnimation.md
│ │ └── xmlui-website-blocks
│ │ ├── _meta.json
│ │ ├── _overview.md
│ │ ├── Carousel.md
│ │ ├── FancyButton.md
│ │ ├── HeroSection.md
│ │ └── ScrollToTop.md
│ ├── extensions.ts
│ ├── index.html
│ ├── index.ts
│ ├── package.json
│ ├── public
│ │ ├── feed.rss
│ │ ├── mockServiceWorker.js
│ │ ├── pages
│ │ │ ├── _meta.json
│ │ │ ├── app-structure.md
│ │ │ ├── build-editor-component.md
│ │ │ ├── build-hello-world-component.md
│ │ │ ├── components-intro.md
│ │ │ ├── context-variables.md
│ │ │ ├── forms.md
│ │ │ ├── globals.md
│ │ │ ├── glossary.md
│ │ │ ├── helper-tags.md
│ │ │ ├── hosted-deployment.md
│ │ │ ├── howto
│ │ │ │ ├── assign-a-complex-json-literal-to-a-component-variable.md
│ │ │ │ ├── chain-a-refetch.md
│ │ │ │ ├── control-cache-invalidation.md
│ │ │ │ ├── debounce-user-input-for-api-calls.md
│ │ │ │ ├── debounce-with-changelistener.md
│ │ │ │ ├── debug-a-component.md
│ │ │ │ ├── delay-a-datasource-until-another-datasource-is-ready.md
│ │ │ │ ├── delegate-a-method.md
│ │ │ │ ├── do-custom-form-validation.md
│ │ │ │ ├── expose-a-method-from-a-component.md
│ │ │ │ ├── filter-and-transform-data-from-an-api.md
│ │ │ │ ├── group-items-in-list-by-a-property.md
│ │ │ │ ├── handle-background-operations.md
│ │ │ │ ├── hide-an-element-until-its-datasource-is-ready.md
│ │ │ │ ├── make-a-set-of-equal-width-cards.md
│ │ │ │ ├── make-a-table-responsive.md
│ │ │ │ ├── make-navpanel-width-responsive.md
│ │ │ │ ├── modify-a-value-reported-in-a-column.md
│ │ │ │ ├── paginate-a-list.md
│ │ │ │ ├── pass-data-to-a-modal-dialog.md
│ │ │ │ ├── react-to-button-click-not-keystrokes.md
│ │ │ │ ├── set-the-initial-value-of-a-select-from-fetched-data.md
│ │ │ │ ├── share-a-modaldialog-across-components.md
│ │ │ │ ├── sync-selections-between-table-and-list-views.md
│ │ │ │ ├── update-ui-optimistically.md
│ │ │ │ ├── use-built-in-form-validation.md
│ │ │ │ └── use-the-same-modaldialog-to-add-or-edit.md
│ │ │ ├── howto.md
│ │ │ ├── intro.md
│ │ │ ├── layout.md
│ │ │ ├── markup.md
│ │ │ ├── mcp.md
│ │ │ ├── modal-dialogs.md
│ │ │ ├── news-and-reviews.md
│ │ │ ├── reactive-intro.md
│ │ │ ├── refactoring.md
│ │ │ ├── routing-and-links.md
│ │ │ ├── samples
│ │ │ │ ├── color-palette.xmlui
│ │ │ │ ├── color-values.xmlui
│ │ │ │ ├── shadow-sizes.xmlui
│ │ │ │ ├── spacing-sizes.xmlui
│ │ │ │ ├── swatch.xmlui
│ │ │ │ ├── theme-gallery-brief.xmlui
│ │ │ │ └── theme-gallery.xmlui
│ │ │ ├── scoping.md
│ │ │ ├── scripting.md
│ │ │ ├── styles-and-themes
│ │ │ │ ├── common-units.md
│ │ │ │ ├── layout-props.md
│ │ │ │ ├── theme-variable-defaults.md
│ │ │ │ ├── theme-variables.md
│ │ │ │ └── themes.md
│ │ │ ├── template-properties.md
│ │ │ ├── test.md
│ │ │ ├── tutorial-01.md
│ │ │ ├── tutorial-02.md
│ │ │ ├── tutorial-03.md
│ │ │ ├── tutorial-04.md
│ │ │ ├── tutorial-05.md
│ │ │ ├── tutorial-06.md
│ │ │ ├── tutorial-07.md
│ │ │ ├── tutorial-08.md
│ │ │ ├── tutorial-09.md
│ │ │ ├── tutorial-10.md
│ │ │ ├── tutorial-11.md
│ │ │ ├── tutorial-12.md
│ │ │ ├── universal-properties.md
│ │ │ ├── user-defined-components.md
│ │ │ ├── vscode.md
│ │ │ ├── working-with-markdown.md
│ │ │ ├── working-with-text.md
│ │ │ ├── xmlui-animations
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── Animation.md
│ │ │ │ ├── FadeAnimation.md
│ │ │ │ ├── FadeInAnimation.md
│ │ │ │ ├── FadeOutAnimation.md
│ │ │ │ ├── ScaleAnimation.md
│ │ │ │ └── SlideInAnimation.md
│ │ │ ├── xmlui-charts
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── BarChart.md
│ │ │ │ ├── DonutChart.md
│ │ │ │ ├── LabelList.md
│ │ │ │ ├── Legend.md
│ │ │ │ ├── LineChart.md
│ │ │ │ └── PieChart.md
│ │ │ ├── xmlui-pdf
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Pdf.md
│ │ │ └── xmlui-spreadsheet
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ └── Spreadsheet.md
│ │ ├── resources
│ │ │ ├── devdocs
│ │ │ │ ├── debug-proxy-object-2.png
│ │ │ │ ├── debug-proxy-object.png
│ │ │ │ ├── table_editor_01.png
│ │ │ │ ├── table_editor_02.png
│ │ │ │ ├── table_editor_03.png
│ │ │ │ ├── table_editor_04.png
│ │ │ │ ├── table_editor_05.png
│ │ │ │ ├── table_editor_06.png
│ │ │ │ ├── table_editor_07.png
│ │ │ │ ├── table_editor_08.png
│ │ │ │ ├── table_editor_09.png
│ │ │ │ ├── table_editor_10.png
│ │ │ │ ├── table_editor_11.png
│ │ │ │ ├── table-editor-01.png
│ │ │ │ ├── table-editor-02.png
│ │ │ │ ├── table-editor-03.png
│ │ │ │ ├── table-editor-04.png
│ │ │ │ ├── table-editor-06.png
│ │ │ │ ├── table-editor-07.png
│ │ │ │ ├── table-editor-08.png
│ │ │ │ ├── table-editor-09.png
│ │ │ │ └── xmlui-rendering-of-tiptap-markdown.png
│ │ │ ├── favicon.ico
│ │ │ ├── files
│ │ │ │ ├── clients.json
│ │ │ │ ├── daily-revenue.json
│ │ │ │ ├── dashboard-stats.json
│ │ │ │ ├── demo.xmlui
│ │ │ │ ├── demo.xmlui.xs
│ │ │ │ ├── downloads
│ │ │ │ │ └── downloads.json
│ │ │ │ ├── for-download
│ │ │ │ │ ├── index-with-api.html
│ │ │ │ │ ├── index.html
│ │ │ │ │ ├── mockApi.js
│ │ │ │ │ ├── start-darwin.sh
│ │ │ │ │ ├── start-linux.sh
│ │ │ │ │ ├── start.bat
│ │ │ │ │ └── xmlui
│ │ │ │ │ └── xmlui-standalone.umd.js
│ │ │ │ ├── getting-started
│ │ │ │ │ ├── cl-tutorial-final.zip
│ │ │ │ │ ├── cl-tutorial.zip
│ │ │ │ │ ├── cl-tutorial2.zip
│ │ │ │ │ ├── cl-tutorial3.zip
│ │ │ │ │ ├── cl-tutorial4.zip
│ │ │ │ │ ├── cl-tutorial5.zip
│ │ │ │ │ ├── cl-tutorial6.zip
│ │ │ │ │ ├── getting-started.zip
│ │ │ │ │ ├── hello-xmlui.zip
│ │ │ │ │ ├── xmlui-empty.zip
│ │ │ │ │ └── xmlui-starter.zip
│ │ │ │ ├── howto
│ │ │ │ │ └── component-icons
│ │ │ │ │ └── up-arrow.svg
│ │ │ │ ├── invoices.json
│ │ │ │ ├── monthly-status.json
│ │ │ │ ├── news-and-reviews.json
│ │ │ │ ├── products.json
│ │ │ │ ├── releases.json
│ │ │ │ ├── tutorials
│ │ │ │ │ ├── datasource
│ │ │ │ │ │ └── api.ts
│ │ │ │ │ └── p2do
│ │ │ │ │ ├── api.ts
│ │ │ │ │ └── todo-logo.svg
│ │ │ │ └── xmlui.json
│ │ │ ├── github.svg
│ │ │ ├── images
│ │ │ │ ├── apiaction-tutorial
│ │ │ │ │ ├── add-success.png
│ │ │ │ │ ├── apiaction-param.png
│ │ │ │ │ ├── change-completed.png
│ │ │ │ │ ├── change-in-progress.png
│ │ │ │ │ ├── confirm-delete.png
│ │ │ │ │ ├── data-error.png
│ │ │ │ │ ├── data-progress.png
│ │ │ │ │ ├── data-success.png
│ │ │ │ │ ├── display-1.png
│ │ │ │ │ ├── item-deleted.png
│ │ │ │ │ ├── item-updated.png
│ │ │ │ │ ├── missing-api-key.png
│ │ │ │ │ ├── new-item-added.png
│ │ │ │ │ └── test-message.png
│ │ │ │ ├── chat-api
│ │ │ │ │ └── domain-model.svg
│ │ │ │ ├── components
│ │ │ │ │ ├── image
│ │ │ │ │ │ └── breakfast.jpg
│ │ │ │ │ ├── markdown
│ │ │ │ │ │ └── colors.png
│ │ │ │ │ └── modal
│ │ │ │ │ ├── deep_link_dialog_1.jpg
│ │ │ │ │ └── deep_link_dialog_2.jpg
│ │ │ │ ├── create-apps
│ │ │ │ │ ├── collapsed-vertical.png
│ │ │ │ │ ├── using-forms-warning-dialog.png
│ │ │ │ │ └── using-forms.png
│ │ │ │ ├── datasource-tutorial
│ │ │ │ │ ├── data-with-header.png
│ │ │ │ │ ├── filtered-data.png
│ │ │ │ │ ├── filtered-items.png
│ │ │ │ │ ├── initial-page-items.png
│ │ │ │ │ ├── list-items.png
│ │ │ │ │ ├── next-page-items.png
│ │ │ │ │ ├── no-data.png
│ │ │ │ │ ├── pagination-1.jpg
│ │ │ │ │ ├── pagination-1.png
│ │ │ │ │ ├── polling-1.png
│ │ │ │ │ ├── refetch-data.png
│ │ │ │ │ ├── slow-loading.png
│ │ │ │ │ ├── test-message.png
│ │ │ │ │ ├── Thumbs.db
│ │ │ │ │ ├── unconventional-data.png
│ │ │ │ │ └── unfiltered-items.png
│ │ │ │ ├── flower.jpg
│ │ │ │ ├── get-started
│ │ │ │ │ ├── add-new-contact.png
│ │ │ │ │ ├── app-modified.png
│ │ │ │ │ ├── app-start.png
│ │ │ │ │ ├── app-with-boxes.png
│ │ │ │ │ ├── app-with-toast.png
│ │ │ │ │ ├── boilerplate-structure.png
│ │ │ │ │ ├── cl-initial.png
│ │ │ │ │ ├── cl-start.png
│ │ │ │ │ ├── contact-counts.png
│ │ │ │ │ ├── contact-dialog-title.png
│ │ │ │ │ ├── contact-dialog.png
│ │ │ │ │ ├── contact-menus.png
│ │ │ │ │ ├── contact-predicates.png
│ │ │ │ │ ├── context-menu.png
│ │ │ │ │ ├── dashboard-numbers.png
│ │ │ │ │ ├── default-contact-list.png
│ │ │ │ │ ├── delete-contact.png
│ │ │ │ │ ├── delete-task.png
│ │ │ │ │ ├── detailed-template.png
│ │ │ │ │ ├── edit-contact-details.png
│ │ │ │ │ ├── edited-contact-saved.png
│ │ │ │ │ ├── empty-sections.png
│ │ │ │ │ ├── filter-completed.png
│ │ │ │ │ ├── fullwidth-desktop.png
│ │ │ │ │ ├── fullwidth-mobile.png
│ │ │ │ │ ├── initial-table.png
│ │ │ │ │ ├── items-and-badges.png
│ │ │ │ │ ├── loading-message.png
│ │ │ │ │ ├── new-contact-button.png
│ │ │ │ │ ├── new-contact-saved.png
│ │ │ │ │ ├── no-empty-sections.png
│ │ │ │ │ ├── personal-todo-initial.png
│ │ │ │ │ ├── piechart.png
│ │ │ │ │ ├── review-today.png
│ │ │ │ │ ├── rudimentary-dashboard.png
│ │ │ │ │ ├── section-collapsed.png
│ │ │ │ │ ├── sectioned-items.png
│ │ │ │ │ ├── sections-ordered.png
│ │ │ │ │ ├── spacex-list-with-links.png
│ │ │ │ │ ├── spacex-list.png
│ │ │ │ │ ├── start-personal-todo-1.png
│ │ │ │ │ ├── submit-new-contact.png
│ │ │ │ │ ├── submit-new-task.png
│ │ │ │ │ ├── syntax-highlighting.png
│ │ │ │ │ ├── table-with-badge.png
│ │ │ │ │ ├── template-with-card.png
│ │ │ │ │ ├── test-emulated-api.png
│ │ │ │ │ ├── Thumbs.db
│ │ │ │ │ ├── todo-logo.png
│ │ │ │ │ └── xmlui-tools.png
│ │ │ │ ├── HelloApp.png
│ │ │ │ ├── HelloApp2.png
│ │ │ │ ├── logos
│ │ │ │ │ ├── xmlui1.svg
│ │ │ │ │ ├── xmlui2.svg
│ │ │ │ │ ├── xmlui3.svg
│ │ │ │ │ ├── xmlui4.svg
│ │ │ │ │ ├── xmlui5.svg
│ │ │ │ │ ├── xmlui6.svg
│ │ │ │ │ └── xmlui7.svg
│ │ │ │ ├── pdf
│ │ │ │ │ └── dummy-pdf.jpg
│ │ │ │ ├── rendering-engine
│ │ │ │ │ ├── AppEngine-flow.svg
│ │ │ │ │ ├── Component.svg
│ │ │ │ │ ├── CompoundComponent.svg
│ │ │ │ │ ├── RootComponent.svg
│ │ │ │ │ └── tree-with-containers.svg
│ │ │ │ ├── reviewers-guide
│ │ │ │ │ ├── AppEngine-flow.svg
│ │ │ │ │ └── incbutton-in-action.png
│ │ │ │ ├── tools
│ │ │ │ │ └── boilerplate-structure.png
│ │ │ │ ├── try.svg
│ │ │ │ ├── tutorial
│ │ │ │ │ ├── app-chat-history.png
│ │ │ │ │ ├── app-content-placeholder.png
│ │ │ │ │ ├── app-header-and-content.png
│ │ │ │ │ ├── app-links-channel-selected.png
│ │ │ │ │ ├── app-links-click.png
│ │ │ │ │ ├── app-navigation.png
│ │ │ │ │ ├── finished-ex01.png
│ │ │ │ │ ├── finished-ex02.png
│ │ │ │ │ ├── hello.png
│ │ │ │ │ ├── splash-screen-advanced.png
│ │ │ │ │ ├── splash-screen-after-click.png
│ │ │ │ │ ├── splash-screen-centered.png
│ │ │ │ │ ├── splash-screen-events.png
│ │ │ │ │ ├── splash-screen-expression.png
│ │ │ │ │ ├── splash-screen-reuse-after.png
│ │ │ │ │ ├── splash-screen-reuse-before.png
│ │ │ │ │ └── splash-screen.png
│ │ │ │ └── tutorial-01.png
│ │ │ ├── llms.txt
│ │ │ ├── logo-dark.svg
│ │ │ ├── logo.svg
│ │ │ ├── pg-popout.svg
│ │ │ └── xmlui-logo.svg
│ │ ├── serve.json
│ │ └── web.config
│ ├── scripts
│ │ ├── download-latest-xmlui.js
│ │ ├── generate-rss.js
│ │ ├── get-releases.js
│ │ └── utils.js
│ ├── src
│ │ ├── components
│ │ │ ├── BlogOverview.xmlui
│ │ │ ├── BlogPage.xmlui
│ │ │ ├── Boxes.xmlui
│ │ │ ├── Breadcrumb.xmlui
│ │ │ ├── ChangeLog.xmlui
│ │ │ ├── ColorPalette.xmlui
│ │ │ ├── DocumentLinks.xmlui
│ │ │ ├── DocumentPage.xmlui
│ │ │ ├── DocumentPageNoTOC.xmlui
│ │ │ ├── Icons.xmlui
│ │ │ ├── IncButton.xmlui
│ │ │ ├── IncButton2.xmlui
│ │ │ ├── NameValue.xmlui
│ │ │ ├── PageNotFound.xmlui
│ │ │ ├── PaletteItem.xmlui
│ │ │ ├── Palettes.xmlui
│ │ │ ├── SectionHeader.xmlui
│ │ │ ├── TBD.xmlui
│ │ │ ├── Test.xmlui
│ │ │ ├── ThemesIntro.xmlui
│ │ │ ├── ThousandThemes.xmlui
│ │ │ ├── TubeStops.xmlui
│ │ │ ├── TubeStops.xmlui.xs
│ │ │ └── TwoColumnCode.xmlui
│ │ ├── config.ts
│ │ ├── Main.xmlui
│ │ └── themes
│ │ ├── docs-theme.ts
│ │ ├── earthtone.ts
│ │ ├── xmlui-gray-on-default.ts
│ │ ├── xmlui-green-on-default.ts
│ │ └── xmlui-orange-on-default.ts
│ └── tsconfig.json
├── LICENSE
├── package-lock.json
├── package.json
├── packages
│ ├── tsconfig.json
│ ├── xmlui-animations
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── Animation.tsx
│ │ ├── AnimationNative.tsx
│ │ ├── FadeAnimation.tsx
│ │ ├── FadeInAnimation.tsx
│ │ ├── FadeOutAnimation.tsx
│ │ ├── index.tsx
│ │ ├── ScaleAnimation.tsx
│ │ └── SlideInAnimation.tsx
│ ├── xmlui-devtools
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ ├── src
│ │ │ ├── devtools
│ │ │ │ ├── DevTools.tsx
│ │ │ │ ├── DevToolsNative.module.scss
│ │ │ │ ├── DevToolsNative.tsx
│ │ │ │ ├── ModalDialog.module.scss
│ │ │ │ ├── ModalDialog.tsx
│ │ │ │ ├── ModalVisibilityContext.tsx
│ │ │ │ ├── Tooltip.module.scss
│ │ │ │ ├── Tooltip.tsx
│ │ │ │ └── utils.ts
│ │ │ ├── editor
│ │ │ │ └── Editor.tsx
│ │ │ └── index.tsx
│ │ └── vite.config-overrides.ts
│ ├── xmlui-hello-world
│ │ ├── .gitignore
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── HelloWorld.module.scss
│ │ ├── HelloWorld.tsx
│ │ ├── HelloWorldNative.tsx
│ │ └── index.tsx
│ ├── xmlui-os-frames
│ │ ├── .gitignore
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── index.tsx
│ │ ├── IPhoneFrame.module.scss
│ │ ├── IPhoneFrame.tsx
│ │ ├── MacOSAppFrame.module.scss
│ │ ├── MacOSAppFrame.tsx
│ │ ├── WindowsAppFrame.module.scss
│ │ └── WindowsAppFrame.tsx
│ ├── xmlui-pdf
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ ├── components
│ │ │ │ └── Pdf.xmlui
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── index.tsx
│ │ ├── LazyPdfNative.tsx
│ │ ├── Pdf.module.scss
│ │ └── Pdf.tsx
│ ├── xmlui-playground
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── hooks
│ │ │ ├── usePlayground.ts
│ │ │ └── useToast.ts
│ │ ├── index.tsx
│ │ ├── playground
│ │ │ ├── Box.module.scss
│ │ │ ├── Box.tsx
│ │ │ ├── CodeSelector.module.scss
│ │ │ ├── CodeSelector.tsx
│ │ │ ├── ConfirmationDialog.module.scss
│ │ │ ├── ConfirmationDialog.tsx
│ │ │ ├── Editor.tsx
│ │ │ ├── Header.module.scss
│ │ │ ├── Header.tsx
│ │ │ ├── Playground.tsx
│ │ │ ├── PlaygroundContent.module.scss
│ │ │ ├── PlaygroundContent.tsx
│ │ │ ├── PlaygroundNative.module.scss
│ │ │ ├── PlaygroundNative.tsx
│ │ │ ├── Preview.tsx
│ │ │ ├── StandalonePlayground.tsx
│ │ │ ├── StandalonePlaygroundNative.module.scss
│ │ │ ├── StandalonePlaygroundNative.tsx
│ │ │ ├── ThemeSwitcher.module.scss
│ │ │ ├── ThemeSwitcher.tsx
│ │ │ └── utils.ts
│ │ ├── providers
│ │ │ ├── Toast.module.scss
│ │ │ └── ToastProvider.tsx
│ │ ├── state
│ │ │ └── store.ts
│ │ ├── themes
│ │ │ └── theme.ts
│ │ └── utils
│ │ └── helpers.ts
│ ├── xmlui-search
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── index.tsx
│ │ ├── Search.module.scss
│ │ └── Search.tsx
│ ├── xmlui-spreadsheet
│ │ ├── .gitignore
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── index.tsx
│ │ ├── Spreadsheet.tsx
│ │ └── SpreadsheetNative.tsx
│ └── xmlui-website-blocks
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── demo
│ │ ├── components
│ │ │ ├── HeroBackgroundBreakoutPage.xmlui
│ │ │ ├── HeroBackgroundsPage.xmlui
│ │ │ ├── HeroContentsPage.xmlui
│ │ │ ├── HeroTextAlignPage.xmlui
│ │ │ ├── HeroTextPage.xmlui
│ │ │ └── HeroTonesPage.xmlui
│ │ ├── Main.xmlui
│ │ └── themes
│ │ └── default.ts
│ ├── index.html
│ ├── index.ts
│ ├── meta
│ │ └── componentsMetadata.ts
│ ├── package.json
│ ├── public
│ │ └── resources
│ │ ├── building.jpg
│ │ └── xmlui-logo.svg
│ └── src
│ ├── Carousel
│ │ ├── Carousel.module.scss
│ │ ├── Carousel.tsx
│ │ ├── CarouselContext.tsx
│ │ └── CarouselNative.tsx
│ ├── FancyButton
│ │ ├── FancyButton.module.scss
│ │ ├── FancyButton.tsx
│ │ └── FancyButton.xmlui
│ ├── Hello
│ │ ├── Hello.tsx
│ │ ├── Hello.xmlui
│ │ └── Hello.xmlui.xs
│ ├── HeroSection
│ │ ├── HeroSection.module.scss
│ │ ├── HeroSection.spec.ts
│ │ ├── HeroSection.tsx
│ │ └── HeroSectionNative.tsx
│ ├── index.tsx
│ ├── ScrollToTop
│ │ ├── ScrollToTop.module.scss
│ │ ├── ScrollToTop.tsx
│ │ └── ScrollToTopNative.tsx
│ └── vite-env.d.ts
├── playwright.config.ts
├── README.md
├── tools
│ ├── codefence
│ │ └── xmlui-code-fence-docs.md
│ ├── create-app
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── create-app.ts
│ │ ├── helpers
│ │ │ ├── copy.ts
│ │ │ ├── get-pkg-manager.ts
│ │ │ ├── git.ts
│ │ │ ├── install.ts
│ │ │ ├── is-folder-empty.ts
│ │ │ ├── is-writeable.ts
│ │ │ ├── make-dir.ts
│ │ │ └── validate-pkg.ts
│ │ ├── index.ts
│ │ ├── package.json
│ │ ├── templates
│ │ │ ├── default
│ │ │ │ └── ts
│ │ │ │ ├── gitignore
│ │ │ │ ├── index.html
│ │ │ │ ├── index.ts
│ │ │ │ ├── public
│ │ │ │ │ ├── mockServiceWorker.js
│ │ │ │ │ ├── resources
│ │ │ │ │ │ ├── favicon.ico
│ │ │ │ │ │ └── xmlui-logo.svg
│ │ │ │ │ └── serve.json
│ │ │ │ └── src
│ │ │ │ ├── components
│ │ │ │ │ ├── ApiAware.xmlui
│ │ │ │ │ ├── Home.xmlui
│ │ │ │ │ ├── IncButton.xmlui
│ │ │ │ │ └── PagePanel.xmlui
│ │ │ │ ├── config.ts
│ │ │ │ └── Main.xmlui
│ │ │ ├── index.ts
│ │ │ └── types.ts
│ │ └── tsconfig.json
│ ├── create-xmlui-hello-world
│ │ ├── index.js
│ │ └── package.json
│ └── vscode
│ ├── .gitignore
│ ├── .vscode
│ │ ├── launch.json
│ │ └── tasks.json
│ ├── .vscodeignore
│ ├── build.sh
│ ├── CHANGELOG.md
│ ├── esbuild.js
│ ├── eslint.config.mjs
│ ├── formatter-docs.md
│ ├── generate-test-sample.sh
│ ├── LICENSE.md
│ ├── package-lock.json
│ ├── package.json
│ ├── README.md
│ ├── resources
│ │ ├── xmlui-logo.png
│ │ └── xmlui-markup-syntax-highlighting.png
│ ├── src
│ │ ├── extension.ts
│ │ └── server.ts
│ ├── syntaxes
│ │ └── xmlui.tmLanguage.json
│ ├── test-samples
│ │ └── sample.xmlui
│ ├── tsconfig.json
│ └── tsconfig.tsbuildinfo
├── turbo.json
└── xmlui
├── .gitignore
├── bin
│ ├── bootstrap.cjs
│ ├── bootstrap.js
│ ├── build-lib.ts
│ ├── build.ts
│ ├── index.ts
│ ├── preview.ts
│ ├── start.ts
│ ├── vite-xmlui-plugin.ts
│ └── viteConfig.ts
├── CHANGELOG.md
├── conventions
│ ├── component-qa-checklist.md
│ ├── copilot-conventions.md
│ ├── create-xmlui-components.md
│ ├── mermaid.md
│ ├── testing-conventions.md
│ └── xmlui-in-a-nutshell.md
├── dev-docs
│ ├── accessibility.md
│ ├── build-system.md
│ ├── build-xmlui.md
│ ├── component-behaviors.md
│ ├── component-metadata.md
│ ├── components-with-options.md
│ ├── containers.md
│ ├── data-operations.md
│ ├── glossary.md
│ ├── index.md
│ ├── next
│ │ ├── component-dev-guide.md
│ │ ├── configuration-management-enhancement-summary.md
│ │ ├── documentation-scripts-refactoring-complete-summary.md
│ │ ├── documentation-scripts-refactoring-plan.md
│ │ ├── duplicate-pattern-extraction-summary.md
│ │ ├── error-handling-standardization-summary.md
│ │ ├── generating-component-reference.md
│ │ ├── index.md
│ │ ├── logging-consistency-implementation-summary.md
│ │ ├── project-build.md
│ │ ├── project-structure.md
│ │ ├── theme-context.md
│ │ ├── tiptap-design-considerations.md
│ │ ├── working-with-code.md
│ │ ├── xmlui-runtime-architecture
│ │ └── xmlui-wcag-accessibility-report.md
│ ├── react-fundamentals.md
│ ├── release-method.md
│ ├── standalone-app.md
│ ├── theme-variables-refactoring.md
│ ├── ud-components.md
│ └── xmlui-repo.md
├── package.json
├── scripts
│ ├── coverage-only.js
│ ├── e2e-test-summary.js
│ ├── extract-component-metadata.js
│ ├── generate-docs
│ │ ├── build-downloads-map.mjs
│ │ ├── build-pages-map.mjs
│ │ ├── components-config.json
│ │ ├── configuration-management.mjs
│ │ ├── constants.mjs
│ │ ├── create-theme-files.mjs
│ │ ├── DocsGenerator.mjs
│ │ ├── error-handling.mjs
│ │ ├── extensions-config.json
│ │ ├── folders.mjs
│ │ ├── generate-summary-files.mjs
│ │ ├── get-docs.mjs
│ │ ├── input-handler.mjs
│ │ ├── logger.mjs
│ │ ├── logging-standards.mjs
│ │ ├── MetadataProcessor.mjs
│ │ ├── pattern-utilities.mjs
│ │ └── utils.mjs
│ ├── generate-metadata-markdown.js
│ ├── get-langserver-metadata.js
│ ├── inline-links.mjs
│ └── README-e2e-summary.md
├── src
│ ├── abstractions
│ │ ├── _conventions.md
│ │ ├── ActionDefs.ts
│ │ ├── AppContextDefs.ts
│ │ ├── ComponentDefs.ts
│ │ ├── ContainerDefs.ts
│ │ ├── ExtensionDefs.ts
│ │ ├── FunctionDefs.ts
│ │ ├── RendererDefs.ts
│ │ ├── scripting
│ │ │ ├── BlockScope.ts
│ │ │ ├── Compilation.ts
│ │ │ ├── LogicalThread.ts
│ │ │ ├── LoopScope.ts
│ │ │ ├── modules.ts
│ │ │ ├── ScriptParserError.ts
│ │ │ ├── Token.ts
│ │ │ ├── TryScope.ts
│ │ │ └── TryScopeExp.ts
│ │ └── ThemingDefs.ts
│ ├── components
│ │ ├── _conventions.md
│ │ ├── abstractions.ts
│ │ ├── Accordion
│ │ │ ├── Accordion.md
│ │ │ ├── Accordion.module.scss
│ │ │ ├── Accordion.spec.ts
│ │ │ ├── Accordion.tsx
│ │ │ ├── AccordionContext.tsx
│ │ │ ├── AccordionItem.tsx
│ │ │ ├── AccordionItemNative.tsx
│ │ │ └── AccordionNative.tsx
│ │ ├── Animation
│ │ │ └── AnimationNative.tsx
│ │ ├── APICall
│ │ │ ├── APICall.md
│ │ │ ├── APICall.spec.ts
│ │ │ ├── APICall.tsx
│ │ │ └── APICallNative.tsx
│ │ ├── App
│ │ │ ├── App.md
│ │ │ ├── App.module.scss
│ │ │ ├── App.spec.ts
│ │ │ ├── App.tsx
│ │ │ ├── AppLayoutContext.ts
│ │ │ ├── AppNative.tsx
│ │ │ ├── AppStateContext.ts
│ │ │ ├── doc-resources
│ │ │ │ ├── condensed-sticky.xmlui
│ │ │ │ ├── condensed.xmlui
│ │ │ │ ├── horizontal-sticky.xmlui
│ │ │ │ ├── horizontal.xmlui
│ │ │ │ ├── vertical-full-header.xmlui
│ │ │ │ ├── vertical-sticky.xmlui
│ │ │ │ └── vertical.xmlui
│ │ │ ├── IndexerContext.ts
│ │ │ ├── LinkInfoContext.ts
│ │ │ ├── SearchContext.tsx
│ │ │ ├── Sheet.module.scss
│ │ │ └── Sheet.tsx
│ │ ├── AppHeader
│ │ │ ├── AppHeader.md
│ │ │ ├── AppHeader.module.scss
│ │ │ ├── AppHeader.spec.ts
│ │ │ ├── AppHeader.tsx
│ │ │ └── AppHeaderNative.tsx
│ │ ├── AppState
│ │ │ ├── AppState.md
│ │ │ ├── AppState.spec.ts
│ │ │ ├── AppState.tsx
│ │ │ └── AppStateNative.tsx
│ │ ├── AutoComplete
│ │ │ ├── AutoComplete.md
│ │ │ ├── AutoComplete.module.scss
│ │ │ ├── AutoComplete.spec.ts
│ │ │ ├── AutoComplete.tsx
│ │ │ ├── AutoCompleteContext.tsx
│ │ │ └── AutoCompleteNative.tsx
│ │ ├── Avatar
│ │ │ ├── Avatar.md
│ │ │ ├── Avatar.module.scss
│ │ │ ├── Avatar.spec.ts
│ │ │ ├── Avatar.tsx
│ │ │ └── AvatarNative.tsx
│ │ ├── Backdrop
│ │ │ ├── Backdrop.md
│ │ │ ├── Backdrop.module.scss
│ │ │ ├── Backdrop.spec.ts
│ │ │ ├── Backdrop.tsx
│ │ │ └── BackdropNative.tsx
│ │ ├── Badge
│ │ │ ├── Badge.md
│ │ │ ├── Badge.module.scss
│ │ │ ├── Badge.spec.ts
│ │ │ ├── Badge.tsx
│ │ │ └── BadgeNative.tsx
│ │ ├── Bookmark
│ │ │ ├── Bookmark.md
│ │ │ ├── Bookmark.module.scss
│ │ │ ├── Bookmark.spec.ts
│ │ │ ├── Bookmark.tsx
│ │ │ └── BookmarkNative.tsx
│ │ ├── Breakout
│ │ │ ├── Breakout.module.scss
│ │ │ ├── Breakout.spec.ts
│ │ │ ├── Breakout.tsx
│ │ │ └── BreakoutNative.tsx
│ │ ├── Button
│ │ │ ├── Button-style.spec.ts
│ │ │ ├── Button.md
│ │ │ ├── Button.module.scss
│ │ │ ├── Button.spec.ts
│ │ │ ├── Button.tsx
│ │ │ └── ButtonNative.tsx
│ │ ├── Card
│ │ │ ├── Card.md
│ │ │ ├── Card.module.scss
│ │ │ ├── Card.spec.ts
│ │ │ ├── Card.tsx
│ │ │ └── CardNative.tsx
│ │ ├── Carousel
│ │ │ ├── Carousel.md
│ │ │ ├── Carousel.module.scss
│ │ │ ├── Carousel.spec.ts
│ │ │ ├── Carousel.tsx
│ │ │ ├── CarouselContext.tsx
│ │ │ ├── CarouselItem.tsx
│ │ │ ├── CarouselItemNative.tsx
│ │ │ └── CarouselNative.tsx
│ │ ├── ChangeListener
│ │ │ ├── ChangeListener.md
│ │ │ ├── ChangeListener.spec.ts
│ │ │ ├── ChangeListener.tsx
│ │ │ └── ChangeListenerNative.tsx
│ │ ├── chart-color-schemes.ts
│ │ ├── Charts
│ │ │ ├── AreaChart
│ │ │ │ ├── AreaChart.md
│ │ │ │ ├── AreaChart.spec.ts
│ │ │ │ ├── AreaChart.tsx
│ │ │ │ └── AreaChartNative.tsx
│ │ │ ├── BarChart
│ │ │ │ ├── BarChart.md
│ │ │ │ ├── BarChart.module.scss
│ │ │ │ ├── BarChart.spec.ts
│ │ │ │ ├── BarChart.tsx
│ │ │ │ └── BarChartNative.tsx
│ │ │ ├── DonutChart
│ │ │ │ ├── DonutChart.spec.ts
│ │ │ │ └── DonutChart.tsx
│ │ │ ├── LabelList
│ │ │ │ ├── LabelList.module.scss
│ │ │ │ ├── LabelList.spec.ts
│ │ │ │ ├── LabelList.tsx
│ │ │ │ └── LabelListNative.tsx
│ │ │ ├── Legend
│ │ │ │ ├── Legend.spec.ts
│ │ │ │ ├── Legend.tsx
│ │ │ │ └── LegendNative.tsx
│ │ │ ├── LineChart
│ │ │ │ ├── LineChart.md
│ │ │ │ ├── LineChart.module.scss
│ │ │ │ ├── LineChart.spec.ts
│ │ │ │ ├── LineChart.tsx
│ │ │ │ └── LineChartNative.tsx
│ │ │ ├── PieChart
│ │ │ │ ├── PieChart.md
│ │ │ │ ├── PieChart.spec.ts
│ │ │ │ ├── PieChart.tsx
│ │ │ │ ├── PieChartNative.module.scss
│ │ │ │ └── PieChartNative.tsx
│ │ │ ├── RadarChart
│ │ │ │ ├── RadarChart.md
│ │ │ │ ├── RadarChart.spec.ts
│ │ │ │ ├── RadarChart.tsx
│ │ │ │ └── RadarChartNative.tsx
│ │ │ ├── Tooltip
│ │ │ │ ├── TooltipContent.module.scss
│ │ │ │ ├── TooltipContent.spec.ts
│ │ │ │ └── TooltipContent.tsx
│ │ │ └── utils
│ │ │ ├── abstractions.ts
│ │ │ └── ChartProvider.tsx
│ │ ├── Checkbox
│ │ │ ├── Checkbox.md
│ │ │ ├── Checkbox.spec.ts
│ │ │ └── Checkbox.tsx
│ │ ├── CodeBlock
│ │ │ ├── CodeBlock.module.scss
│ │ │ ├── CodeBlock.spec.ts
│ │ │ ├── CodeBlock.tsx
│ │ │ ├── CodeBlockNative.tsx
│ │ │ └── highlight-code.ts
│ │ ├── collectedComponentMetadata.ts
│ │ ├── ColorPicker
│ │ │ ├── ColorPicker.md
│ │ │ ├── ColorPicker.module.scss
│ │ │ ├── ColorPicker.spec.ts
│ │ │ ├── ColorPicker.tsx
│ │ │ └── ColorPickerNative.tsx
│ │ ├── Column
│ │ │ ├── Column.md
│ │ │ ├── Column.tsx
│ │ │ ├── ColumnNative.tsx
│ │ │ ├── doc-resources
│ │ │ │ └── list-component-data.js
│ │ │ └── TableContext.tsx
│ │ ├── component-utils.ts
│ │ ├── ComponentProvider.tsx
│ │ ├── ComponentRegistryContext.tsx
│ │ ├── container-helpers.tsx
│ │ ├── ContentSeparator
│ │ │ ├── ContentSeparator.md
│ │ │ ├── ContentSeparator.module.scss
│ │ │ ├── ContentSeparator.spec.ts
│ │ │ ├── ContentSeparator.tsx
│ │ │ └── ContentSeparatorNative.tsx
│ │ ├── DataSource
│ │ │ ├── DataSource.md
│ │ │ └── DataSource.tsx
│ │ ├── DateInput
│ │ │ ├── DateInput.md
│ │ │ ├── DateInput.module.scss
│ │ │ ├── DateInput.spec.ts
│ │ │ ├── DateInput.tsx
│ │ │ └── DateInputNative.tsx
│ │ ├── DatePicker
│ │ │ ├── DatePicker.md
│ │ │ ├── DatePicker.module.scss
│ │ │ ├── DatePicker.spec.ts
│ │ │ ├── DatePicker.tsx
│ │ │ └── DatePickerNative.tsx
│ │ ├── DropdownMenu
│ │ │ ├── DropdownMenu.md
│ │ │ ├── DropdownMenu.module.scss
│ │ │ ├── DropdownMenu.spec.ts
│ │ │ ├── DropdownMenu.tsx
│ │ │ ├── DropdownMenuNative.tsx
│ │ │ ├── MenuItem.md
│ │ │ └── SubMenuItem.md
│ │ ├── EmojiSelector
│ │ │ ├── EmojiSelector.md
│ │ │ ├── EmojiSelector.spec.ts
│ │ │ ├── EmojiSelector.tsx
│ │ │ └── EmojiSelectorNative.tsx
│ │ ├── ExpandableItem
│ │ │ ├── ExpandableItem.module.scss
│ │ │ ├── ExpandableItem.spec.ts
│ │ │ ├── ExpandableItem.tsx
│ │ │ └── ExpandableItemNative.tsx
│ │ ├── FileInput
│ │ │ ├── FileInput.md
│ │ │ ├── FileInput.module.scss
│ │ │ ├── FileInput.spec.ts
│ │ │ ├── FileInput.tsx
│ │ │ └── FileInputNative.tsx
│ │ ├── FileUploadDropZone
│ │ │ ├── FileUploadDropZone.md
│ │ │ ├── FileUploadDropZone.module.scss
│ │ │ ├── FileUploadDropZone.spec.ts
│ │ │ ├── FileUploadDropZone.tsx
│ │ │ └── FileUploadDropZoneNative.tsx
│ │ ├── FlowLayout
│ │ │ ├── FlowLayout.md
│ │ │ ├── FlowLayout.module.scss
│ │ │ ├── FlowLayout.spec.ts
│ │ │ ├── FlowLayout.spec.ts-snapshots
│ │ │ │ └── Edge-cases-boxShadow-is-not-clipped-1-non-smoke-darwin.png
│ │ │ ├── FlowLayout.tsx
│ │ │ └── FlowLayoutNative.tsx
│ │ ├── Footer
│ │ │ ├── Footer.md
│ │ │ ├── Footer.module.scss
│ │ │ ├── Footer.spec.ts
│ │ │ ├── Footer.tsx
│ │ │ └── FooterNative.tsx
│ │ ├── Form
│ │ │ ├── Form.md
│ │ │ ├── Form.module.scss
│ │ │ ├── Form.spec.ts
│ │ │ ├── Form.tsx
│ │ │ ├── formActions.ts
│ │ │ ├── FormContext.ts
│ │ │ └── FormNative.tsx
│ │ ├── FormItem
│ │ │ ├── FormItem.md
│ │ │ ├── FormItem.module.scss
│ │ │ ├── FormItem.spec.ts
│ │ │ ├── FormItem.tsx
│ │ │ ├── FormItemNative.tsx
│ │ │ ├── HelperText.module.scss
│ │ │ ├── HelperText.tsx
│ │ │ ├── ItemWithLabel.tsx
│ │ │ └── Validations.ts
│ │ ├── FormSection
│ │ │ ├── FormSection.md
│ │ │ ├── FormSection.ts
│ │ │ └── FormSection.xmlui
│ │ ├── Fragment
│ │ │ ├── Fragment.spec.ts
│ │ │ └── Fragment.tsx
│ │ ├── Heading
│ │ │ ├── abstractions.ts
│ │ │ ├── H1.md
│ │ │ ├── H1.spec.ts
│ │ │ ├── H2.md
│ │ │ ├── H2.spec.ts
│ │ │ ├── H3.md
│ │ │ ├── H3.spec.ts
│ │ │ ├── H4.md
│ │ │ ├── H4.spec.ts
│ │ │ ├── H5.md
│ │ │ ├── H5.spec.ts
│ │ │ ├── H6.md
│ │ │ ├── H6.spec.ts
│ │ │ ├── Heading.md
│ │ │ ├── Heading.module.scss
│ │ │ ├── Heading.spec.ts
│ │ │ ├── Heading.tsx
│ │ │ └── HeadingNative.tsx
│ │ ├── HoverCard
│ │ │ ├── HoverCard.tsx
│ │ │ └── HovercardNative.tsx
│ │ ├── HtmlTags
│ │ │ ├── HtmlTags.module.scss
│ │ │ ├── HtmlTags.spec.ts
│ │ │ └── HtmlTags.tsx
│ │ ├── Icon
│ │ │ ├── AdmonitionDanger.tsx
│ │ │ ├── AdmonitionInfo.tsx
│ │ │ ├── AdmonitionNote.tsx
│ │ │ ├── AdmonitionTip.tsx
│ │ │ ├── AdmonitionWarning.tsx
│ │ │ ├── ApiIcon.tsx
│ │ │ ├── ArrowDropDown.module.scss
│ │ │ ├── ArrowDropDown.tsx
│ │ │ ├── ArrowDropUp.module.scss
│ │ │ ├── ArrowDropUp.tsx
│ │ │ ├── ArrowLeft.module.scss
│ │ │ ├── ArrowLeft.tsx
│ │ │ ├── ArrowRight.module.scss
│ │ │ ├── ArrowRight.tsx
│ │ │ ├── Attach.tsx
│ │ │ ├── Binding.module.scss
│ │ │ ├── Binding.tsx
│ │ │ ├── BoardIcon.tsx
│ │ │ ├── BoxIcon.tsx
│ │ │ ├── CheckIcon.tsx
│ │ │ ├── ChevronDownIcon.tsx
│ │ │ ├── ChevronLeft.tsx
│ │ │ ├── ChevronRight.tsx
│ │ │ ├── ChevronUpIcon.tsx
│ │ │ ├── CodeFileIcon.tsx
│ │ │ ├── CodeSandbox.tsx
│ │ │ ├── CompactListIcon.tsx
│ │ │ ├── ContentCopyIcon.tsx
│ │ │ ├── DarkToLightIcon.tsx
│ │ │ ├── DatabaseIcon.module.scss
│ │ │ ├── DatabaseIcon.tsx
│ │ │ ├── DocFileIcon.tsx
│ │ │ ├── DocIcon.tsx
│ │ │ ├── DotMenuHorizontalIcon.tsx
│ │ │ ├── DotMenuIcon.tsx
│ │ │ ├── EmailIcon.tsx
│ │ │ ├── EmptyFolderIcon.tsx
│ │ │ ├── ErrorIcon.tsx
│ │ │ ├── ExpressionIcon.tsx
│ │ │ ├── FillPlusCricleIcon.tsx
│ │ │ ├── FilterIcon.tsx
│ │ │ ├── FolderIcon.tsx
│ │ │ ├── GlobeIcon.tsx
│ │ │ ├── HomeIcon.tsx
│ │ │ ├── HyperLinkIcon.tsx
│ │ │ ├── Icon.md
│ │ │ ├── Icon.module.scss
│ │ │ ├── Icon.spec.ts
│ │ │ ├── Icon.tsx
│ │ │ ├── IconNative.tsx
│ │ │ ├── ImageFileIcon.tsx
│ │ │ ├── Inspect.tsx
│ │ │ ├── LightToDark.tsx
│ │ │ ├── LinkIcon.tsx
│ │ │ ├── ListIcon.tsx
│ │ │ ├── LooseListIcon.tsx
│ │ │ ├── MoonIcon.tsx
│ │ │ ├── MoreOptionsIcon.tsx
│ │ │ ├── NoSortIcon.tsx
│ │ │ ├── PDFIcon.tsx
│ │ │ ├── PenIcon.tsx
│ │ │ ├── PhoneIcon.tsx
│ │ │ ├── PhotoIcon.tsx
│ │ │ ├── PlusIcon.tsx
│ │ │ ├── SearchIcon.tsx
│ │ │ ├── ShareIcon.tsx
│ │ │ ├── SortAscendingIcon.tsx
│ │ │ ├── SortDescendingIcon.tsx
│ │ │ ├── StarsIcon.tsx
│ │ │ ├── SunIcon.tsx
│ │ │ ├── svg
│ │ │ │ ├── admonition_danger.svg
│ │ │ │ ├── admonition_info.svg
│ │ │ │ ├── admonition_note.svg
│ │ │ │ ├── admonition_tip.svg
│ │ │ │ ├── admonition_warning.svg
│ │ │ │ ├── api.svg
│ │ │ │ ├── arrow-dropdown.svg
│ │ │ │ ├── arrow-left.svg
│ │ │ │ ├── arrow-right.svg
│ │ │ │ ├── arrow-up.svg
│ │ │ │ ├── attach.svg
│ │ │ │ ├── binding.svg
│ │ │ │ ├── box.svg
│ │ │ │ ├── bulb.svg
│ │ │ │ ├── code-file.svg
│ │ │ │ ├── code-sandbox.svg
│ │ │ │ ├── dark_to_light.svg
│ │ │ │ ├── database.svg
│ │ │ │ ├── doc.svg
│ │ │ │ ├── empty-folder.svg
│ │ │ │ ├── expression.svg
│ │ │ │ ├── eye-closed.svg
│ │ │ │ ├── eye-dark.svg
│ │ │ │ ├── eye.svg
│ │ │ │ ├── file-text.svg
│ │ │ │ ├── filter.svg
│ │ │ │ ├── folder.svg
│ │ │ │ ├── img.svg
│ │ │ │ ├── inspect.svg
│ │ │ │ ├── light_to_dark.svg
│ │ │ │ ├── moon.svg
│ │ │ │ ├── pdf.svg
│ │ │ │ ├── photo.svg
│ │ │ │ ├── share.svg
│ │ │ │ ├── stars.svg
│ │ │ │ ├── sun.svg
│ │ │ │ ├── trending-down.svg
│ │ │ │ ├── trending-level.svg
│ │ │ │ ├── trending-up.svg
│ │ │ │ ├── txt.svg
│ │ │ │ ├── unknown-file.svg
│ │ │ │ ├── unlink.svg
│ │ │ │ └── xls.svg
│ │ │ ├── TableDeleteColumnIcon.tsx
│ │ │ ├── TableDeleteRowIcon.tsx
│ │ │ ├── TableInsertColumnIcon.tsx
│ │ │ ├── TableInsertRowIcon.tsx
│ │ │ ├── TrashIcon.tsx
│ │ │ ├── TrendingDownIcon.tsx
│ │ │ ├── TrendingLevelIcon.tsx
│ │ │ ├── TrendingUpIcon.tsx
│ │ │ ├── TxtIcon.tsx
│ │ │ ├── UnknownFileIcon.tsx
│ │ │ ├── UnlinkIcon.tsx
│ │ │ ├── UserIcon.tsx
│ │ │ ├── WarningIcon.tsx
│ │ │ └── XlsIcon.tsx
│ │ ├── IconProvider.tsx
│ │ ├── IconRegistryContext.tsx
│ │ ├── IFrame
│ │ │ ├── IFrame.md
│ │ │ ├── IFrame.module.scss
│ │ │ ├── IFrame.spec.ts
│ │ │ ├── IFrame.tsx
│ │ │ └── IFrameNative.tsx
│ │ ├── Image
│ │ │ ├── Image.md
│ │ │ ├── Image.module.scss
│ │ │ ├── Image.spec.ts
│ │ │ ├── Image.tsx
│ │ │ └── ImageNative.tsx
│ │ ├── Input
│ │ │ ├── index.ts
│ │ │ ├── InputAdornment.module.scss
│ │ │ ├── InputAdornment.tsx
│ │ │ ├── InputDivider.module.scss
│ │ │ ├── InputDivider.tsx
│ │ │ ├── InputLabel.module.scss
│ │ │ ├── InputLabel.tsx
│ │ │ ├── PartialInput.module.scss
│ │ │ └── PartialInput.tsx
│ │ ├── InspectButton
│ │ │ ├── InspectButton.module.scss
│ │ │ └── InspectButton.tsx
│ │ ├── Items
│ │ │ ├── Items.md
│ │ │ ├── Items.spec.ts
│ │ │ ├── Items.tsx
│ │ │ └── ItemsNative.tsx
│ │ ├── Link
│ │ │ ├── Link.md
│ │ │ ├── Link.module.scss
│ │ │ ├── Link.spec.ts
│ │ │ ├── Link.tsx
│ │ │ └── LinkNative.tsx
│ │ ├── List
│ │ │ ├── doc-resources
│ │ │ │ └── list-component-data.js
│ │ │ ├── List.md
│ │ │ ├── List.module.scss
│ │ │ ├── List.spec.ts
│ │ │ ├── List.tsx
│ │ │ └── ListNative.tsx
│ │ ├── Logo
│ │ │ ├── doc-resources
│ │ │ │ └── xmlui-logo.svg
│ │ │ ├── Logo.md
│ │ │ ├── Logo.tsx
│ │ │ └── LogoNative.tsx
│ │ ├── Markdown
│ │ │ ├── CodeText.module.scss
│ │ │ ├── CodeText.tsx
│ │ │ ├── Markdown.md
│ │ │ ├── Markdown.module.scss
│ │ │ ├── Markdown.spec.ts
│ │ │ ├── Markdown.tsx
│ │ │ ├── MarkdownNative.tsx
│ │ │ ├── parse-binding-expr.ts
│ │ │ └── utils.ts
│ │ ├── metadata-helpers.ts
│ │ ├── ModalDialog
│ │ │ ├── ConfirmationModalContextProvider.tsx
│ │ │ ├── Dialog.module.scss
│ │ │ ├── Dialog.tsx
│ │ │ ├── ModalDialog.md
│ │ │ ├── ModalDialog.module.scss
│ │ │ ├── ModalDialog.spec.ts
│ │ │ ├── ModalDialog.tsx
│ │ │ ├── ModalDialogNative.tsx
│ │ │ └── ModalVisibilityContext.tsx
│ │ ├── NavGroup
│ │ │ ├── NavGroup.md
│ │ │ ├── NavGroup.module.scss
│ │ │ ├── NavGroup.spec.ts
│ │ │ ├── NavGroup.tsx
│ │ │ ├── NavGroupContext.ts
│ │ │ └── NavGroupNative.tsx
│ │ ├── NavLink
│ │ │ ├── NavLink.md
│ │ │ ├── NavLink.module.scss
│ │ │ ├── NavLink.spec.ts
│ │ │ ├── NavLink.tsx
│ │ │ └── NavLinkNative.tsx
│ │ ├── NavPanel
│ │ │ ├── NavPanel.md
│ │ │ ├── NavPanel.module.scss
│ │ │ ├── NavPanel.spec.ts
│ │ │ ├── NavPanel.tsx
│ │ │ └── NavPanelNative.tsx
│ │ ├── NestedApp
│ │ │ ├── AppWithCodeView.module.scss
│ │ │ ├── AppWithCodeView.tsx
│ │ │ ├── AppWithCodeViewNative.tsx
│ │ │ ├── defaultProps.tsx
│ │ │ ├── logo.svg
│ │ │ ├── NestedApp.module.scss
│ │ │ ├── NestedApp.tsx
│ │ │ ├── NestedAppNative.tsx
│ │ │ ├── Tooltip.module.scss
│ │ │ ├── Tooltip.tsx
│ │ │ └── utils.ts
│ │ ├── NoResult
│ │ │ ├── NoResult.md
│ │ │ ├── NoResult.module.scss
│ │ │ ├── NoResult.spec.ts
│ │ │ ├── NoResult.tsx
│ │ │ └── NoResultNative.tsx
│ │ ├── NumberBox
│ │ │ ├── numberbox-abstractions.ts
│ │ │ ├── NumberBox.md
│ │ │ ├── NumberBox.module.scss
│ │ │ ├── NumberBox.spec.ts
│ │ │ ├── NumberBox.tsx
│ │ │ └── NumberBoxNative.tsx
│ │ ├── Option
│ │ │ ├── Option.md
│ │ │ ├── Option.spec.ts
│ │ │ ├── Option.tsx
│ │ │ ├── OptionNative.tsx
│ │ │ └── OptionTypeProvider.tsx
│ │ ├── PageMetaTitle
│ │ │ ├── PageMetaTilteNative.tsx
│ │ │ ├── PageMetaTitle.md
│ │ │ ├── PageMetaTitle.spec.ts
│ │ │ └── PageMetaTitle.tsx
│ │ ├── Pages
│ │ │ ├── Page.md
│ │ │ ├── Pages.md
│ │ │ ├── Pages.module.scss
│ │ │ ├── Pages.tsx
│ │ │ └── PagesNative.tsx
│ │ ├── Pagination
│ │ │ ├── Pagination.md
│ │ │ ├── Pagination.module.scss
│ │ │ ├── Pagination.spec.ts
│ │ │ ├── Pagination.tsx
│ │ │ └── PaginationNative.tsx
│ │ ├── PositionedContainer
│ │ │ ├── PositionedContainer.module.scss
│ │ │ ├── PositionedContainer.tsx
│ │ │ └── PositionedContainerNative.tsx
│ │ ├── ProfileMenu
│ │ │ ├── ProfileMenu.module.scss
│ │ │ └── ProfileMenu.tsx
│ │ ├── ProgressBar
│ │ │ ├── ProgressBar.md
│ │ │ ├── ProgressBar.module.scss
│ │ │ ├── ProgressBar.spec.ts
│ │ │ ├── ProgressBar.tsx
│ │ │ └── ProgressBarNative.tsx
│ │ ├── Queue
│ │ │ ├── Queue.md
│ │ │ ├── Queue.spec.ts
│ │ │ ├── Queue.tsx
│ │ │ ├── queueActions.ts
│ │ │ └── QueueNative.tsx
│ │ ├── RadioGroup
│ │ │ ├── RadioGroup.md
│ │ │ ├── RadioGroup.module.scss
│ │ │ ├── RadioGroup.spec.ts
│ │ │ ├── RadioGroup.tsx
│ │ │ ├── RadioGroupNative.tsx
│ │ │ ├── RadioItem.tsx
│ │ │ └── RadioItemNative.tsx
│ │ ├── RealTimeAdapter
│ │ │ ├── RealTimeAdapter.tsx
│ │ │ └── RealTimeAdapterNative.tsx
│ │ ├── Redirect
│ │ │ ├── Redirect.md
│ │ │ ├── Redirect.spec.ts
│ │ │ └── Redirect.tsx
│ │ ├── ResponsiveBar
│ │ │ ├── README.md
│ │ │ ├── ResponsiveBar.md
│ │ │ ├── ResponsiveBar.module.scss
│ │ │ ├── ResponsiveBar.spec.ts
│ │ │ ├── ResponsiveBar.tsx
│ │ │ └── ResponsiveBarNative.tsx
│ │ ├── Select
│ │ │ ├── HiddenOption.tsx
│ │ │ ├── OptionContext.ts
│ │ │ ├── Select.md
│ │ │ ├── Select.module.scss
│ │ │ ├── Select.spec.ts
│ │ │ ├── Select.tsx
│ │ │ ├── SelectContext.tsx
│ │ │ └── SelectNative.tsx
│ │ ├── SelectionStore
│ │ │ ├── SelectionStore.md
│ │ │ ├── SelectionStore.tsx
│ │ │ └── SelectionStoreNative.tsx
│ │ ├── Slider
│ │ │ ├── Slider.md
│ │ │ ├── Slider.module.scss
│ │ │ ├── Slider.spec.ts
│ │ │ ├── Slider.tsx
│ │ │ └── SliderNative.tsx
│ │ ├── Slot
│ │ │ ├── Slot.md
│ │ │ ├── Slot.spec.ts
│ │ │ └── Slot.ts
│ │ ├── SlotItem.tsx
│ │ ├── SpaceFiller
│ │ │ ├── SpaceFiller.md
│ │ │ ├── SpaceFiller.module.scss
│ │ │ ├── SpaceFiller.spec.ts
│ │ │ ├── SpaceFiller.tsx
│ │ │ └── SpaceFillerNative.tsx
│ │ ├── Spinner
│ │ │ ├── Spinner.md
│ │ │ ├── Spinner.module.scss
│ │ │ ├── Spinner.spec.ts
│ │ │ ├── Spinner.tsx
│ │ │ └── SpinnerNative.tsx
│ │ ├── Splitter
│ │ │ ├── HSplitter.md
│ │ │ ├── HSplitter.spec.ts
│ │ │ ├── Splitter.md
│ │ │ ├── Splitter.module.scss
│ │ │ ├── Splitter.spec.ts
│ │ │ ├── Splitter.tsx
│ │ │ ├── SplitterNative.tsx
│ │ │ ├── utils.ts
│ │ │ ├── VSplitter.md
│ │ │ └── VSplitter.spec.ts
│ │ ├── Stack
│ │ │ ├── CHStack.md
│ │ │ ├── CHStack.spec.ts
│ │ │ ├── CVStack.md
│ │ │ ├── CVStack.spec.ts
│ │ │ ├── HStack.md
│ │ │ ├── HStack.spec.ts
│ │ │ ├── Stack.md
│ │ │ ├── Stack.module.scss
│ │ │ ├── Stack.spec.ts
│ │ │ ├── Stack.tsx
│ │ │ ├── StackNative.tsx
│ │ │ ├── VStack.md
│ │ │ └── VStack.spec.ts
│ │ ├── StickyBox
│ │ │ ├── StickyBox.md
│ │ │ ├── StickyBox.module.scss
│ │ │ ├── StickyBox.tsx
│ │ │ └── StickyBoxNative.tsx
│ │ ├── Switch
│ │ │ ├── Switch.md
│ │ │ ├── Switch.spec.ts
│ │ │ └── Switch.tsx
│ │ ├── Table
│ │ │ ├── doc-resources
│ │ │ │ └── list-component-data.js
│ │ │ ├── react-table-config.d.ts
│ │ │ ├── Table.md
│ │ │ ├── Table.module.scss
│ │ │ ├── Table.spec.ts
│ │ │ ├── Table.tsx
│ │ │ ├── TableNative.tsx
│ │ │ └── useRowSelection.tsx
│ │ ├── TableOfContents
│ │ │ ├── TableOfContents.module.scss
│ │ │ ├── TableOfContents.spec.ts
│ │ │ ├── TableOfContents.tsx
│ │ │ └── TableOfContentsNative.tsx
│ │ ├── Tabs
│ │ │ ├── TabContext.tsx
│ │ │ ├── TabItem.md
│ │ │ ├── TabItem.tsx
│ │ │ ├── TabItemNative.tsx
│ │ │ ├── Tabs.md
│ │ │ ├── Tabs.module.scss
│ │ │ ├── Tabs.spec.ts
│ │ │ ├── Tabs.tsx
│ │ │ └── TabsNative.tsx
│ │ ├── Text
│ │ │ ├── Text.md
│ │ │ ├── Text.module.scss
│ │ │ ├── Text.spec.ts
│ │ │ ├── Text.tsx
│ │ │ └── TextNative.tsx
│ │ ├── TextArea
│ │ │ ├── TextArea.md
│ │ │ ├── TextArea.module.scss
│ │ │ ├── TextArea.spec.ts
│ │ │ ├── TextArea.tsx
│ │ │ ├── TextAreaNative.tsx
│ │ │ ├── TextAreaResizable.tsx
│ │ │ └── useComposedRef.ts
│ │ ├── TextBox
│ │ │ ├── TextBox.md
│ │ │ ├── TextBox.module.scss
│ │ │ ├── TextBox.spec.ts
│ │ │ ├── TextBox.tsx
│ │ │ └── TextBoxNative.tsx
│ │ ├── Theme
│ │ │ ├── NotificationToast.tsx
│ │ │ ├── Theme.md
│ │ │ ├── Theme.module.scss
│ │ │ ├── Theme.spec.ts
│ │ │ ├── Theme.tsx
│ │ │ └── ThemeNative.tsx
│ │ ├── TimeInput
│ │ │ ├── TimeInput.md
│ │ │ ├── TimeInput.module.scss
│ │ │ ├── TimeInput.spec.ts
│ │ │ ├── TimeInput.tsx
│ │ │ ├── TimeInputNative.tsx
│ │ │ └── utils.ts
│ │ ├── Timer
│ │ │ ├── Timer.md
│ │ │ ├── Timer.spec.ts
│ │ │ ├── Timer.tsx
│ │ │ └── TimerNative.tsx
│ │ ├── Toggle
│ │ │ ├── Toggle.module.scss
│ │ │ └── Toggle.tsx
│ │ ├── ToneChangerButton
│ │ │ ├── ToneChangerButton.md
│ │ │ ├── ToneChangerButton.spec.ts
│ │ │ └── ToneChangerButton.tsx
│ │ ├── ToneSwitch
│ │ │ ├── ToneSwitch.md
│ │ │ ├── ToneSwitch.module.scss
│ │ │ ├── ToneSwitch.spec.ts
│ │ │ ├── ToneSwitch.tsx
│ │ │ └── ToneSwitchNative.tsx
│ │ ├── Tooltip
│ │ │ ├── Tooltip.md
│ │ │ ├── Tooltip.module.scss
│ │ │ ├── Tooltip.spec.ts
│ │ │ ├── Tooltip.tsx
│ │ │ └── TooltipNative.tsx
│ │ ├── Tree
│ │ │ ├── testData.ts
│ │ │ ├── Tree-dynamic.spec.ts
│ │ │ ├── Tree-icons.spec.ts
│ │ │ ├── Tree.md
│ │ │ ├── Tree.spec.ts
│ │ │ ├── TreeComponent.module.scss
│ │ │ ├── TreeComponent.tsx
│ │ │ └── TreeNative.tsx
│ │ ├── TreeDisplay
│ │ │ ├── TreeDisplay.md
│ │ │ ├── TreeDisplay.module.scss
│ │ │ ├── TreeDisplay.tsx
│ │ │ └── TreeDisplayNative.tsx
│ │ ├── ValidationSummary
│ │ │ ├── ValidationSummary.module.scss
│ │ │ └── ValidationSummary.tsx
│ │ └── VisuallyHidden.tsx
│ ├── components-core
│ │ ├── abstractions
│ │ │ ├── ComponentRenderer.ts
│ │ │ ├── LoaderRenderer.ts
│ │ │ ├── standalone.ts
│ │ │ └── treeAbstractions.ts
│ │ ├── action
│ │ │ ├── actions.ts
│ │ │ ├── APICall.tsx
│ │ │ ├── FileDownloadAction.tsx
│ │ │ ├── FileUploadAction.tsx
│ │ │ ├── NavigateAction.tsx
│ │ │ └── TimedAction.tsx
│ │ ├── ApiBoundComponent.tsx
│ │ ├── appContext
│ │ │ ├── date-functions.ts
│ │ │ ├── math-function.ts
│ │ │ └── misc-utils.ts
│ │ ├── AppContext.tsx
│ │ ├── behaviors
│ │ │ ├── Behavior.tsx
│ │ │ └── CoreBehaviors.tsx
│ │ ├── component-hooks.ts
│ │ ├── ComponentDecorator.tsx
│ │ ├── ComponentViewer.tsx
│ │ ├── CompoundComponent.tsx
│ │ ├── constants.ts
│ │ ├── DebugViewProvider.tsx
│ │ ├── descriptorHelper.ts
│ │ ├── devtools
│ │ │ ├── InspectorDialog.module.scss
│ │ │ ├── InspectorDialog.tsx
│ │ │ └── InspectorDialogVisibilityContext.tsx
│ │ ├── EngineError.ts
│ │ ├── event-handlers.ts
│ │ ├── InspectorButton.module.scss
│ │ ├── InspectorContext.tsx
│ │ ├── interception
│ │ │ ├── abstractions.ts
│ │ │ ├── ApiInterceptor.ts
│ │ │ ├── ApiInterceptorProvider.tsx
│ │ │ ├── apiInterceptorWorker.ts
│ │ │ ├── Backend.ts
│ │ │ ├── Errors.ts
│ │ │ ├── IndexedDb.ts
│ │ │ ├── initMock.ts
│ │ │ ├── InMemoryDb.ts
│ │ │ ├── ReadonlyCollection.ts
│ │ │ └── useApiInterceptorContext.tsx
│ │ ├── loader
│ │ │ ├── ApiLoader.tsx
│ │ │ ├── DataLoader.tsx
│ │ │ ├── ExternalDataLoader.tsx
│ │ │ ├── Loader.tsx
│ │ │ ├── MockLoaderRenderer.tsx
│ │ │ └── PageableLoader.tsx
│ │ ├── LoaderComponent.tsx
│ │ ├── markup-check.ts
│ │ ├── parts.ts
│ │ ├── renderers.ts
│ │ ├── rendering
│ │ │ ├── AppContent.tsx
│ │ │ ├── AppRoot.tsx
│ │ │ ├── AppWrapper.tsx
│ │ │ ├── buildProxy.ts
│ │ │ ├── collectFnVarDeps.ts
│ │ │ ├── ComponentAdapter.tsx
│ │ │ ├── ComponentWrapper.tsx
│ │ │ ├── Container.tsx
│ │ │ ├── containers.ts
│ │ │ ├── ContainerWrapper.tsx
│ │ │ ├── ErrorBoundary.module.scss
│ │ │ ├── ErrorBoundary.tsx
│ │ │ ├── InvalidComponent.module.scss
│ │ │ ├── InvalidComponent.tsx
│ │ │ ├── nodeUtils.ts
│ │ │ ├── reducer.ts
│ │ │ ├── renderChild.tsx
│ │ │ ├── StandaloneComponent.tsx
│ │ │ ├── StateContainer.tsx
│ │ │ ├── UnknownComponent.module.scss
│ │ │ ├── UnknownComponent.tsx
│ │ │ └── valueExtractor.ts
│ │ ├── reportEngineError.ts
│ │ ├── RestApiProxy.ts
│ │ ├── script-runner
│ │ │ ├── asyncProxy.ts
│ │ │ ├── AttributeValueParser.ts
│ │ │ ├── bannedFunctions.ts
│ │ │ ├── BindingTreeEvaluationContext.ts
│ │ │ ├── eval-tree-async.ts
│ │ │ ├── eval-tree-common.ts
│ │ │ ├── eval-tree-sync.ts
│ │ │ ├── ParameterParser.ts
│ │ │ ├── process-statement-async.ts
│ │ │ ├── process-statement-common.ts
│ │ │ ├── process-statement-sync.ts
│ │ │ ├── ScriptingSourceTree.ts
│ │ │ ├── simplify-expression.ts
│ │ │ ├── statement-queue.ts
│ │ │ └── visitors.ts
│ │ ├── StandaloneApp.tsx
│ │ ├── StandaloneExtensionManager.ts
│ │ ├── TableOfContentsContext.tsx
│ │ ├── theming
│ │ │ ├── _themes.scss
│ │ │ ├── component-layout-resolver.ts
│ │ │ ├── extendThemeUtils.ts
│ │ │ ├── hvar.ts
│ │ │ ├── layout-resolver.ts
│ │ │ ├── parse-layout-props.ts
│ │ │ ├── StyleContext.tsx
│ │ │ ├── StyleRegistry.ts
│ │ │ ├── ThemeContext.tsx
│ │ │ ├── ThemeProvider.tsx
│ │ │ ├── themes
│ │ │ │ ├── base-utils.ts
│ │ │ │ ├── palette.ts
│ │ │ │ ├── root.ts
│ │ │ │ ├── solid.ts
│ │ │ │ ├── theme-colors.ts
│ │ │ │ └── xmlui.ts
│ │ │ ├── themeVars.module.scss
│ │ │ ├── themeVars.ts
│ │ │ ├── transformThemeVars.ts
│ │ │ └── utils.ts
│ │ ├── utils
│ │ │ ├── actionUtils.ts
│ │ │ ├── audio-utils.ts
│ │ │ ├── base64-utils.ts
│ │ │ ├── compound-utils.ts
│ │ │ ├── css-utils.ts
│ │ │ ├── DataLoaderQueryKeyGenerator.ts
│ │ │ ├── date-utils.ts
│ │ │ ├── extractParam.ts
│ │ │ ├── hooks.tsx
│ │ │ ├── LruCache.ts
│ │ │ ├── mergeProps.ts
│ │ │ ├── misc.ts
│ │ │ ├── request-params.ts
│ │ │ ├── statementUtils.ts
│ │ │ └── treeUtils.ts
│ │ └── xmlui-parser.ts
│ ├── index-standalone.ts
│ ├── index.scss
│ ├── index.ts
│ ├── language-server
│ │ ├── server-common.ts
│ │ ├── server-web-worker.ts
│ │ ├── server.ts
│ │ ├── services
│ │ │ ├── common
│ │ │ │ ├── docs-generation.ts
│ │ │ │ ├── lsp-utils.ts
│ │ │ │ ├── metadata-utils.ts
│ │ │ │ └── syntax-node-utilities.ts
│ │ │ ├── completion.ts
│ │ │ ├── diagnostic.ts
│ │ │ ├── format.ts
│ │ │ └── hover.ts
│ │ └── xmlui-metadata-generated.js
│ ├── logging
│ │ ├── LoggerContext.tsx
│ │ ├── LoggerInitializer.tsx
│ │ ├── LoggerService.ts
│ │ └── xmlui.ts
│ ├── logo.svg
│ ├── parsers
│ │ ├── common
│ │ │ ├── GenericToken.ts
│ │ │ ├── InputStream.ts
│ │ │ └── utils.ts
│ │ ├── scripting
│ │ │ ├── code-behind-collect.ts
│ │ │ ├── Lexer.ts
│ │ │ ├── modules.ts
│ │ │ ├── Parser.ts
│ │ │ ├── ParserError.ts
│ │ │ ├── ScriptingNodeTypes.ts
│ │ │ ├── TokenTrait.ts
│ │ │ ├── TokenType.ts
│ │ │ └── tree-visitor.ts
│ │ ├── style-parser
│ │ │ ├── errors.ts
│ │ │ ├── source-tree.ts
│ │ │ ├── StyleInputStream.ts
│ │ │ ├── StyleLexer.ts
│ │ │ ├── StyleParser.ts
│ │ │ └── tokens.ts
│ │ └── xmlui-parser
│ │ ├── CharacterCodes.ts
│ │ ├── diagnostics.ts
│ │ ├── fileExtensions.ts
│ │ ├── index.ts
│ │ ├── lint.ts
│ │ ├── parser.ts
│ │ ├── ParserError.ts
│ │ ├── scanner.ts
│ │ ├── syntax-kind.ts
│ │ ├── syntax-node.ts
│ │ ├── transform.ts
│ │ ├── utils.ts
│ │ ├── xmlui-serializer.ts
│ │ └── xmlui-tree.ts
│ ├── react-app-env.d.ts
│ ├── syntax
│ │ ├── monaco
│ │ │ ├── grammar.monacoLanguage.ts
│ │ │ ├── index.ts
│ │ │ ├── xmlui-dark.ts
│ │ │ ├── xmlui-light.ts
│ │ │ └── xmluiscript.monacoLanguage.ts
│ │ └── textMate
│ │ ├── index.ts
│ │ ├── xmlui-dark.json
│ │ ├── xmlui-light.json
│ │ ├── xmlui.json
│ │ └── xmlui.tmLanguage.json
│ ├── testing
│ │ ├── assertions.ts
│ │ ├── component-test-helpers.ts
│ │ ├── ComponentDrivers.ts
│ │ ├── drivers
│ │ │ ├── DateInputDriver.ts
│ │ │ ├── index.ts
│ │ │ ├── ModalDialogDriver.ts
│ │ │ ├── NumberBoxDriver.ts
│ │ │ ├── TextBoxDriver.ts
│ │ │ ├── TimeInputDriver.ts
│ │ │ ├── TimerDriver.ts
│ │ │ └── TreeDriver.ts
│ │ ├── fixtures.ts
│ │ ├── index.ts
│ │ ├── infrastructure
│ │ │ ├── index.html
│ │ │ ├── main.tsx
│ │ │ ├── public
│ │ │ │ ├── mockServiceWorker.js
│ │ │ │ ├── resources
│ │ │ │ │ ├── bell.svg
│ │ │ │ │ ├── box.svg
│ │ │ │ │ ├── doc.svg
│ │ │ │ │ ├── eye.svg
│ │ │ │ │ ├── flower-640x480.jpg
│ │ │ │ │ ├── sun.svg
│ │ │ │ │ ├── test-image-100x100.jpg
│ │ │ │ │ └── txt.svg
│ │ │ │ └── serve.json
│ │ │ └── TestBed.tsx
│ │ └── themed-app-test-helpers.ts
│ └── vite-env.d.ts
├── tests
│ ├── components
│ │ ├── CodeBlock
│ │ │ └── hightlight-code.test.ts
│ │ ├── playground-pattern.test.ts
│ │ └── Tree
│ │ └── Tree-states.test.ts
│ ├── components-core
│ │ ├── abstractions
│ │ │ └── treeAbstractions.test.ts
│ │ ├── container
│ │ │ └── buildProxy.test.ts
│ │ ├── interception
│ │ │ ├── orderBy.test.ts
│ │ │ ├── ReadOnlyCollection.test.ts
│ │ │ └── request-param-converter.test.ts
│ │ ├── scripts-runner
│ │ │ ├── AttributeValueParser.test.ts
│ │ │ ├── eval-tree-arrow-async.test.ts
│ │ │ ├── eval-tree-arrow.test.ts
│ │ │ ├── eval-tree-func-decl-async.test.ts
│ │ │ ├── eval-tree-func-decl.test.ts
│ │ │ ├── eval-tree-pre-post.test.ts
│ │ │ ├── eval-tree-regression.test.ts
│ │ │ ├── eval-tree.test.ts
│ │ │ ├── function-proxy.test.ts
│ │ │ ├── parser-regression.test.ts
│ │ │ ├── process-event.test.ts
│ │ │ ├── process-function.test.ts
│ │ │ ├── process-implicit-context.test.ts
│ │ │ ├── process-statement-asgn.test.ts
│ │ │ ├── process-statement-destruct.test.ts
│ │ │ ├── process-statement-regs.test.ts
│ │ │ ├── process-statement-sync.test.ts
│ │ │ ├── process-statement.test.ts
│ │ │ ├── process-switch-sync.test.ts
│ │ │ ├── process-switch.test.ts
│ │ │ ├── process-try-sync.test.ts
│ │ │ ├── process-try.test.ts
│ │ │ └── test-helpers.ts
│ │ ├── test-metadata-handler.ts
│ │ ├── theming
│ │ │ ├── border-segments.test.ts
│ │ │ ├── component-layout.resolver.test.ts
│ │ │ ├── layout-property-parser.test.ts
│ │ │ ├── layout-resolver.test.ts
│ │ │ ├── layout-resolver2.test.ts
│ │ │ ├── layout-vp-override.test.ts
│ │ │ └── padding-segments.test.ts
│ │ └── utils
│ │ ├── date-utils.test.ts
│ │ ├── format-human-elapsed-time.test.ts
│ │ └── LruCache.test.ts
│ ├── language-server
│ │ ├── completion.test.ts
│ │ ├── format.test.ts
│ │ ├── hover.test.ts
│ │ └── mockData.ts
│ └── parsers
│ ├── common
│ │ └── input-stream.test.ts
│ ├── markdown
│ │ └── parse-binding-expression.test.ts
│ ├── parameter-parser.test.ts
│ ├── paremeter-parser.test.ts
│ ├── scripting
│ │ ├── eval-tree-arrow.test.ts
│ │ ├── eval-tree-pre-post.test.ts
│ │ ├── eval-tree.test.ts
│ │ ├── function-proxy.test.ts
│ │ ├── lexer-literals.test.ts
│ │ ├── lexer-misc.test.ts
│ │ ├── module-parse.test.ts
│ │ ├── parser-arrow.test.ts
│ │ ├── parser-assignments.test.ts
│ │ ├── parser-binary.test.ts
│ │ ├── parser-destructuring.test.ts
│ │ ├── parser-errors.test.ts
│ │ ├── parser-expressions.test.ts
│ │ ├── parser-function.test.ts
│ │ ├── parser-literals.test.ts
│ │ ├── parser-primary.test.ts
│ │ ├── parser-regex.test.ts
│ │ ├── parser-statements.test.ts
│ │ ├── parser-unary.test.ts
│ │ ├── process-event.test.ts
│ │ ├── process-implicit-context.test.ts
│ │ ├── process-statement-asgn.test.ts
│ │ ├── process-statement-destruct.test.ts
│ │ ├── process-statement-regs.test.ts
│ │ ├── process-statement-sync.test.ts
│ │ ├── process-statement.test.ts
│ │ ├── process-switch-sync.test.ts
│ │ ├── process-switch.test.ts
│ │ ├── process-try-sync.test.ts
│ │ ├── process-try.test.ts
│ │ ├── simplify-expression.test.ts
│ │ ├── statement-hooks.test.ts
│ │ └── test-helpers.ts
│ ├── style-parser
│ │ ├── generateHvarChain.test.ts
│ │ ├── parseHVar.test.ts
│ │ ├── parser.test.ts
│ │ └── tokens.test.ts
│ └── xmlui
│ ├── lint.test.ts
│ ├── parser.test.ts
│ ├── scanner.test.ts
│ ├── transform.attr.test.ts
│ ├── transform.circular.test.ts
│ ├── transform.element.test.ts
│ ├── transform.errors.test.ts
│ ├── transform.escape.test.ts
│ ├── transform.regression.test.ts
│ ├── transform.script.test.ts
│ ├── transform.test.ts
│ └── xmlui.ts
├── tests-e2e
│ ├── api-bound-component-regression.spec.ts
│ ├── api-call-as-extracted-component.spec.ts
│ ├── assign-to-object-or-array-regression.spec.ts
│ ├── binding-regression.spec.ts
│ ├── children-as-template-context-vars.spec.ts
│ ├── compound-component.spec.ts
│ ├── context-vars-regression.spec.ts
│ ├── data-bindings.spec.ts
│ ├── datasource-and-api-usage-in-var.spec.ts
│ ├── datasource-direct-binding.spec.ts
│ ├── datasource-onLoaded-regression.spec.ts
│ ├── modify-array-item-regression.spec.ts
│ ├── namespaces.spec.ts
│ ├── push-to-array-regression.spec.ts
│ ├── screen-breakpoints.spec.ts
│ ├── scripting.spec.ts
│ ├── state-scope-in-pages.spec.ts
│ └── state-var-scopes.spec.ts
├── tsconfig.json
├── tsdown.config.ts
├── vite.config.ts
└── vitest.config.ts
```
# Files
--------------------------------------------------------------------------------
/docs/public/resources/files/tutorials/datasource/api.ts:
--------------------------------------------------------------------------------
```typescript
import { ApiInterceptorDefinition } from "xmlui";
const mock: ApiInterceptorDefinition = {
type: "db",
config: {
database: "xmluiDataManipuliationTutorial",
version: 1
},
apiUrl: "/api",
helpers: {
Assertions: {
getIfPresent: `(itemId) => {
const found = $db.$shoppingItems.byId(itemId);
if (!found) {
throw Errors.NotFound404("Item id:" + itemId + " not found");
}
return found;
}`
},
},
auth: {
defaultLoggedInUser: {
id: 1
}
},
schemaDescriptor: {
tables: [
{
name: "shoppingItems",
fields: {
id: "bigint",
name: "varchar(64)",
quantity: "integer",
unit: "varchar(64)",
category: "varchar(64)",
inPantry: "boolean",
},
pk: ["++id"],
},
{
name: "polledItems",
fields: {
id: "bigint",
name: "varchar(64)",
quantity: "integer",
unit: "varchar(64)",
category: "varchar(64)",
inPantry: "boolean",
},
pk: ["++id"],
},
{
name: "recipes",
fields: {
id: "bigint",
name: "varchar(64)",
},
pk: ["++id"],
}
],
dtos: {
recipe: {
id: "number",
name: "string",
ingredients: "{shoppingItems[]}",
},
},
},
initialData: {
shoppingItems: [
{
id: 1,
name: "Carrots",
quantity: 100,
unit: "grams",
category: "vegetables",
inPantry: true,
},
{
id: 2,
name: "Bananas",
quantity: 6,
unit: "pieces",
category: "fruits",
inPantry: false,
},
{
id: 3,
name: "Apples",
quantity: 5,
unit: "pieces",
category: "fruits",
inPantry: true,
},
{
id: 4,
name: "Spinach",
quantity: 1,
unit: "bunch",
category: "vegetables",
inPantry: true,
},
{
id: 5,
name: "Milk",
quantity: 10,
unit: "liter",
category: "dairy",
inPantry: false,
},
{
id: 6,
name: "Cheese",
quantity: 200,
unit: "grams",
category: "dairy",
inPantry: false,
},
{
id: 7,
name: "Tomatoes",
quantity: 3,
unit: "pieces",
category: "vegetables",
inPantry: false,
},
{
id: 8,
name: "Oranges",
quantity: 4,
unit: "pieces",
category: "fruits",
inPantry: true,
},
{
id: 9,
name: "Broccoli",
quantity: 2,
unit: "heads",
category: "vegetables",
inPantry: true,
},
{
id: 10,
name: "Eggs",
quantity: 12,
unit: "pieces",
category: "dairy",
inPantry: false,
},
{
id: 11,
name: "Potatoes",
quantity: 1,
unit: "kg",
category: "vegetables",
inPantry: true,
},
{
id: 12,
name: "Grapes",
quantity: 500,
unit: "grams",
category: "fruits",
inPantry: false,
},
{
id: 13,
name: "Chicken",
quantity: 2,
unit: "kg",
category: "meat",
inPantry: false,
},
{
id: 14,
name: "Bread",
quantity: 1,
unit: "loaf",
category: "bakery",
inPantry: true,
},
{
id: 15,
name: "Yogurt",
quantity: 500,
unit: "grams",
category: "dairy",
inPantry: true,
},
{
id: 16,
name: "Olive Oil",
quantity: 1,
unit: "liter",
category: "cooking oils",
inPantry: true,
},
],
polledItems: [],
recipes: [
{
id: 1,
name: "Salad",
ingredients: [
{
id: 1,
name: "Carrots",
quantity: 100,
unit: "grams",
category: "vegetables",
inPantry: true,
},
{
id: 7,
name: "Tomatoes",
quantity: 3,
unit: "pieces",
category: "vegetables",
inPantry: false,
},
{
id: 9,
name: "Broccoli",
quantity: 2,
unit: "heads",
category: "vegetables",
inPantry: true,
},
]
},
{
id: 2,
name: "Orange Banana Smoothie",
ingredients: [
{
id: 2,
name: "Bananas",
quantity: 6,
unit: "pieces",
category: "fruits",
inPantry: false,
},
{
id: 5,
name: "Milk",
quantity: 10,
unit: "liter",
category: "dairy",
inPantry: false,
},
{
id: 8,
name: "Oranges",
quantity: 4,
unit: "pieces",
category: "fruits",
inPantry: true,
},
]
},
{
id: 3,
name: "Carrot Soup",
ingredients: [
{
id: 1,
name: "Carrots",
quantity: 100,
unit: "grams",
category: "vegetables",
inPantry: true,
},
{
id: 16,
name: "Olive Oil",
quantity: 1,
unit: "liter",
category: "cooking oils",
inPantry: true,
},
]
},
],
},
operations: {
login: {
url: "/login",
method: "post",
queryParamTypes: {
userId: "integer"
},
handler: "$authService.login({id: $queryParams.userId})"
},
loadMe: {
url: "/users/me",
method: "get",
handler: "$db.$users.byId($loggedInUser.id)"
},
test: {
url: "/test",
method: "get",
handler: "return { message: 'Hello from the Server!' }",
},
"shopping-list": {
url: "/shopping-list",
method: "get",
responseShape: "shoppingItem[]",
handler: `$db.$shoppingItems.toArray()`
},
"shopping-list-slow": {
url: "/shopping-list-slow",
method: "get",
responseShape: "shoppingItem[]",
handler: `
delay(8000);
return $db.$shoppingItems.toArray();`
},
"shopping-list-create": {
url: "/shopping-list",
method: "post",
queryParamTypes: {
highlight: "boolean",
},
responseShape: "shoppingItem?",
handler: `{
if ($queryParams.highlight) {
$requestBody.name = $requestBody.name.toUpperCase() + "!!!";
}
return $db.$shoppingItems.insert($requestBody);
}`
},
"shopping-list-create-query": {
url: "/shopping-list-query",
method: "post",
queryParamTypes: {
addIfInPantry: "boolean",
},
responseShape: "shoppingItem?",
handler: `return $db.$shoppingItems.insert($requestBody);`
},
"shopping-list-delete": {
url: "/shopping-list/:itemId",
method: "delete",
pathParamTypes: {
itemId: "integer",
},
responseShape: "boolean",
handler: `$db.$shoppingItems.native().where("id").equals($pathParams.itemId).delete();`
},
"shopping-list-update": {
url: "/shopping-list/:itemId",
method: "put",
pathParamTypes: {
itemId: "integer",
},
responseShape: "shoppingItem",
handler: `
const item = $db.$shoppingItems.byId($pathParams.itemId);
$db.$shoppingItems.update({...item, ...$requestBody});`
},
"shopping-list-update-slow": {
url: "/shopping-list-slow/:itemId",
method: "put",
pathParamTypes: {
itemId: "integer",
},
responseShape: "shoppingItem",
handler: `{
delay(5000);
if ($requestBody.name.toLowerCase() === "error") {
throw Errors.HttpError(400, { message: "Error!" });
}
const item = $db.$shoppingItems.byId($pathParams.itemId);
$db.$shoppingItems.update({...item, ...$requestBody});
}`
},
"shopping-list-meta": {
url: "/shopping-list-meta",
method: "get",
responseShape: "shoppingItem[]",
handler: `
const shoppingList = $db.$shoppingItems.toArray();
return {
items: shoppingList,
meta: {
totalItems: shoppingList.length,
},
};`
},
"shopping-list-query": {
url: "/shopping-list-query",
method: "get",
responseShape: "shoppingItem[]",
handler: `
let items = $db.$shoppingItems.toArray();
const { inPantry, limit } = $queryParams;
items = inPantry !== undefined ? items.filter(item => item.inPantry): items;
items = limit !== undefined ? items.slice(0, limit) : items;
return items;`
},
"shopping-list-refetch": {
url: "/shopping-list-refetch",
method: "get",
responseShape: "shoppingItem[]",
handler: `
$db.$shoppingItems.insert({
name: "Carrots",
quantity: 100,
unit: "grams",
category: "vegetables",
inPantry: true,
})`
},
"shopping-list-headers": {
url: "/shopping-list-headers",
method: "get",
responseShape: "shoppingItem[]",
handler: `
const token = $requestHeaders['x-api-key'];
if (token) {
console.log("Token #" + token + " accepted!");
return $db.$shoppingItems.toArray();
}
throw Errors.HttpError(400, { message: "No API key provided in header!" });`,
},
"shopping-list-headers-post": {
url: "/shopping-list-headers",
method: "post",
responseShape: "shoppingItem[]",
handler: `
const token = $requestHeaders['x-api-key'];
if (token === "1111") {
console.log("Token #" + token + " accepted!");
return $db.$shoppingItems.insert($requestBody);
}
throw Errors.HttpError(400, { message: "No valid API key provided in header!" });`,
},
"shopping-list-polled": {
url: "/shopping-list-polled",
method: "get",
responseShape: "shoppingItem[]",
handler: `
if ($db.$polledItems.native().count() >= 5) {
$db.$polledItems.native().clear();
}
const groceriesLength = $db.$shoppingItems.native().count();
const randomIdx = Math.floor(Math.random() * (groceriesLength + 1));
const newItem = $db.$shoppingItems.byId(randomIdx);
if (newItem) {
newItem.id = undefined;
$db.$polledItems.insert({
...newItem,
});
}
return $db.$polledItems.toArray();`
},
"shopping-list-pagination": {
url: "/shopping-list-pagination",
method: "get",
responseShape: "shoppingItems[]",
handler: `
const items = $db.$shoppingItems.toArray();
const { size, nextPageParam } = $queryParams;
const _size = parseInt(size);
let startIndex = 0;
let endIndex = items.length < _size ? items.length : _size;
if (nextPageParam !== undefined) {
const startId = parseInt(nextPageParam);
const temp = items.findIndex(item => item.id === startId);
if (temp === -1) {
throw Errors.HttpError(404, "No such item");
}
startIndex = temp + 1;
endIndex = startIndex + _size < items.length ? startIndex + _size : items.length;
}
return items.slice(startIndex, endIndex);`
},
"shopping-item-unconventional": {
url: "/shopping-item-unconventional",
method: "post",
responseShape: "shoppingItem",
handler: "Assertions.getIfPresent($requestBody.id)"
},
"shopping-item": {
url: "/shopping-list/:itemId",
method: "get",
pathParamTypes: {
itemId: "integer"
},
responseShape: "shoppingItem",
handler: "Assertions.getIfPresent($pathParams.itemId)"
},
"shopping-item-recipes": {
url: "/shopping-item-recipes/:itemId",
method: "get",
pathParamTypes: {
itemId: "integer"
},
responseShape: "recipe[]",
handler: `
const item = Assertions.getIfPresent($pathParams.itemId);
const recipes = $db.$recipes.toArray().filter(recipe => recipe.ingredients.find(ingredient => ingredient.name === item.name));
return recipes;`
}
}
};
export default mock;
```
--------------------------------------------------------------------------------
/xmlui/src/components-core/markup-check.ts:
--------------------------------------------------------------------------------
```typescript
import type { ComponentDef, CompoundComponentDef } from "../abstractions/ComponentDefs";
import { parseParameterString } from "./script-runner/ParameterParser";
import { Parser } from "../parsers/scripting/Parser";
import { layoutOptionKeys } from "./descriptorHelper";
import { viewportSizeNames } from "../components/abstractions";
type IsValidFunction<T> = (propKey: string, propValue: T) => string | string[] | undefined | null;
// --- This interface reperesent an object that can handle component metadata.
// --- As the metadata format may change in the futue, this interface is used to
// --- abstract the metadata handling.
export interface MetadataHandler {
componentRegistered: (componentName: string) => boolean;
getComponentProps: (componentName: string) => PropDescriptorHash;
getComponentEvents: (componentName: string) => Record<string, any>;
acceptArbitraryProps: (componentName: string) => boolean;
getComponentValidator: (componentName: string) => ComponentValidator | void;
}
export type PropDescriptor = {
type?: string;
availableValues?: any[];
defaultValue?: any;
isValid?: IsValidFunction<any>;
};
export type PropDescriptorHash = Record<string, PropDescriptor>;
export type ComponentValidator = (instance: ComponentDef, devMode?: boolean) => string | string[] | null;
type VisitResult = { cancel?: boolean; abort?: boolean };
/**
* This function checks the XMLUI markup for potential issues. It retrieves
* a list of errors and warnings.
* @param rootDef Root component definition
* @param components Components referenced in the XMLUI markup
* @param devMode Indicates if the check is performed in development mode
* @returns List of errors and warnings
*/
export function checkXmlUiMarkup(
rootDef: ComponentDef | null,
components: CompoundComponentDef[],
metadataHandler: MetadataHandler,
devMode?: boolean,
): MarkupCheckResult[] {
const errorsCollected: MarkupCheckResult[] = [];
// --- Initialize the check
const continuation: VisitResult = {};
const componentIdsCollected = new Set<string>();
const compoundIdsCollected = new Set<string>();
// --- Visit the root component
if (rootDef) {
visitComponent(rootDef, null, componentDefVisitor, continuation, metadataHandler);
}
// --- Visit the compound components
if (!continuation.abort) {
for (const component of components) {
// --- Rule: Compound component name must be a valid JavaScript identifier
if (!isValidIdentifier(component.name)) {
reportError("M007", "Component", component.name);
}
// --- Rule: Compound component name cannot be 'Component'
if (component.name === "Component") {
reportError("M008", "Component", component.name);
}
// --- Rule: Compound component must not have the name of a registered component
if (metadataHandler.componentRegistered(component.name)) {
reportError("M009", "Component", component.name);
}
// --- Rule: Compound component name must be unique
if (compoundIdsCollected.has(component.name)) {
reportError("M010", "Component", component.name);
} else {
compoundIdsCollected.add(component.name);
}
// --- Reset component ID scope
componentIdsCollected.clear();
// --- Visit the compount component's definition
visitComponent(component.component, null, componentDefVisitor, continuation, metadataHandler);
}
}
// --- Done.
return errorsCollected;
// --- This visitor checks the rules for a particular component
function componentDefVisitor(
def: ComponentDef,
parent: ComponentDef | null | undefined,
before: boolean,
continuation: VisitResult,
): void {
// --- This is the visitor function to check a ComponentDef markup
if (!before) {
// --- Do not visit the component definition after its children have been visited
return;
}
// --- Rule: Component name must be registered
if (!metadataHandler.componentRegistered(def.type)) {
reportError("M001", parent?.type ?? "Root", def.type);
// continuation.cancel = true;
return;
}
// --- Rule: an ID must be a valid JavaScript identifier
if (def.uid) {
if (!isValidIdentifier(def.uid)) {
reportError("M002", def.type, def.type, def.uid);
} else if (componentIdsCollected.has(def.uid)) {
reportError("M003", def.type, def.uid);
} else {
componentIdsCollected.add(def.uid);
}
}
// --- Check all props of the component
const propDescriptors = metadataHandler.getComponentProps(def.type) ?? {};
const currentProps = def.props ?? {};
for (const propName of Object.keys(currentProps)) {
const propDescriptor = propDescriptors[propName];
// --- Rule: The property must be defined in the component descriptor or be a layout option
// --- or the component must accept arbitrary properties
if (propDescriptor) {
// --- The property has a descriptor, so it is allowed.
// --- Rule: The property value must be parseable
const propValue = currentProps[propName];
if (typeof propValue === "string") {
try {
parseParameterString(propValue);
} catch (error) {
reportError("M006", def.type, propName, (error as any).message);
}
}
} else {
// --- The property has no descriptor.
const propParts = propName.split("-");
// --- Check for a layout property
const validProp =
// --- Layout property
(propParts.length === 1 && layoutOptionKeys.includes(propName)) ||
// --- Layout property with viewport size
(propParts.length === 2 &&
layoutOptionKeys.includes(propParts[0]) &&
viewportSizeNames.includes(propParts[1])) ||
// --- Arbitrary property is allowed
metadataHandler.acceptArbitraryProps(def.type);
if (!validProp) {
// --- The component does not accept arbitrary properties and
// --- the property is not a layout option
reportError("M005", def.type, def.type, propName);
}
}
}
// --- Check all events of the component
const eventDescriptors = metadataHandler.getComponentEvents(def.type) ?? {};
const currentEvents = def.events ?? {};
for (const eventName of Object.keys(currentEvents)) {
const eventDescriptor = eventDescriptors[eventName];
// --- Rule: The event must be defined in the component descriptor
if (eventDescriptor) {
// --- The event has a descriptor, so it is allowed.
const eventValue = currentEvents[eventName];
if (typeof eventValue === "string") {
// --- Rule: The event value must be parseable
const parser = new Parser(eventValue);
try {
parser.parseStatements();
if (parser.errors.length > 0) {
reportError("M012", def.type, eventName, parser.errors[0].text);
}
} catch (error) {
reportError("M012", def.type, eventName, (error as any).message);
}
}
} else {
reportError("M011", def.type, def.type, eventName);
}
}
// --- Check the component validator
const componentValidator = metadataHandler.getComponentValidator(def.type);
if (componentValidator) {
const validationErrors = componentValidator(def, devMode);
if (validationErrors) {
if (Array.isArray(validationErrors)) {
for (const error of validationErrors) {
reportError("M013", def.type, error);
}
} else {
reportError("M013", def.type, validationErrors);
}
}
}
}
/**
* Checks if a string is a valid JavaScript identifier.
* @param identifier The string to check.
* @returns True if the string is a valid identifier, false otherwise.
*/
function isValidIdentifier(identifier: string): boolean {
const identifierRegex = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
return identifierRegex.test(identifier);
}
function reportError(code: ErrorCode, name: string, ...args: any) {
_reportError(code, name, false, ...args);
}
function reportWarning(code: ErrorCode, name: string, ...args: any) {
_reportError(code, name, true, ...args);
}
function _reportError(code: ErrorCode, name: string, isWarning: boolean, ...args: any) {
let errorText: string = errorMessages[code] ?? "Unkonwn error";
if (args) {
args.forEach((a: string, idx: number) => (errorText = replace(errorText, `{${idx}}`, args[idx].toString())));
}
errorsCollected.push({ name, code, message: errorText, isWarning, args });
function replace(input: string, placeholder: string, replacement: string): string {
do {
input = input.replace(placeholder, replacement);
} while (input.includes(placeholder));
return input;
}
}
}
// --- This function visits a component, its nested components and children
export function visitComponent(
def: ComponentDef,
parent: ComponentDef | null | undefined,
visitor: (
def: ComponentDef,
parent: ComponentDef | null | undefined,
before: boolean,
continuation: VisitResult,
) => void,
continuation: VisitResult = {},
metadataHandler: MetadataHandler,
): void {
// --- Visit the component (before)
visitor(def, parent, true, continuation);
if (continuation.abort || continuation.cancel) {
// --- Stop the visit
return;
}
// --- Visit the properties with "ComponentDef" value
const propDescriptors = metadataHandler.getComponentProps(def.type) ?? {};
const currentProps = def.props ?? {};
for (const propName of Object.keys(currentProps)) {
const propDescriptor = propDescriptors[propName];
if (!propDescriptor) {
// --- No descriptor for the property, skip it
continue;
}
const propValue = currentProps[propName];
if (propDescriptor.type === "ComponentDef" && propValue.type) {
// --- This property holds a nested component, visit it
visitComponent(propValue, def, visitor, continuation, metadataHandler);
if (continuation.abort || continuation.cancel) {
// --- Stop the visit
return;
}
}
}
// --- Visit events with nested components
const eventDescriptors = metadataHandler.getComponentEvents(def.type) ?? {};
const currentEvents = def.events ?? {};
for (const eventName of Object.keys(currentEvents)) {
const eventDescriptor = eventDescriptors[eventName];
if (!eventDescriptor) {
// --- No descriptor for the events, skip it
continue;
}
const eventValue = currentEvents[eventName];
if (typeof eventValue === "object" && eventValue.type) {
// --- This event holds a nested component, visit it
visitComponent(eventValue, def, visitor, continuation, metadataHandler);
if (continuation.abort) {
// --- Stop visiting this component
return;
}
if (continuation.cancel) {
// --- Skip the remaining items
break;
}
}
}
// --- Visit the component children
if (def.children) {
for (const child of def.children) {
visitComponent(child, def, visitor, continuation, metadataHandler);
if (continuation.abort) {
// --- Stop visiting this component
return;
}
if (continuation.cancel) {
// --- Skip the remaining items
break;
}
}
}
// --- Visit the component (after)
visitor(def, undefined, false, continuation);
}
export type MarkupCheckResult = {
name: string;
code: ErrorCode;
message: string;
isWarning?: boolean;
args?: Array<any>
};
// --- Available error codes
type ErrorCode =
| "M001"
| "M002"
| "M003"
| "M004"
| "M005"
| "M006"
| "M007"
| "M008"
| "M009"
| "M010"
| "M011"
| "M012"
| "M013";
// --- Error message type description
type ErrorText = Record<string, string>;
// --- The error messages of error codes
const errorMessages: ErrorText = {
M001: "The component '{0}' is not registered",
M002: "The '{0}' element has an invalid id: '{1}'",
M003: "Invalid component identifier: '{0}'",
M004: "Duplicated component identifier: '{0}'",
M005: "The '{0}' element has an invalid property: '{1}'",
M006: "Parsing property value of '{0}' failed: {1}",
M007: "The name of a reusable component is invalid: '{0}'",
M008: "The name of a reusable component must not be '{0}', as it is a reserved name",
M009: "A reusable component cannot have the name of a registered component: '{0}'",
M010: "Duplicated reusable component name: '{0}'",
M011: "The '{0}' element has an invalid event: '{1}'",
M012: "Parsing event value of '{0}' failed: {1}",
M013: "Component validation failed: '{0}'",
};
```
--------------------------------------------------------------------------------
/xmlui/src/components/Toggle/Toggle.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);
}
// Variables for Checkbox - default variant
$borderRadius-Checkbox--default: createThemeVar("Input:borderRadius-Checkbox--default");
$borderColor-Checkbox--default: createThemeVar("Input:borderColor-Checkbox--default");
$backgroundColor-Checkbox--default: createThemeVar("Input:backgroundColor-Checkbox--default");
$outlineWidth-Checkbox--default--focus: createThemeVar("Input:outlineWidth-Checkbox--default--focus");
$outlineColor-Checkbox--default--focus: createThemeVar("Input:outlineColor-Checkbox--default--focus");
$outlineStyle-Checkbox--default--focus: createThemeVar("Input:outlineStyle-Checkbox--default--focus");
$outlineOffset-Checkbox--default--focus: createThemeVar("Input:outlineOffset-Checkbox--default--focus");
// Variables for Checkbox - error variant
$borderRadius-Checkbox--error: createThemeVar("Input:borderRadius-Checkbox--error");
$borderColor-Checkbox--error: createThemeVar("Input:borderColor-Checkbox--error");
$backgroundColor-Checkbox--error: createThemeVar("Input:backgroundColor-Checkbox--error");
$outlineWidth-Checkbox--error--focus: createThemeVar("Input:outlineWidth-Checkbox--error--focus");
$outlineColor-Checkbox--error--focus: createThemeVar("Input:outlineColor-Checkbox--error--focus");
$outlineStyle-Checkbox--error--focus: createThemeVar("Input:outlineStyle-Checkbox--error--focus");
$outlineOffset-Checkbox--error--focus: createThemeVar("Input:outlineOffset-Checkbox--error--focus");
// Variables for Checkbox - warning variant
$borderRadius-Checkbox--warning: createThemeVar("Input:borderRadius-Checkbox--warning");
$borderColor-Checkbox--warning: createThemeVar("Input:borderColor-Checkbox--warning");
$backgroundColor-Checkbox--warning: createThemeVar("Input:backgroundColor-Checkbox--warning");
$outlineWidth-Checkbox--warning--focus: createThemeVar("Input:outlineWidth-Checkbox--warning--focus");
$outlineColor-Checkbox--warning--focus: createThemeVar("Input:outlineColor-Checkbox--warning--focus");
$outlineStyle-Checkbox--warning--focus: createThemeVar("Input:outlineStyle-Checkbox--warning--focus");
$outlineOffset-Checkbox--warning--focus: createThemeVar("Input:outlineOffset-Checkbox--warning--focus");
// Variables for Checkbox - success variant
$borderRadius-Checkbox--success: createThemeVar("Input:borderRadius-Checkbox--success");
$borderColor-Checkbox--success: createThemeVar("Input:borderColor-Checkbox--success");
$backgroundColor-Checkbox--success: createThemeVar("Input:backgroundColor-Checkbox--success");
$outlineWidth-Checkbox--success--focus: createThemeVar("Input:outlineWidth-Checkbox--success--focus");
$outlineColor-Checkbox--success--focus: createThemeVar("Input:outlineColor-Checkbox--success--focus");
$outlineStyle-Checkbox--success--focus: createThemeVar("Input:outlineStyle-Checkbox--success--focus");
$outlineOffset-Checkbox--success--focus: createThemeVar("Input:outlineOffset-Checkbox--success--focus");
// Variables for Checkbox - hover and disabled states
$borderColor-Checkbox--default--hover: createThemeVar("Input:borderColor-Checkbox--default--hover");
$backgroundColor-Checkbox--disabled: createThemeVar("Input:backgroundColor-Checkbox--disabled");
$borderColor-Checkbox--disabled: createThemeVar("Input:borderColor-Checkbox--disabled");
// Variables for Checkbox - checked states
$borderColor-checked-Checkbox: createThemeVar("Input:borderColor-checked-Checkbox");
$backgroundColor-checked-Checkbox: createThemeVar("Input:backgroundColor-checked-Checkbox");
$borderColor-checked-Checkbox--error: createThemeVar("Input:borderColor-checked-Checkbox--error");
$backgroundColor-checked-Checkbox--error: createThemeVar("Input:backgroundColor-checked-Checkbox--error");
$borderColor-checked-Checkbox--warning: createThemeVar("Input:borderColor-checked-Checkbox--warning");
$backgroundColor-checked-Checkbox--warning: createThemeVar("Input:backgroundColor-checked-Checkbox--warning");
$borderColor-checked-Checkbox--success: createThemeVar("Input:borderColor-checked-Checkbox--success");
$backgroundColor-checked-Checkbox--success: createThemeVar("Input:backgroundColor-checked-Checkbox--success");
// Variables for Checkbox indicator
$backgroundColor-indicator-Checkbox: createThemeVar("backgroundColor-indicator-Checkbox");
// Variables for Switch - hover and disabled states
$borderColor-Switch--default--hover: createThemeVar("Input:borderColor-Switch--default--hover");
$backgroundColor-Switch--disabled: createThemeVar("Input:backgroundColor-Switch--disabled");
$borderColor-Switch--disabled: createThemeVar("Input:borderColor-Switch--disabled");
// Variables for Switch - checked states
$borderColor-checked-Switch: createThemeVar("Input:borderColor-checked-Switch");
$backgroundColor-checked-Switch: createThemeVar("Input:backgroundColor-checked-Switch");
$borderColor-checked-Switch--error: createThemeVar("Input:borderColor-checked-Switch--error");
$backgroundColor-checked-Switch--error: createThemeVar("Input:backgroundColor-checked-Switch--error");
$borderColor-checked-Switch--warning: createThemeVar("Input:borderColor-checked-Switch--warning");
$backgroundColor-checked-Switch--warning: createThemeVar("Input:backgroundColor-checked-Switch--warning");
$borderColor-checked-Switch--success: createThemeVar("Input:borderColor-checked-Switch--success");
$backgroundColor-checked-Switch--success: createThemeVar("Input:backgroundColor-checked-Switch--success");
// Variables for Switch
$backgroundColor-Switch: createThemeVar("Input:backgroundColor-Switch");
$borderColor-Switch: createThemeVar("Input:borderColor-Switch");
$backgroundColor-indicator-Switch: createThemeVar("backgroundColor-indicator-Switch");
$backgroundColor-checked-Switch: createThemeVar("backgroundColor-checked-Switch");
$backgroundColor-indicator-checked-Switch: createThemeVar("backgroundColor-indicator-checked-Switch");
$backgroundColor-Switch-indicator--disabled: createThemeVar("backgroundColor-Switch-indicator--disabled");
$outlineWidth-Switch--focus: createThemeVar("Input:outlineWidth-Switch--focus");
$outlineColor-Switch--focus: createThemeVar("Input:outlineColor-Switch--focus");
$outlineStyle-Switch--focus: createThemeVar("Input:outlineStyle-Switch--focus");
$outlineOffset-Switch--focus: createThemeVar("Input:outlineOffset-Switch--focus");
// Variables for Switch - validation variants
$borderColor-Switch--error: createThemeVar("Input:borderColor-Switch--error");
$borderColor-Switch--warning: createThemeVar("Input:borderColor-Switch--warning");
$borderColor-Switch--success: createThemeVar("Input:borderColor-Switch--success");
// --- CSS properties of a particular Checkbox variant
@mixin checkboxVariant($variantName) {
border-radius: createThemeVar("Input:borderRadius-Checkbox--#{$variantName}");
border-color: createThemeVar("Input:borderColor-Checkbox--#{$variantName}");
background-color: createThemeVar("Input:backgroundColor-Checkbox--#{$variantName}");
&:focus-visible {
outline-width: createThemeVar("Input:outlineWidth-Checkbox--#{$variantName}--focus");
outline-color: createThemeVar("Input:outlineColor-Checkbox--#{$variantName}--focus");
outline-style: createThemeVar("Input:outlineStyle-Checkbox--#{$variantName}--focus");
outline-offset: createThemeVar("Input:outlineOffset-Checkbox--#{$variantName}--focus");
}
}
@mixin hoverAndDisabledState($componentName) {
&:not([readonly]):not(:disabled):hover {
border-color: createThemeVar("Input:borderColor-#{$componentName}--default--hover");
}
&:disabled {
cursor: not-allowed;
background-color: createThemeVar("Input:backgroundColor-#{$componentName}--disabled");
border-color: createThemeVar("Input:borderColor-#{$componentName}--disabled");
}
}
@mixin checkedState($componentName) {
&:checked {
border-color: createThemeVar("Input:borderColor-checked-#{$componentName}");
background-color: createThemeVar("Input:backgroundColor-checked-#{$componentName}");
}
&:checked:disabled {
background-color: createThemeVar("Input:backgroundColor-#{$componentName}--disabled");
border-color: createThemeVar("Input:borderColor-#{$componentName}--disabled");
}
&:checked.error {
border-color: createThemeVar("Input:borderColor-checked-#{$componentName}--error");
background-color: createThemeVar("Input:backgroundColor-checked-#{$componentName}--error");
}
&:checked.warning {
border-color: createThemeVar("Input:borderColor-checked-#{$componentName}--warning");
background-color: createThemeVar("Input:backgroundColor-checked-#{$componentName}--warning");
}
&:checked.valid {
border-color: createThemeVar("Input:borderColor-checked-#{$componentName}--success");
background-color: createThemeVar("Input:backgroundColor-checked-#{$componentName}--success");
}
}
@layer components {
.resetAppearance {
/* Add if not using autoprefixer */
-webkit-appearance: none;
appearance: none;
/* Not removed via appearance */
margin: 0;
}
.label {
width: 100%;
}
.inputContainer {
z-index: -1;
position: relative;
opacity: 0;
width: 0;
height: 0;
}
// --------------- Checkbox ---------------
.checkbox {
display: grid;
place-content: center;
min-width: 1em;
min-height: 1em;
width: 1em;
height: 1em;
border: 2px solid transparent;
&:not([readonly]) {
cursor: pointer;
}
@include checkboxVariant("default");
@include hoverAndDisabledState("Checkbox");
.forceHover {
border-color: $borderColor-Checkbox--default--hover;
// Don't override background-color - let the existing background rules apply
}
&.error {
@include checkboxVariant("error");
}
&.warning {
@include checkboxVariant("warning");
}
&.valid {
@include checkboxVariant("success");
}
&::before {
content: "";
width: 0.5em;
height: 0.5em;
transform: scale(0);
transition: 0.1s transform ease-out;
box-shadow: inset 1em 1em $backgroundColor-indicator-Checkbox;
transform-origin: center;
clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
}
&:checked::before {
transform: scale(1);
}
@include checkedState("Checkbox");
&:indeterminate {
background-color: $backgroundColor-checked-Checkbox;
border-color: $borderColor-checked-Checkbox;
&[readonly] {
pointer-events: none;
}
}
&:indeterminate:disabled {
background-color: $backgroundColor-Checkbox--disabled;
border-color: $borderColor-Checkbox--disabled;
}
&:indeterminate::before {
clip-path: circle(30% at 50% 50%);
transform: scale(1);
}
}
// --------------- Switch ---------------
.switch {
--thumb-size: 1rem;
--thumb-position: 0%;
--track-size: calc(var(--thumb-size) * 3);
--padding-size: 4px;
&:not([readonly]) {
cursor: pointer;
}
flex-shrink: 0;
display: grid;
align-items: center;
grid: [track] 1fr / [track] 1fr;
background-color: $backgroundColor-Switch;
width: var(--track-size);
min-height: var(--thumb-size);
padding: var(--padding-size);
border: 1px solid $borderColor-Switch;
border-radius: 1rem;
&::before {
content: "";
grid-area: track;
height: var(--thumb-size);
width: var(--thumb-size);
background: $backgroundColor-indicator-Switch;
border-radius: 50%;
transform: translateX(var(--thumb-position));
transition: 0.3s transform;
}
&:checked {
background: $backgroundColor-checked-Switch;
&::before {
background: $backgroundColor-indicator-checked-Switch;
--thumb-position: calc(
var(--track-size) - var(--thumb-size) - 2 * var(--padding-size) - 2px
);
}
}
@include hoverAndDisabledState("Switch");
@include checkedState("Switch");
.forceHover {
border-color: $borderColor-Switch--default--hover;
// Don't override background-color - let the existing background rules apply
}
&:focus-visible {
outline-width: $outlineWidth-Switch--focus;
outline-color: $outlineColor-Switch--focus;
outline-style: $outlineStyle-Switch--focus;
outline-offset: $outlineOffset-Switch--focus;
}
&:disabled {
&::before {
background-color: $backgroundColor-Switch-indicator--disabled;
}
}
&.error {
border-color: $borderColor-Switch--error;
}
&.warning {
border-color: $borderColor-Switch--warning;
}
&.valid {
border-color: $borderColor-Switch--success;
}
}
}
// --- We export the theme variables to add them to the component renderer
:export {
themeVars: t.json-stringify($themeVars);
}
```
--------------------------------------------------------------------------------
/xmlui/src/components/AppState/AppState.spec.ts:
--------------------------------------------------------------------------------
```typescript
import { test, expect } from "../../testing/fixtures";
// =============================================================================
// BASIC FUNCTIONALITY TESTS
// =============================================================================
test("initializes with default props", async ({ initTestBed, page }) => {
await initTestBed(`
<Fragment>
<AppState id="appState" />
<Text testId="stateValue">|{JSON.stringify(appState.value)}|</Text>
</Fragment>
`);
// AppState should initialize with default bucket and display the correct value
await expect(page.getByTestId("stateValue")).toBeVisible();
await expect(page.getByTestId("stateValue")).toHaveText("||");
});
test("initializes with initial state value", async ({ initTestBed, page }) => {
await initTestBed(`
<Fragment>
<AppState id="appState" initialValue="{{ mode: true }}"/>
<Text testId="stateValue">|{appState.value.mode}|</Text>
</Fragment>
`);
// AppState should initialize with default bucket and display the correct value
await expect(page.getByTestId("stateValue")).toBeVisible();
await expect(page.getByTestId("stateValue")).toHaveText("|true|");
});
test("initializes with multiple initial state value", async ({ initTestBed, page }) => {
await initTestBed(`
<Fragment>
<AppState id="appState" initialValue="{{ mode: true }}"/>
<AppState id="appState2" initialValue="{{ otherMode: 123 }}"/>
<Text testId="stateValue">|{appState.value.mode}|{appState2.value.otherMode}|</Text>
</Fragment>
`);
// AppState should initialize with default bucket and display the correct value
await expect(page.getByTestId("stateValue")).toBeVisible();
await expect(page.getByTestId("stateValue")).toHaveText("|true|123|");
});
test("initializes with provided bucket name and no initial value", async ({
initTestBed,
page,
}) => {
await initTestBed(`
<Fragment>
<AppState id="appState" bucket="settings" />
<Text testId="stateValue">|{JSON.stringify(appState.value)}|</Text>
</Fragment>
`);
// AppState should initialize with default bucket and display the correct value
await expect(page.getByTestId("stateValue")).toBeVisible();
await expect(page.getByTestId("stateValue")).toHaveText("||");
});
test("initializes with bucket name and initial state value", async ({ initTestBed, page }) => {
await initTestBed(`
<Fragment>
<AppState id="appState" bucket="settings" initialValue="{{ mode: true }}"/>
<Text testId="stateValue">|{appState.value.mode}|</Text>
</Fragment>
`);
// AppState should initialize with default bucket and display the correct value
await expect(page.getByTestId("stateValue")).toBeVisible();
await expect(page.getByTestId("stateValue")).toHaveText("|true|");
});
test("initializes with bucket name and multiple initial state value", async ({
initTestBed,
page,
}) => {
await initTestBed(`
<Fragment>
<AppState id="appState" bucket="settings" initialValue="{{ mode: true }}"/>
<AppState id="appState2" bucket="settings" initialValue="{{ otherMode: 123 }}"/>
<Text testId="stateValue">|{appState.value.mode}|{appState.value.otherMode}|</Text>
</Fragment>
`);
// AppState should initialize with default bucket and display the correct value
await expect(page.getByTestId("stateValue")).toBeVisible();
await expect(page.getByTestId("stateValue")).toHaveText("|true|123|");
});
test("updates state using the update API", async ({ initTestBed, page }) => {
await initTestBed(`
<Fragment>
<AppState id="appState" initialValue="{{ counter: 0 }}" />
<Button testId="updateBtn" onClick="appState.update({ counter: appState.value.counter + 1 })">
Increment
</Button>
<Text testId="stateValue">{JSON.stringify(appState.value)}</Text>
</Fragment>
`);
// Check initial state
await expect(page.getByTestId("stateValue")).toHaveText('{"counter":0}');
// Update state by clicking button
await page.getByTestId("updateBtn").click();
// Check updated state
await expect(page.getByTestId("stateValue")).toHaveText('{"counter":1}');
});
test("updates state using the update API (using backet name)", async ({ initTestBed, page }) => {
await initTestBed(`
<Fragment>
<AppState id="appState" bucket="settings" initialValue="{{ counter: 0 }}" />
<Button testId="updateBtn" onClick="appState.update({ counter: appState.value.counter + 1 })">
Increment
</Button>
<Text testId="stateValue">{JSON.stringify(appState.value)}</Text>
</Fragment>
`);
// Check initial state
await expect(page.getByTestId("stateValue")).toHaveText('{"counter":0}');
// Update state by clicking button
await page.getByTestId("updateBtn").click();
// Check updated state
await expect(page.getByTestId("stateValue")).toHaveText('{"counter":1}');
});
// =============================================================================
// STATE SHARING TESTS
// =============================================================================
test("shares state between multiple instances with the default bucket", async ({
initTestBed,
page,
}) => {
await initTestBed(`
<Fragment>
<AppState id="appState1" initialValue="{{ counter: 0 }}" />
<AppState id="appState2" />
<Button testId="updateBtn" onClick="appState1.update({ counter: appState1.value.counter + 1 })">
Increment
</Button>
<Text testId="stateValue1">{JSON.stringify(appState1.value)}</Text>
<Text testId="stateValue2">{JSON.stringify(appState2.value)}</Text>
</Fragment>
`);
// Check initial state in both instances
await expect(page.getByTestId("stateValue1")).toHaveText('{"counter":0}');
await expect(page.getByTestId("stateValue2")).toHaveText('{"counter":0}');
// Update state through first instance
await page.getByTestId("updateBtn").click();
// Both instances should reflect the change
await expect(page.getByTestId("stateValue1")).toHaveText('{"counter":1}');
await expect(page.getByTestId("stateValue2")).toHaveText('{"counter":1}');
});
test("shares state between multiple instances with a specific bucket", async ({
initTestBed,
page,
}) => {
await initTestBed(`
<Fragment>
<AppState id="appState1" initialValue="{{ counter: 0 }}" bucket="bucket1" />
<AppState id="appState2" bucket="bucket1" />
<Button testId="updateBtn" onClick="appState1.update({ counter: appState1.value.counter + 1 })">
Increment
</Button>
<Text testId="stateValue1">{JSON.stringify(appState1.value)}</Text>
<Text testId="stateValue2">{JSON.stringify(appState2.value)}</Text>
</Fragment>
`);
// Check initial state in both instances
await expect(page.getByTestId("stateValue1")).toHaveText('{"counter":0}');
await expect(page.getByTestId("stateValue2")).toHaveText('{"counter":0}');
// Update state through first instance
await page.getByTestId("updateBtn").click();
// Both instances should reflect the change
await expect(page.getByTestId("stateValue1")).toHaveText('{"counter":1}');
await expect(page.getByTestId("stateValue2")).toHaveText('{"counter":1}');
});
test("maintains separate states for different buckets", async ({ initTestBed, page }) => {
await initTestBed(`
<Fragment>
<AppState id="appState1" initialValue="{{ counter: 0 }}" bucket="bucket1" />
<AppState id="appState2" initialValue="{{ counter: 0 }}" bucket="bucket2" />
<Button testId="updateBtn" onClick="appState1.update({ counter: appState1.value.counter + 1 })">
Increment
</Button>
<Text testId="stateValue1">{JSON.stringify(appState1.value)}</Text>
<Text testId="stateValue2">{JSON.stringify(appState2.value)}</Text>
</Fragment>
`);
// Check initial state in both instances
await expect(page.getByTestId("stateValue1")).toHaveText('{"counter":0}');
await expect(page.getByTestId("stateValue2")).toHaveText('{"counter":0}');
// Update state through first instance
await page.getByTestId("updateBtn").click();
// Only the first instance should reflect the change
await expect(page.getByTestId("stateValue1")).toHaveText('{"counter":1}');
await expect(page.getByTestId("stateValue2")).toHaveText('{"counter":0}');
});
// =============================================================================
// EDGE CASE TESTS
// =============================================================================
test("handles undefined initialValue gracefully", async ({ initTestBed, page }) => {
await initTestBed(`
<Fragment>
<AppState ref="appState" initialValue="{undefined}" />
<Text testId="stateValue">|{JSON.stringify(appState.value)}|</Text>
</Fragment>
`);
// Should not throw error with undefined initialValue
await expect(page.getByTestId("stateValue")).toBeVisible();
await expect(page.getByTestId("stateValue")).toHaveText("||");
});
test("handles complex nested state updates correctly", async ({ initTestBed, page }) => {
await initTestBed(`
<Fragment>
<AppState
id="appState"
initialValue="{{ user: { name: 'John', profile: { age: 30, roles: ['admin'] } } }}"
/>
<Button testId="updateBtn"
onClick="appState.update({
user: { ...appState.value.user,
profile: { ...appState.value.user.profile, age: 31 } }
})">
Update Age
</Button>
<Text testId="stateValue">{JSON.stringify(appState.value)}</Text>
</Fragment>
`);
// Check initial state
await expect(page.getByTestId("stateValue")).toContainText('"age":30');
// Update nested property
await page.getByTestId("updateBtn").click();
// Check updated nested property
await expect(page.getByTestId("stateValue")).toContainText('"age":31');
// Check other properties remain unchanged
await expect(page.getByTestId("stateValue")).toContainText('"name":"John"');
await expect(page.getByTestId("stateValue")).toContainText('"roles":["admin"]');
});
// =============================================================================
// PERFORMANCE TESTS
// =============================================================================
test("handles multiple rapid state updates efficiently", async ({ initTestBed, page }) => {
await initTestBed(`
<Fragment var.clickCount="{0}">
<AppState id="appState" initialValue="{{ counter: 0 }}" />
<Button
testId="updateBtn"
onClick="appState.update({ counter: appState.value.counter + 1 }); clickCount = clickCount + 1;">
Increment
</Button>
<Text testId="stateValue">{JSON.stringify(appState.value)}</Text>
<Text testId="clickCount">{clickCount}</Text>
</Fragment>
`);
// Perform multiple rapid clicks
for (let i = 0; i < 5; i++) {
await page.getByTestId("updateBtn").click();
}
// Verify click count
await expect(page.getByTestId("clickCount")).toHaveText("5");
// Verify state was updated correctly
await expect(page.getByTestId("stateValue")).toContainText('{"counter":5}');
});
// =============================================================================
// INTEGRATION TESTS
// =============================================================================
test("integrates with other components that consume app state", async ({ initTestBed, page }) => {
// TODO: review these Copilot-created tests
await initTestBed(`
<Fragment>
<AppState id="appState" initialValue="{{ theme: 'light', fontSize: 14 }}" />
<Fragment var.currentTheme="initial">
<Button testId="themeBtn" onClick="currentTheme = appState.value.theme">
Get Theme
</Button>
<Text testId="themeValue">{currentTheme}</Text>
</Fragment>
</Fragment>
`);
// Get the theme value by clicking button
await page.getByTestId("themeBtn").click();
// Check that the value was correctly retrieved from AppState
await expect(page.getByTestId("themeValue")).toHaveText("light");
});
test("works correctly when wrapped in conditional rendering", async ({ initTestBed, page }) => {
// TODO: review these Copilot-created tests
await initTestBed(`
<Fragment var.showState="{false}">
<Fragment when="{showState}">
<Text>AppState is visible</Text>
<AppState id="appState" initialValue="{{ visible: true }}" />
</Fragment>
<Button testId="toggleBtn" onClick="showState = !showState">Toggle AppState</Button>
<Text testId="visibilityStatus">{appState.value.visible ? 'Visible' : 'Hidden'}</Text>
</Fragment>
`);
// Initially the AppState component should be rendered
await expect(page.getByTestId("visibilityStatus")).toHaveText("Hidden");
// Toggle the component's visibility
await page.getByTestId("toggleBtn").click();
await expect(page.getByTestId("visibilityStatus")).toHaveText("Visible");
// Toggle it back: The AppState component is hidden, but the state is still remain
// there. It should work this way.
await page.getByTestId("toggleBtn").click();
await expect(page.getByTestId("visibilityStatus")).toHaveText("Visible");
});
```
--------------------------------------------------------------------------------
/xmlui/src/components-core/script-runner/ScriptingSourceTree.ts:
--------------------------------------------------------------------------------
```typescript
import type { GenericToken } from "../../parsers/common/GenericToken";
import type { TokenType } from "../../parsers/scripting/TokenType";
import type { ScriptParserErrorMessage } from "../../abstractions/scripting/ScriptParserError";
// --- All binding expression tree node types
type ScriptNode = Statement | Expression;
type ScriptingToken = GenericToken<TokenType>;
// The root type of all source tree nodes
export interface ScripNodeBase {
// Node type discriminator
type: ScriptNode["type"];
// The unique id of the node
nodeId: number;
// The start token of the node
startToken?: ScriptingToken;
// The end token of the node
endToken?: ScriptingToken;
}
// Import the actual implementation constants from outside the abstractions folder
import * as NodeTypes from "../../parsers/scripting/ScriptingNodeTypes";
// Re-export the constants so they can be used both as types and values
export const {
// Statement node type values
T_BLOCK_STATEMENT,
T_EMPTY_STATEMENT,
T_EXPRESSION_STATEMENT,
T_ARROW_EXPRESSION_STATEMENT,
T_LET_STATEMENT,
T_CONST_STATEMENT,
T_VAR_STATEMENT,
T_IF_STATEMENT,
T_RETURN_STATEMENT,
T_BREAK_STATEMENT,
T_CONTINUE_STATEMENT,
T_WHILE_STATEMENT,
T_DO_WHILE_STATEMENT,
T_FOR_STATEMENT,
T_FOR_IN_STATEMENT,
T_FOR_OF_STATEMENT,
T_THROW_STATEMENT,
T_TRY_STATEMENT,
T_SWITCH_STATEMENT,
T_FUNCTION_DECLARATION,
// Expression node type values
T_UNARY_EXPRESSION,
T_BINARY_EXPRESSION,
T_SEQUENCE_EXPRESSION,
T_CONDITIONAL_EXPRESSION,
T_FUNCTION_INVOCATION_EXPRESSION,
T_MEMBER_ACCESS_EXPRESSION,
T_CALCULATED_MEMBER_ACCESS_EXPRESSION,
T_IDENTIFIER,
T_TEMPLATE_LITERAL_EXPRESSION,
T_LITERAL,
T_ARRAY_LITERAL,
T_OBJECT_LITERAL,
T_SPREAD_EXPRESSION,
T_ASSIGNMENT_EXPRESSION,
T_NO_ARG_EXPRESSION,
T_ARROW_EXPRESSION,
T_PREFIX_OP_EXPRESSION,
T_POSTFIX_OP_EXPRESSION,
T_REACTIVE_VAR_DECLARATION,
// Other node type values
T_VAR_DECLARATION,
T_DESTRUCTURE,
T_ARRAY_DESTRUCTURE,
T_OBJECT_DESTRUCTURE,
T_SWITCH_CASE
} = NodeTypes;
// --- Statement node types
type BLOCK_STATEMENT = typeof T_BLOCK_STATEMENT;
type EMPTY_STATEMENT = typeof T_EMPTY_STATEMENT;
type EXPRESSION_STATEMENT = typeof T_EXPRESSION_STATEMENT;
type ARROW_EXPRESSION_STATEMENT = typeof T_ARROW_EXPRESSION_STATEMENT;
type LET_STATEMENT = typeof T_LET_STATEMENT;
type CONST_STATEMENT = typeof T_CONST_STATEMENT;
type VAR_STATEMENT = typeof T_VAR_STATEMENT;
type IF_STATEMENT = typeof T_IF_STATEMENT;
type RETURN_STATEMENT = typeof T_RETURN_STATEMENT;
type BREAK_STATEMENT = typeof T_BREAK_STATEMENT;
type CONTINUE_STATEMENT = typeof T_CONTINUE_STATEMENT;
type WHILE_STATEMENT = typeof T_WHILE_STATEMENT;
type DO_WHILE_STATEMENT = typeof T_DO_WHILE_STATEMENT;
type FOR_STATEMENT = typeof T_FOR_STATEMENT;
type FOR_IN_STATEMENT = typeof T_FOR_IN_STATEMENT;
type FOR_OF_STATEMENT = typeof T_FOR_OF_STATEMENT;
type THROW_STATEMENT = typeof T_THROW_STATEMENT;
type TRY_STATEMENT = typeof T_TRY_STATEMENT;
type SWITCH_STATEMENT = typeof T_SWITCH_STATEMENT;
type FUNCTION_DECLARATION = typeof T_FUNCTION_DECLARATION;
// --- Expression node types
type UNARY_EXPRESSION = typeof T_UNARY_EXPRESSION;
type BINARY_EXPRESSION = typeof T_BINARY_EXPRESSION;
type SEQUENCE_EXPRESSION = typeof T_SEQUENCE_EXPRESSION;
type CONDITIONAL_EXPRESSION = typeof T_CONDITIONAL_EXPRESSION;
type FUNCTION_INVOCATION_EXPRESSION = typeof T_FUNCTION_INVOCATION_EXPRESSION;
type MEMBER_ACCESS_EXPRESSION = typeof T_MEMBER_ACCESS_EXPRESSION;
type CALCULATED_MEMBER_ACCESS_EXPRESSION = typeof T_CALCULATED_MEMBER_ACCESS_EXPRESSION;
type IDENTIFIER = typeof T_IDENTIFIER;
type TEMPLATE_LITERAL_EXPRESSION = typeof T_TEMPLATE_LITERAL_EXPRESSION;
type LITERAL = typeof T_LITERAL;
type ARRAY_LITERAL = typeof T_ARRAY_LITERAL;
type OBJECT_LITERAL = typeof T_OBJECT_LITERAL;
type SPREAD_EXPRESSION = typeof T_SPREAD_EXPRESSION;
type ASSIGNMENT_EXPRESSION = typeof T_ASSIGNMENT_EXPRESSION;
type NO_ARG_EXPRESSION = typeof T_NO_ARG_EXPRESSION;
type ARROW_EXPRESSION = typeof T_ARROW_EXPRESSION;
type PREFIX_OP_EXPRESSION = typeof T_PREFIX_OP_EXPRESSION;
type POSTFIX_OP_EXPRESSION = typeof T_POSTFIX_OP_EXPRESSION;
type REACTIVE_VAR_DECLARATION = typeof T_REACTIVE_VAR_DECLARATION;
// --- Other node types
type VAR_DECLARATION = typeof T_VAR_DECLARATION;
type DESTRUCTURE = typeof T_DESTRUCTURE;
type ARRAY_DESTRUCTURE = typeof T_ARRAY_DESTRUCTURE;
type OBJECT_DESTRUCTURE = typeof T_OBJECT_DESTRUCTURE;
type SWITCH_CASE = typeof T_SWITCH_CASE;
// =====================================================================================================================
// Statements
export type Statement =
| BlockStatement
| EmptyStatement
| ExpressionStatement
| ArrowExpressionStatement
| LetStatement
| ConstStatement
| VarStatement
| IfStatement
| ReturnStatement
| BreakStatement
| ContinueStatement
| WhileStatement
| DoWhileStatement
| ForStatement
| ForInStatement
| ForOfStatement
| ThrowStatement
| TryStatement
| SwitchStatement
| FunctionDeclaration;
export type LoopStatement = WhileStatement | DoWhileStatement;
export interface EmptyStatement extends ScripNodeBase {
type: EMPTY_STATEMENT;
}
export interface ExpressionStatement extends ScripNodeBase {
type: EXPRESSION_STATEMENT;
expr: Expression;
}
export interface ArrowExpressionStatement extends ScripNodeBase {
type: ARROW_EXPRESSION_STATEMENT;
expr: ArrowExpression;
}
export interface VarDeclaration extends ExpressionBase {
type: VAR_DECLARATION;
id?: string;
aDestr?: ArrayDestructure[];
oDestr?: ObjectDestructure[];
expr?: Expression;
}
export interface DestructureBase extends ExpressionBase {
id?: string;
aDestr?: ArrayDestructure[];
oDestr?: ObjectDestructure[];
}
export interface Destructure extends DestructureBase {
type: DESTRUCTURE;
aDestr?: ArrayDestructure[];
oDestr?: ObjectDestructure[];
}
export interface ArrayDestructure extends DestructureBase {
type: ARRAY_DESTRUCTURE;
}
export interface ObjectDestructure extends DestructureBase {
type: OBJECT_DESTRUCTURE;
id: string;
alias?: string;
}
export interface LetStatement extends ScripNodeBase {
type: LET_STATEMENT;
decls: VarDeclaration[];
}
export interface ConstStatement extends ScripNodeBase {
type: CONST_STATEMENT;
decls: VarDeclaration[];
}
export interface VarStatement extends ScripNodeBase {
type: VAR_STATEMENT;
decls: ReactiveVarDeclaration[];
}
export interface ReactiveVarDeclaration extends ExpressionBase {
type: REACTIVE_VAR_DECLARATION;
id: Identifier;
expr: Expression;
}
export interface BlockStatement extends ScripNodeBase {
type: BLOCK_STATEMENT;
stmts: Statement[];
}
export interface IfStatement extends ScripNodeBase {
type: IF_STATEMENT;
cond: Expression;
thenB: Statement;
elseB?: Statement;
}
export interface ReturnStatement extends ScripNodeBase {
type: RETURN_STATEMENT;
expr?: Expression;
}
export interface WhileStatement extends ScripNodeBase {
type: WHILE_STATEMENT;
cond: Expression;
body: Statement;
}
export interface DoWhileStatement extends ScripNodeBase {
type: DO_WHILE_STATEMENT;
cond: Expression;
body: Statement;
}
export interface BreakStatement extends ScripNodeBase {
type: BREAK_STATEMENT;
}
export interface ContinueStatement extends ScripNodeBase {
type: CONTINUE_STATEMENT;
}
export interface ThrowStatement extends ScripNodeBase {
type: THROW_STATEMENT;
expr: Expression;
}
export interface TryStatement extends ScripNodeBase {
type: TRY_STATEMENT;
tryB: BlockStatement;
catchB?: BlockStatement;
catchV?: Identifier;
finallyB?: BlockStatement;
}
export interface ForStatement extends ScripNodeBase {
type: FOR_STATEMENT;
init?: ExpressionStatement | LetStatement;
cond?: Expression;
upd?: Expression;
body: Statement;
}
export type ForVarBinding = "let" | "const" | "none";
export interface ForInStatement extends ScripNodeBase {
type: FOR_IN_STATEMENT;
varB: ForVarBinding;
id: Identifier;
expr: Expression;
body: Statement;
}
export interface ForOfStatement extends ScripNodeBase {
type: FOR_OF_STATEMENT;
varB: ForVarBinding;
id: Identifier;
expr: Expression;
body: Statement;
}
export interface SwitchStatement extends ScripNodeBase {
type: SWITCH_STATEMENT;
expr: Expression;
cases: SwitchCase[];
}
export interface SwitchCase extends ExpressionBase {
type: SWITCH_CASE;
caseE?: Expression;
stmts?: Statement[];
}
export interface FunctionDeclaration extends ScripNodeBase {
type: FUNCTION_DECLARATION;
id: Identifier;
args: Expression[];
stmt: BlockStatement;
}
// =====================================================================================================================
// Expressions
// All syntax nodes that represent an expression
export type Expression =
| UnaryExpression
| BinaryExpression
| SequenceExpression
| ConditionalExpression
| FunctionInvocationExpression
| MemberAccessExpression
| CalculatedMemberAccessExpression
| Identifier
| TemplateLiteralExpression
| Literal
| ArrayLiteral
| ObjectLiteral
| SpreadExpression
| AssignmentExpression
| NoArgExpression
| ArrowExpression
| PrefixOpExpression
| PostfixOpExpression
| ReactiveVarDeclaration
| VarDeclaration
| Destructure
| ObjectDestructure
| ArrayDestructure
| SwitchCase;
// Common base node for all expression syntax nodes
export interface ExpressionBase extends ScripNodeBase {
// Is this expression parenthesized?
parenthesized?: number;
}
export type UnaryOpSymbols = "+" | "-" | "~" | "!" | "typeof" | "delete";
export type BinaryOpSymbols =
| "**"
| "*"
| "/"
| "%"
| "+"
| "-"
| "<<"
| ">>"
| ">>>"
| "<"
| "<="
| ">"
| ">="
| "=="
| "==="
| "!="
| "!=="
| "&"
| "|"
| "^"
| "&&"
| "||"
| "??"
| "in";
export type AssignmentSymbols =
| "="
| "+="
| "-="
| "**="
| "*="
| "/="
| "%="
| "<<="
| ">>="
| ">>>="
| "&="
| "^="
| "|="
| "&&="
| "||="
| "??=";
export type PrefixOpSymbol = "++" | "--";
export interface UnaryExpression extends ExpressionBase {
type: UNARY_EXPRESSION;
op: UnaryOpSymbols;
expr: Expression;
}
export interface BinaryExpression extends ExpressionBase {
type: BINARY_EXPRESSION;
op: BinaryOpSymbols;
left: Expression;
right: Expression;
}
export interface SequenceExpression extends ExpressionBase {
type: SEQUENCE_EXPRESSION;
exprs: Expression[];
loose?: boolean;
}
export interface ConditionalExpression extends ExpressionBase {
type: CONDITIONAL_EXPRESSION;
cond: Expression;
thenE: Expression;
elseE: Expression;
}
export interface FunctionInvocationExpression extends ExpressionBase {
type: FUNCTION_INVOCATION_EXPRESSION;
obj: Expression;
arguments: Expression[];
}
export interface MemberAccessExpression extends ExpressionBase {
type: MEMBER_ACCESS_EXPRESSION;
obj: Expression;
member: string;
opt?: boolean;
}
export interface CalculatedMemberAccessExpression extends ExpressionBase {
type: CALCULATED_MEMBER_ACCESS_EXPRESSION;
obj: Expression;
member: Expression;
}
export interface Identifier extends ExpressionBase {
type: IDENTIFIER;
name: string;
isGlobal?: boolean;
}
export interface Literal extends ExpressionBase {
type: LITERAL;
value: any;
}
export interface TemplateLiteralExpression extends ExpressionBase {
type: TEMPLATE_LITERAL_EXPRESSION;
segments: (Literal | Expression)[];
}
export interface ArrayLiteral extends ExpressionBase {
type: ARRAY_LITERAL;
items: Expression[];
loose?: boolean;
}
export interface ObjectLiteral extends ExpressionBase {
type: OBJECT_LITERAL;
props: (SpreadExpression | [Expression, Expression])[];
}
export interface SpreadExpression extends ExpressionBase {
type: SPREAD_EXPRESSION;
expr: Expression;
}
export interface AssignmentExpression extends ExpressionBase {
type: ASSIGNMENT_EXPRESSION;
leftValue: Expression;
op: AssignmentSymbols;
expr: Expression;
}
export interface NoArgExpression extends ExpressionBase {
type: NO_ARG_EXPRESSION;
}
export interface ArrowExpression extends ExpressionBase {
type: ARROW_EXPRESSION;
name?: string;
args: Expression[];
statement: Statement;
}
export interface PrefixOpExpression extends ExpressionBase {
type: PREFIX_OP_EXPRESSION;
op: PrefixOpSymbol;
expr: Expression;
}
export interface PostfixOpExpression extends ExpressionBase {
type: POSTFIX_OP_EXPRESSION;
op: PrefixOpSymbol;
expr: Expression;
}
/**
* Represents a parsed and resolved module
*/
export type ScriptModule = {
type: "ScriptModule";
name: string;
parent?: ScriptModule | null;
functions: Record<string, FunctionDeclaration>;
statements: Statement[];
sources: Map<Statement, string>;
};
/**
* Represents a module error
*/
export type ModuleErrors = Record<string, ScriptParserErrorMessage[]>;
export type CollectedDeclarations = {
vars: Record<string, CodeDeclaration>;
functions: Record<string, CodeDeclaration>;
moduleErrors?: ModuleErrors;
};
export type CodeDeclaration = {
source?: string;
tree: Expression;
[x: string]: unknown;
};
```
--------------------------------------------------------------------------------
/xmlui/src/components/NestedApp/NestedAppNative.tsx:
--------------------------------------------------------------------------------
```typescript
import type { CSSProperties, ReactNode } from "react";
import { startTransition, useCallback, useEffect, useMemo, useRef, useState } from "react";
import type { Root } from "react-dom/client";
import ReactDOM from "react-dom/client";
import { AppRoot } from "../../components-core/rendering/AppRoot";
import type { ThemeTone } from "../../abstractions/ThemingDefs";
import { errReportComponent, xmlUiMarkupToComponent } from "../../components-core/xmlui-parser";
import { ApiInterceptorProvider } from "../../components-core/interception/ApiInterceptorProvider";
import { ErrorBoundary } from "../../components-core/rendering/ErrorBoundary";
import type { CompoundComponentDef } from "../../abstractions/ComponentDefs";
import { useTheme } from "../../components-core/theming/ThemeContext";
import { useComponentRegistry } from "../ComponentRegistryContext";
import { useIndexerContext } from "../App/IndexerContext";
import { useApiInterceptorContext } from "../../components-core/interception/useApiInterceptorContext";
import { EMPTY_ARRAY } from "../../components-core/constants";
import { useIsomorphicLayoutEffect } from "../../components-core/utils/hooks";
import styles from "./NestedApp.module.scss";
import classnames from "classnames";
import {
StyleInjectionTargetContext,
StyleProvider,
useStyles,
} from "../../components-core/theming/StyleContext";
type NestedAppProps = {
api?: any;
app: string;
components?: any[];
config?: any;
activeTone?: ThemeTone;
activeTheme?: string;
height?: string | number;
style?: CSSProperties;
refreshVersion?: number;
withSplashScreen?: boolean;
className?: string;
};
function AnimatedLogo() {
return (
<div className={styles.loadingContainer}>
<div className={styles.loadingText}>Loading XMLUI App...</div>
<div className={styles.logoWrapper}>
<svg viewBox="0 0 26 26" xmlns="http://www.w3.org/2000/svg">
{/* Unchanged inner paths */}
<path
d="M9.04674 19.3954C8.2739 19.3954 7.60226 19.2265 7.03199 18.8887C6.47443 18.5384 6.0435 18.0505 5.73938 17.425C5.43526 16.7869 5.2832 16.0362 5.2832 15.173V9.89961C5.2832 9.7745 5.32771 9.66815 5.41637 9.58059C5.50502 9.493 5.61275 9.44922 5.73938 9.44922H7.41222C7.55157 9.44922 7.6593 9.493 7.73524 9.58059C7.8239 9.66815 7.86841 9.7745 7.86841 9.89961V15.0604C7.86841 16.6117 8.55895 17.3874 9.94021 17.3874C10.5991 17.3874 11.1187 17.181 11.4988 16.7681C11.8917 16.3553 12.0881 15.786 12.0881 15.0604V9.89961C12.0881 9.7745 12.1325 9.66815 12.2211 9.58059C12.3098 9.493 12.4175 9.44922 12.5443 9.44922H14.217C14.3436 9.44922 14.4513 9.493 14.54 9.58059C14.6288 9.66815 14.6732 9.7745 14.6732 9.89961V18.7574C14.6732 18.8825 14.6288 18.9888 14.54 19.0764C14.4513 19.164 14.3436 19.2078 14.217 19.2078H12.6773C12.538 19.2078 12.4239 19.164 12.3352 19.0764C12.2591 18.9888 12.2211 18.8825 12.2211 18.7574V17.988C11.879 18.4258 11.4545 18.7699 10.9476 19.0201C10.4407 19.2703 9.80704 19.3954 9.04674 19.3954Z"
fill="#3367CC"
/>
<path
d="M17.6397 19.2104C17.5129 19.2104 17.4052 19.1666 17.3165 19.079C17.2279 18.9914 17.1835 18.8851 17.1835 18.76V9.90221C17.1835 9.7771 17.2279 9.67075 17.3165 9.58319C17.4052 9.4956 17.5129 9.45182 17.6397 9.45182H19.2174C19.3567 9.45182 19.4644 9.4956 19.5404 9.58319C19.6292 9.67075 19.6736 9.7771 19.6736 9.90221V18.76C19.6736 18.8851 19.6292 18.9914 19.5404 19.079C19.4644 19.1666 19.3567 19.2104 19.2174 19.2104H17.6397ZM17.5636 7.8379C17.4243 7.8379 17.3102 7.80038 17.2215 7.72531C17.1454 7.63773 17.1074 7.52514 17.1074 7.38751V6.03633C17.1074 5.91122 17.1454 5.80487 17.2215 5.71731C17.3102 5.62972 17.4243 5.58594 17.5636 5.58594H19.2933C19.4327 5.58594 19.5467 5.62972 19.6354 5.71731C19.7242 5.80487 19.7686 5.91122 19.7686 6.03633V7.38751C19.7686 7.52514 19.7242 7.63773 19.6354 7.72531C19.5467 7.80038 19.4327 7.8379 19.2933 7.8379H17.5636Z"
fill="#3367CC"
/>
{/* ✨ MODIFIED outer path for animation */}
<path
className={styles.animatedLogoPath}
d="M23.0215 2.81748H2.53486V23.044H23.0215V2.81748Z"
fill="none"
stroke="#3367CC"
strokeWidth="0.75"
/>
</svg>
</div>
</div>
);
}
export function LazyNestedApp({
immediate,
...restProps
}: NestedAppProps & { immediate?: boolean }) {
const [shouldRender, setShouldRender] = useState(immediate || false);
useEffect(() => {
if (!immediate) {
startTransition(() => {
setShouldRender(true);
});
}
}, [immediate]);
if (!shouldRender) {
return null;
}
return <NestedApp {...restProps} />;
}
export function IndexAwareNestedApp(props: NestedAppProps & { immediate?: boolean }) {
const { indexing } = useIndexerContext();
if (indexing) {
return null;
}
return <LazyNestedApp {...props} />;
}
export function NestedApp({
api,
app,
components = EMPTY_ARRAY,
config,
activeTheme,
activeTone,
height,
style,
refreshVersion,
withSplashScreen = false,
className,
}: NestedAppProps) {
const rootRef = useRef<HTMLDivElement>(null);
const shadowRef = useRef(null);
const contentRootRef = useRef<Root | null>(null);
const theme = useTheme();
const toneToApply = activeTone || config?.defaultTone || theme?.activeThemeTone;
const componentRegistry = useComponentRegistry();
const parentInterceptorContext = useApiInterceptorContext();
const [initialized, setInitialized] = useState(!withSplashScreen);
const [revealAnimationFinished, setRevealAnimationFinished] = useState(false);
//TODO illesg: we should come up with something to make sure that nestedApps don't overwrite each other's mocked api endpoints
// disabled for now, as it messes up the paths of not mocked APIs (e.g. resources/{staticJsonfiles})
//const safeId = playgroundId || nestedAppId;
//const apiUrl = api ? `/${safeId.replaceAll(":", "")}` : '';
const apiUrl = "";
const mock = useMemo(() => {
if (!api) {
return undefined;
}
let apiObject = api;
if (typeof api === "string") {
try {
apiObject = JSON.parse(api.replaceAll("\n", " "));
} catch (e) {
console.error("Failed to parse API definition", e);
return undefined;
}
}
return {
...apiObject,
type: "in-memory",
// apiUrl: apiUrl + (apiObject.apiUrl || ""),
};
}, [api]);
useIsomorphicLayoutEffect(() => {
if (!shadowRef.current && rootRef.current) {
// Clone existing style and link tags
shadowRef.current = rootRef.current.attachShadow({ mode: "open" });
let style = document.createElement("style");
// since we are using shadow DOM, we need to define the layers here
// to ensure that the styles are applied correctly (the adopted styleheets setting is asynchronous, so the layer order might not be respected if we don't do this)
// MUST BE IN SYNC WITH THE STYLESHEET ORDER IN index.scss
style.innerHTML = "@layer reset, base, components, dynamic;";
shadowRef.current.appendChild(style);
// This should run once to prepare the stylesheets
const sheetPromises = Array.from(document.styleSheets).map(async (sheet) => {
// Check if the owner element has the attribute you want to skip
if (
sheet.ownerNode &&
sheet.ownerNode instanceof Element &&
sheet.ownerNode.hasAttribute("data-style-hash")
) {
return null; // Skip this stylesheet
}
// Can't access cross-origin sheets, so skip them
if (!sheet.href || sheet.href.startsWith(window.location.origin)) {
try {
// Create a new CSSStyleSheet object
const newSheet = new CSSStyleSheet();
// Get the CSS rules as text
const cssText = Array.from(sheet.cssRules)
.map((rule) => rule.cssText)
.join(" \n");
// Apply the text to the new sheet object
await newSheet.replace(cssText);
return newSheet;
} catch (e) {
// console.error('Could not process stylesheet:', sheet.href, e);
return null;
}
}
return null;
});
// When your component mounts and the shadow root is available...
void Promise.all(sheetPromises).then((sheets) => {
// Filter out any sheets that failed to load
const validSheets = sheets.filter(Boolean);
// Apply the array of constructed stylesheets to the shadow root
// This is synchronous and does not trigger new network requests
shadowRef.current.adoptedStyleSheets = validSheets;
});
}
if (!contentRootRef.current && shadowRef.current) {
contentRootRef.current = ReactDOM.createRoot(shadowRef.current);
}
}, []);
const onInit = useCallback(() => {
setInitialized(true);
}, []);
useEffect(() => {
let { errors, component, erroneousCompoundComponentName } = xmlUiMarkupToComponent(app);
if (errors.length > 0) {
component = errReportComponent(errors, "Main.xmlui", erroneousCompoundComponentName);
}
const compoundComponents: CompoundComponentDef[] = (components ?? []).map((src) => {
const isErrorReportComponent = typeof src !== "string";
if (isErrorReportComponent) {
return src;
}
let { errors, component, erroneousCompoundComponentName } = xmlUiMarkupToComponent(
src as string,
);
if (errors.length > 0) {
return errReportComponent(errors, `nested xmlui`, erroneousCompoundComponentName);
}
return component;
});
let globalProps = {
name: config?.name,
...(config?.appGlobals || {}),
apiUrl,
};
contentRootRef.current?.render(
<ErrorBoundary node={component}>
<StyleInjectionTargetContext.Provider value={shadowRef.current}>
<StyleProvider forceNew={true}>
<ApiInterceptorProvider
parentInterceptorContext={parentInterceptorContext}
key={`app-${refreshVersion}`}
interceptor={mock}
waitForApiInterceptor={true}
>
<NestedAppRoot themeStylesToReset={theme.themeStyles}>
<AppRoot
onInit={onInit}
isNested={true}
previewMode={true}
standalone={true}
trackContainerHeight={height ? "fixed" : "auto"}
node={component}
globalProps={globalProps}
defaultTheme={activeTheme || config?.defaultTheme}
defaultTone={toneToApply as ThemeTone}
contributes={{
compoundComponents,
themes: config?.themes,
}}
resources={config?.resources}
extensionManager={componentRegistry.getExtensionManager()}
/>
</NestedAppRoot>
</ApiInterceptorProvider>
</StyleProvider>
</StyleInjectionTargetContext.Provider>
</ErrorBoundary>,
);
}, [
onInit,
activeTheme,
app,
componentRegistry,
components,
config?.appGlobals,
config?.defaultTheme,
config?.name,
config?.resources,
config?.themes,
height,
mock,
parentInterceptorContext,
style,
theme.themeStyles,
toneToApply,
refreshVersion,
]);
const mountedRef = useRef(false);
useEffect(() => {
mountedRef.current = true;
return () => {
mountedRef.current = false;
setTimeout(() => {
// Unmount the content root after a delay (and only if the component is not mounted anymore, could happen in dev mode - double useEffect call)
if (!mountedRef.current) {
contentRootRef.current?.unmount();
contentRootRef.current = null;
}
}, 0);
};
}, []);
const animationFinished = useCallback(() => {
setRevealAnimationFinished(true);
}, []);
const shouldAnimate = withSplashScreen && !revealAnimationFinished;
return (
<div className={classnames(styles.nestedAppPlaceholder, className)}>
{shouldAnimate && <AnimatedLogo />}
<div
ref={rootRef}
onTransitionEnd={animationFinished}
className={classnames(styles.nestedAppRoot, {
[styles.shouldAnimate]: shouldAnimate,
[styles.initialized]: initialized,
})}
/>
</div>
);
}
function NestedAppRoot({
children,
themeStylesToReset,
}: {
children: ReactNode;
themeStylesToReset?: Record<string, string>;
}) {
// css variables are leaking into to shadow dom, so we reset them here
const themeVarReset = useMemo(() => {
const vars = {};
Object.keys(themeStylesToReset).forEach((key) => {
vars[key] = "initial";
});
return vars;
}, [themeStylesToReset]);
const resetClassName = useStyles(themeVarReset, { prepend: true });
return (
<div className={classnames(resetClassName, styles.shadowRoot)} id={"nested-app-root"}>
<div className={styles.content}>{children}</div>
</div>
);
}
```
--------------------------------------------------------------------------------
/xmlui/src/components/FileInput/FileInput.spec.ts:
--------------------------------------------------------------------------------
```typescript
import { test, expect } from "../../testing/fixtures";
// =============================================================================
// BASIC FUNCTIONALITY TESTS
// =============================================================================
test("component renders with basic props", async ({ initTestBed, createFileInputDriver }) => {
await initTestBed(`<FileInput/>`);
const driver = await createFileInputDriver();
await expect(driver.component).toBeVisible();
});
test("component displays browse button", async ({ initTestBed, createFileInputDriver }) => {
await initTestBed(`<FileInput buttonLabel="Choose File"/>`);
const driver = await createFileInputDriver();
await expect(driver.getBrowseButton()).toBeVisible();
await expect(driver.getBrowseButton()).toContainText("Choose File");
});
test("component displays placeholder text", async ({ initTestBed, createFileInputDriver }) => {
await initTestBed(`<FileInput placeholder="Select a file..."/>`);
const driver = await createFileInputDriver();
const placeholder = await driver.getPlaceholder();
expect(placeholder).toBe("Select a file...");
});
test("component handles disabled state", async ({ initTestBed, createFileInputDriver }) => {
await initTestBed(`<FileInput enabled="false"/>`);
const driver = await createFileInputDriver();
await expect(driver.getBrowseButton()).toBeDisabled();
expect(await driver.isEnabled()).toBe(false);
});
test("component supports multiple file selection", async ({
initTestBed,
createFileInputDriver,
}) => {
await initTestBed(`<FileInput multiple="true"/>`);
const driver = await createFileInputDriver();
expect(await driver.isMultiple()).toBe(true);
});
test("component supports directory selection", async ({ initTestBed, createFileInputDriver }) => {
await initTestBed(`<FileInput directory="true"/>`);
const driver = await createFileInputDriver();
expect(await driver.isDirectory()).toBe(true);
});
test("component accepts specific file types", async ({ initTestBed, createFileInputDriver }) => {
await initTestBed(`<FileInput acceptsFileType="image/*,application/pdf"/>`);
const driver = await createFileInputDriver();
const acceptedTypes = await driver.getAcceptedFileTypes();
expect(acceptedTypes).toBe("image/*,application/pdf");
});
test("component accepts file type array", async ({ initTestBed, createFileInputDriver }) => {
await initTestBed(`<FileInput acceptsFileType="['.jpg', '.png', '.pdf']"/>`);
const driver = await createFileInputDriver();
const acceptedTypes = await driver.getAcceptedFileTypes();
expect(acceptedTypes).toContain(".jpg");
});
// =============================================================================
// ACCESSIBILITY TESTS (REQUIRED)
// =============================================================================
test("component has correct accessibility attributes", async ({
initTestBed,
createFileInputDriver,
}) => {
await initTestBed(`<FileInput label="Upload Document"/>`);
const driver = await createFileInputDriver();
await expect(driver.getBrowseButton()).toHaveRole("button");
await expect(driver.getHiddenInput()).toHaveAttribute("type", "file");
});
test("component is keyboard accessible", async ({ page, initTestBed, createFileInputDriver }) => {
await initTestBed(`
<VStack>
<FileInput testId="fileInput" label="Input" />
</VStack>
`);
const driver = await createFileInputDriver("fileInput");
await driver.getTextBox().focus();
await expect(driver.getTextBox()).toBeFocused();
});
test("component supports tab navigation", async ({ initTestBed, createFileInputDriver }) => {
await initTestBed(`
<VStack>
<FileInput testId="fileInput" label="Input" />
</VStack>
`);
const driver = await createFileInputDriver("fileInput");
await driver.getTextBox().focus();
await expect(driver.getTextBox()).toBeFocused();
await driver.getTextBox().press("Tab");
await expect(driver.getBrowseButton()).toBeVisible();
await expect(driver.getBrowseButton()).not.toBeDisabled();
await expect(driver.getBrowseButton()).toBeFocused();
});
test("component has hidden file input for screen readers", async ({
initTestBed,
createFileInputDriver,
}) => {
await initTestBed(`<FileInput/>`);
const driver = await createFileInputDriver();
const hiddenInput = driver.getHiddenInput();
await expect(hiddenInput).toBeAttached();
await expect(hiddenInput).toHaveAttribute("type", "file");
});
test("component textbox is readonly for accessibility", async ({
initTestBed,
createFileInputDriver,
}) => {
await initTestBed(`<FileInput/>`);
const driver = await createFileInputDriver();
expect(await driver.hasReadOnlyAttribute()).toBe(true);
});
// =============================================================================
// VISUAL STATE TESTS
// =============================================================================
test("component applies theme variables correctly", async ({
initTestBed,
createFileInputDriver,
}) => {
await initTestBed(`<FileInput/>`, {
testThemeVars: {
"backgroundColor-Button": "rgb(255, 0, 0)",
},
});
const driver = await createFileInputDriver();
// FileInput uses Button themes, so check the button
await expect(driver.getBrowseButton()).toHaveCSS("background-color", "rgb(255, 0, 0)");
});
test("component shows validation states", async ({ initTestBed, createFileInputDriver }) => {
await initTestBed(`<FileInput validationStatus="error"/>`);
const driver = await createFileInputDriver();
// Validation should be visible in the component
await expect(driver.component).toBeVisible();
await expect(driver.getTextBox()).toBeVisible();
});
test("component supports different button variants", async ({
initTestBed,
createFileInputDriver,
}) => {
await initTestBed(`<FileInput variant="outline"/>`);
const driver = await createFileInputDriver();
// Check that variant prop is passed through
await expect(driver.getBrowseButton()).toBeVisible();
await expect(driver.getBrowseButton()).toContainText("Browse");
});
test("component supports different button sizes", async ({
initTestBed,
createFileInputDriver,
}) => {
await initTestBed(`<FileInput buttonSize="lg"/>`);
const driver = await createFileInputDriver();
await expect(driver.getBrowseButton()).toHaveClass(/lg/);
});
test("component supports button positioning", async ({ initTestBed, createFileInputDriver }) => {
await initTestBed(`<FileInput buttonPosition="start"/>`);
const driver = await createFileInputDriver();
await expect(driver.getContainer()).toHaveClass(/buttonStart/);
});
// =============================================================================
// EDGE CASE TESTS (CRITICAL)
// =============================================================================
test("component handles null and undefined props gracefully", async ({
initTestBed,
createFileInputDriver,
}) => {
await initTestBed(`<FileInput/>`);
const driver = await createFileInputDriver();
await expect(driver.component).toBeVisible();
expect(await driver.getSelectedFiles()).toBe("");
});
test("component handles empty acceptsFileType", async ({ initTestBed, createFileInputDriver }) => {
await initTestBed(`<FileInput acceptsFileType=""/>`);
const driver = await createFileInputDriver();
expect(await driver.getAcceptedFileTypes()).toBe("");
});
test("component handles special characters in placeholder", async ({
initTestBed,
createFileInputDriver,
}) => {
await initTestBed(`<FileInput placeholder="Select file with émojis 🚀 & quotes"/>`);
const driver = await createFileInputDriver();
const placeholder = await driver.getPlaceholder();
expect(placeholder).toBe("Select file with émojis 🚀 & quotes");
});
test("component handles conflicting multiple and directory props", async ({
initTestBed,
createFileInputDriver,
}) => {
await initTestBed(`<FileInput multiple="false" directory="true"/>`);
const driver = await createFileInputDriver();
// Directory mode should enable multiple files
expect(await driver.isDirectory()).toBe(true);
expect(await driver.isMultiple()).toBe(true);
});
test("component handles empty file selection gracefully", async ({
initTestBed,
createFileInputDriver,
}) => {
await initTestBed(`<FileInput/>`);
const driver = await createFileInputDriver();
expect(await driver.getSelectedFiles()).toBe("");
});
// =============================================================================
// PERFORMANCE TESTS
// =============================================================================
test("component handles rapid button clicks efficiently", async ({
initTestBed,
createFileInputDriver,
}) => {
const { testStateDriver } = await initTestBed(`
<FileInput onFocus="testState = ++testState || 1"/>
`);
const driver = await createFileInputDriver();
// Multiple rapid clicks should not cause issues
await driver.getBrowseButton().click();
await driver.getBrowseButton().click();
await driver.getBrowseButton().click();
await expect(driver.component).toBeVisible();
});
// =============================================================================
// INTEGRATION TESTS
// =============================================================================
test("component works correctly in different layout contexts", async ({
initTestBed,
createFileInputDriver,
}) => {
await initTestBed(`
<VStack>
<FileInput label="Layout Test"/>
</VStack>
`);
const driver = await createFileInputDriver();
await expect(driver.component).toBeVisible();
});
test("component works in form context", async ({ initTestBed, createFileInputDriver }) => {
await initTestBed(`
<Form>
<FileInput label="Upload File" required="true"/>
</Form>
`);
const driver = await createFileInputDriver();
await expect(driver.component).toBeVisible();
});
test("gotFocus event fires on focus", async ({ initTestBed, page, createFileInputDriver }) => {
const { testStateDriver } = await initTestBed(`
<FileInput testId="fileInput" onGotFocus="testState = 'focused'" />
`);
const driver = await createFileInputDriver("fileInput");
await driver.getTextBox().focus();
await expect.poll(testStateDriver.testState).toEqual("focused");
});
test("gotFocus event fires on label focus", async ({ initTestBed, page }) => {
const { testStateDriver } = await initTestBed(`
<FileInput testId="fileInput" onGotFocus="testState = 'focused'" label="test" />
`);
await page.getByText("test").click();
await expect.poll(testStateDriver.testState).toEqual("focused");
});
test("lostFocus event fires on blue", async ({ initTestBed, page, createFileInputDriver }) => {
const { testStateDriver } = await initTestBed(`
<FileInput testId="fileInput" onLostFocus="testState = 'blurred'" />
`);
const driver = await createFileInputDriver("fileInput");
await driver.getTextBox().focus();
await driver.getTextBox().blur();
await expect.poll(testStateDriver.testState).toEqual("blurred");
});
test("component supports custom button templates", async ({
initTestBed,
createFileInputDriver,
}) => {
await initTestBed(`
<FileInput>
<property name="buttonIcon">
<Icon name="upload"/>
</property>
</FileInput>
`);
const driver = await createFileInputDriver();
await expect(driver.component).toBeVisible();
await expect(driver.getBrowseButton()).toContainText("Browse");
});
test("component handles label positioning correctly", async ({
initTestBed,
createFileInputDriver,
}) => {
await initTestBed(`
<FileInput
label="Upload Document"
labelPosition="top"
labelWidth="100px"
/>
`);
const driver = await createFileInputDriver();
await expect(driver.component).toBeVisible();
});
// =============================================================================
// VISUAL STATE TESTS
// =============================================================================
test("input has correct width in px", async ({ page, initTestBed }) => {
await initTestBed(`<FileInput width="200px" testId="test"/>`, {});
const input = page.getByTestId("test");
const { width } = await input.boundingBox();
expect(width).toBe(200);
});
test("input with label has correct width in px", async ({ page, initTestBed }) => {
await initTestBed(`<FileInput width="200px" label="test" testId="test"/>`, {});
const input = page.getByTestId("test");
const { width } = await input.boundingBox();
expect(width).toBe(200);
});
test("input has correct width in %", async ({ page, initTestBed }) => {
await page.setViewportSize({ width: 400, height: 300 });
await initTestBed(`<FileInput width="50%" testId="test"/>`, {});
const input = page.getByTestId("test");
const { width } = await input.boundingBox();
expect(width).toBe(200);
});
test("input with label has correct width in %", async ({ page, initTestBed }) => {
await page.setViewportSize({ width: 400, height: 300 });
await initTestBed(`<FileInput width="50%" label="test" testId="test"/>`, {});
const input = page.getByTestId("test");
const { width } = await input.boundingBox();
expect(width).toBe(200);
});
```