This is page 46 of 145. Use http://codebase.md/xmlui-org/xmlui/%7Bnode.props.src?lines=false&page={x} to view the full context.
# Directory Structure
```
├── .changeset
│ ├── config.json
│ └── tender-llamas-dress.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
│ │ │ ├── Choose.md
│ │ │ ├── CHStack.md
│ │ │ ├── ColorPicker.md
│ │ │ ├── Column.md
│ │ │ ├── ContentSeparator.md
│ │ │ ├── CVStack.md
│ │ │ ├── DataSource.md
│ │ │ ├── DateInput.md
│ │ │ ├── DatePicker.md
│ │ │ ├── DonutChart.md
│ │ │ ├── DropdownMenu.md
│ │ │ ├── EmojiSelector.md
│ │ │ ├── ExpandableItem.md
│ │ │ ├── FileInput.md
│ │ │ ├── FileUploadDropZone.md
│ │ │ ├── FlowLayout.md
│ │ │ ├── Footer.md
│ │ │ ├── Form.md
│ │ │ ├── FormItem.md
│ │ │ ├── FormSection.md
│ │ │ ├── Fragment.md
│ │ │ ├── H1.md
│ │ │ ├── H2.md
│ │ │ ├── H3.md
│ │ │ ├── H4.md
│ │ │ ├── H5.md
│ │ │ ├── H6.md
│ │ │ ├── Heading.md
│ │ │ ├── HSplitter.md
│ │ │ ├── HStack.md
│ │ │ ├── Icon.md
│ │ │ ├── IFrame.md
│ │ │ ├── Image.md
│ │ │ ├── Items.md
│ │ │ ├── LabelList.md
│ │ │ ├── Legend.md
│ │ │ ├── LineChart.md
│ │ │ ├── Link.md
│ │ │ ├── List.md
│ │ │ ├── Logo.md
│ │ │ ├── Markdown.md
│ │ │ ├── MenuItem.md
│ │ │ ├── MenuSeparator.md
│ │ │ ├── ModalDialog.md
│ │ │ ├── NavGroup.md
│ │ │ ├── NavLink.md
│ │ │ ├── NavPanel.md
│ │ │ ├── NoResult.md
│ │ │ ├── NumberBox.md
│ │ │ ├── Option.md
│ │ │ ├── Page.md
│ │ │ ├── PageMetaTitle.md
│ │ │ ├── Pages.md
│ │ │ ├── Pagination.md
│ │ │ ├── PasswordInput.md
│ │ │ ├── PieChart.md
│ │ │ ├── ProgressBar.md
│ │ │ ├── Queue.md
│ │ │ ├── RadioGroup.md
│ │ │ ├── RealTimeAdapter.md
│ │ │ ├── Redirect.md
│ │ │ ├── Select.md
│ │ │ ├── Slider.md
│ │ │ ├── Slot.md
│ │ │ ├── SpaceFiller.md
│ │ │ ├── Spinner.md
│ │ │ ├── Splitter.md
│ │ │ ├── Stack.md
│ │ │ ├── StickyBox.md
│ │ │ ├── SubMenuItem.md
│ │ │ ├── Switch.md
│ │ │ ├── TabItem.md
│ │ │ ├── Table.md
│ │ │ ├── TableOfContents.md
│ │ │ ├── Tabs.md
│ │ │ ├── Text.md
│ │ │ ├── TextArea.md
│ │ │ ├── TextBox.md
│ │ │ ├── Theme.md
│ │ │ ├── TimeInput.md
│ │ │ ├── Timer.md
│ │ │ ├── ToneChangerButton.md
│ │ │ ├── ToneSwitch.md
│ │ │ ├── Tooltip.md
│ │ │ ├── Tree.md
│ │ │ ├── VSplitter.md
│ │ │ ├── VStack.md
│ │ │ ├── xmlui-animations
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── Animation.md
│ │ │ │ ├── FadeAnimation.md
│ │ │ │ ├── FadeInAnimation.md
│ │ │ │ ├── FadeOutAnimation.md
│ │ │ │ ├── ScaleAnimation.md
│ │ │ │ └── SlideInAnimation.md
│ │ │ ├── xmlui-pdf
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Pdf.md
│ │ │ ├── xmlui-spreadsheet
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Spreadsheet.md
│ │ │ └── xmlui-website-blocks
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── Carousel.md
│ │ │ ├── HelloMd.md
│ │ │ ├── HeroSection.md
│ │ │ └── ScrollToTop.md
│ │ └── extensions
│ │ ├── _meta.json
│ │ ├── xmlui-animations
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── Animation.md
│ │ │ ├── FadeAnimation.md
│ │ │ ├── FadeInAnimation.md
│ │ │ ├── FadeOutAnimation.md
│ │ │ ├── ScaleAnimation.md
│ │ │ └── SlideInAnimation.md
│ │ └── xmlui-website-blocks
│ │ ├── _meta.json
│ │ ├── _overview.md
│ │ ├── Carousel.md
│ │ ├── HelloMd.md
│ │ ├── HeroSection.md
│ │ └── ScrollToTop.md
│ ├── extensions.ts
│ ├── index.html
│ ├── index.ts
│ ├── package.json
│ ├── public
│ │ ├── feed.rss
│ │ ├── mockServiceWorker.js
│ │ ├── pages
│ │ │ ├── _meta.json
│ │ │ ├── app-structure.md
│ │ │ ├── build-editor-component.md
│ │ │ ├── build-hello-world-component.md
│ │ │ ├── components-intro.md
│ │ │ ├── context-variables.md
│ │ │ ├── forms.md
│ │ │ ├── globals.md
│ │ │ ├── glossary.md
│ │ │ ├── helper-tags.md
│ │ │ ├── hosted-deployment.md
│ │ │ ├── howto
│ │ │ │ ├── assign-a-complex-json-literal-to-a-component-variable.md
│ │ │ │ ├── chain-a-refetch.md
│ │ │ │ ├── control-cache-invalidation.md
│ │ │ │ ├── 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
│ │ ├── Choose
│ │ │ ├── Choose.md
│ │ │ ├── Choose.spec.ts
│ │ │ └── Choose.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
│ │ │ └── test-padding.xmlui
│ │ ├── DataSource
│ │ │ ├── DataSource.md
│ │ │ └── DataSource.tsx
│ │ ├── DateInput
│ │ │ ├── DateInput.md
│ │ │ ├── DateInput.module.scss
│ │ │ ├── DateInput.spec.ts
│ │ │ ├── DateInput.tsx
│ │ │ └── DateInputNative.tsx
│ │ ├── DatePicker
│ │ │ ├── DatePicker.md
│ │ │ ├── DatePicker.module.scss
│ │ │ ├── DatePicker.spec.ts
│ │ │ ├── DatePicker.tsx
│ │ │ └── DatePickerNative.tsx
│ │ ├── DropdownMenu
│ │ │ ├── DropdownMenu.md
│ │ │ ├── DropdownMenu.module.scss
│ │ │ ├── DropdownMenu.spec.ts
│ │ │ ├── DropdownMenu.tsx
│ │ │ ├── DropdownMenuNative.tsx
│ │ │ ├── MenuItem.md
│ │ │ └── SubMenuItem.md
│ │ ├── EmojiSelector
│ │ │ ├── EmojiSelector.md
│ │ │ ├── EmojiSelector.spec.ts
│ │ │ ├── EmojiSelector.tsx
│ │ │ └── EmojiSelectorNative.tsx
│ │ ├── ExpandableItem
│ │ │ ├── ExpandableItem.module.scss
│ │ │ ├── ExpandableItem.spec.ts
│ │ │ ├── ExpandableItem.tsx
│ │ │ └── ExpandableItemNative.tsx
│ │ ├── FileInput
│ │ │ ├── FileInput.md
│ │ │ ├── FileInput.module.scss
│ │ │ ├── FileInput.spec.ts
│ │ │ ├── FileInput.tsx
│ │ │ └── FileInputNative.tsx
│ │ ├── FileUploadDropZone
│ │ │ ├── FileUploadDropZone.md
│ │ │ ├── FileUploadDropZone.module.scss
│ │ │ ├── FileUploadDropZone.spec.ts
│ │ │ ├── FileUploadDropZone.tsx
│ │ │ └── FileUploadDropZoneNative.tsx
│ │ ├── FlowLayout
│ │ │ ├── FlowLayout.md
│ │ │ ├── FlowLayout.module.scss
│ │ │ ├── FlowLayout.spec.ts
│ │ │ ├── FlowLayout.spec.ts-snapshots
│ │ │ │ └── Edge-cases-boxShadow-is-not-clipped-1-non-smoke-darwin.png
│ │ │ ├── FlowLayout.tsx
│ │ │ └── FlowLayoutNative.tsx
│ │ ├── Footer
│ │ │ ├── Footer.md
│ │ │ ├── Footer.module.scss
│ │ │ ├── Footer.spec.ts
│ │ │ ├── Footer.tsx
│ │ │ └── FooterNative.tsx
│ │ ├── Form
│ │ │ ├── Form.md
│ │ │ ├── Form.module.scss
│ │ │ ├── Form.spec.ts
│ │ │ ├── Form.tsx
│ │ │ ├── formActions.ts
│ │ │ ├── FormContext.ts
│ │ │ └── FormNative.tsx
│ │ ├── FormItem
│ │ │ ├── FormItem.md
│ │ │ ├── FormItem.module.scss
│ │ │ ├── FormItem.spec.ts
│ │ │ ├── FormItem.tsx
│ │ │ ├── FormItemNative.tsx
│ │ │ ├── HelperText.module.scss
│ │ │ ├── HelperText.tsx
│ │ │ ├── ItemWithLabel.tsx
│ │ │ └── Validations.ts
│ │ ├── FormSection
│ │ │ ├── FormSection.md
│ │ │ ├── FormSection.ts
│ │ │ └── FormSection.xmlui
│ │ ├── Fragment
│ │ │ ├── Fragment.spec.ts
│ │ │ └── Fragment.tsx
│ │ ├── Heading
│ │ │ ├── abstractions.ts
│ │ │ ├── H1.md
│ │ │ ├── H1.spec.ts
│ │ │ ├── H2.md
│ │ │ ├── H2.spec.ts
│ │ │ ├── H3.md
│ │ │ ├── H3.spec.ts
│ │ │ ├── H4.md
│ │ │ ├── H4.spec.ts
│ │ │ ├── H5.md
│ │ │ ├── H5.spec.ts
│ │ │ ├── H6.md
│ │ │ ├── H6.spec.ts
│ │ │ ├── Heading.md
│ │ │ ├── Heading.module.scss
│ │ │ ├── Heading.spec.ts
│ │ │ ├── Heading.tsx
│ │ │ └── HeadingNative.tsx
│ │ ├── HoverCard
│ │ │ ├── HoverCard.tsx
│ │ │ └── HovercardNative.tsx
│ │ ├── HtmlTags
│ │ │ ├── HtmlTags.module.scss
│ │ │ ├── HtmlTags.spec.ts
│ │ │ └── HtmlTags.tsx
│ │ ├── Icon
│ │ │ ├── AdmonitionDanger.tsx
│ │ │ ├── AdmonitionInfo.tsx
│ │ │ ├── AdmonitionNote.tsx
│ │ │ ├── AdmonitionTip.tsx
│ │ │ ├── AdmonitionWarning.tsx
│ │ │ ├── ApiIcon.tsx
│ │ │ ├── ArrowDropDown.module.scss
│ │ │ ├── ArrowDropDown.tsx
│ │ │ ├── ArrowDropUp.module.scss
│ │ │ ├── ArrowDropUp.tsx
│ │ │ ├── ArrowLeft.module.scss
│ │ │ ├── ArrowLeft.tsx
│ │ │ ├── ArrowRight.module.scss
│ │ │ ├── ArrowRight.tsx
│ │ │ ├── Attach.tsx
│ │ │ ├── Binding.module.scss
│ │ │ ├── Binding.tsx
│ │ │ ├── BoardIcon.tsx
│ │ │ ├── BoxIcon.tsx
│ │ │ ├── CheckIcon.tsx
│ │ │ ├── ChevronDownIcon.tsx
│ │ │ ├── ChevronLeft.tsx
│ │ │ ├── ChevronRight.tsx
│ │ │ ├── ChevronUpIcon.tsx
│ │ │ ├── CodeFileIcon.tsx
│ │ │ ├── CodeSandbox.tsx
│ │ │ ├── CompactListIcon.tsx
│ │ │ ├── ContentCopyIcon.tsx
│ │ │ ├── DarkToLightIcon.tsx
│ │ │ ├── DatabaseIcon.module.scss
│ │ │ ├── DatabaseIcon.tsx
│ │ │ ├── DocFileIcon.tsx
│ │ │ ├── DocIcon.tsx
│ │ │ ├── DotMenuHorizontalIcon.tsx
│ │ │ ├── DotMenuIcon.tsx
│ │ │ ├── EmailIcon.tsx
│ │ │ ├── EmptyFolderIcon.tsx
│ │ │ ├── ErrorIcon.tsx
│ │ │ ├── ExpressionIcon.tsx
│ │ │ ├── FillPlusCricleIcon.tsx
│ │ │ ├── FilterIcon.tsx
│ │ │ ├── FolderIcon.tsx
│ │ │ ├── GlobeIcon.tsx
│ │ │ ├── HomeIcon.tsx
│ │ │ ├── HyperLinkIcon.tsx
│ │ │ ├── Icon.md
│ │ │ ├── Icon.module.scss
│ │ │ ├── Icon.spec.ts
│ │ │ ├── Icon.tsx
│ │ │ ├── IconNative.tsx
│ │ │ ├── ImageFileIcon.tsx
│ │ │ ├── Inspect.tsx
│ │ │ ├── LightToDark.tsx
│ │ │ ├── LinkIcon.tsx
│ │ │ ├── ListIcon.tsx
│ │ │ ├── LooseListIcon.tsx
│ │ │ ├── MoonIcon.tsx
│ │ │ ├── MoreOptionsIcon.tsx
│ │ │ ├── NoSortIcon.tsx
│ │ │ ├── PDFIcon.tsx
│ │ │ ├── PenIcon.tsx
│ │ │ ├── PhoneIcon.tsx
│ │ │ ├── PhotoIcon.tsx
│ │ │ ├── PlusIcon.tsx
│ │ │ ├── SearchIcon.tsx
│ │ │ ├── ShareIcon.tsx
│ │ │ ├── SortAscendingIcon.tsx
│ │ │ ├── SortDescendingIcon.tsx
│ │ │ ├── StarsIcon.tsx
│ │ │ ├── SunIcon.tsx
│ │ │ ├── svg
│ │ │ │ ├── admonition_danger.svg
│ │ │ │ ├── admonition_info.svg
│ │ │ │ ├── admonition_note.svg
│ │ │ │ ├── admonition_tip.svg
│ │ │ │ ├── admonition_warning.svg
│ │ │ │ ├── api.svg
│ │ │ │ ├── arrow-dropdown.svg
│ │ │ │ ├── arrow-left.svg
│ │ │ │ ├── arrow-right.svg
│ │ │ │ ├── arrow-up.svg
│ │ │ │ ├── attach.svg
│ │ │ │ ├── binding.svg
│ │ │ │ ├── box.svg
│ │ │ │ ├── bulb.svg
│ │ │ │ ├── code-file.svg
│ │ │ │ ├── code-sandbox.svg
│ │ │ │ ├── dark_to_light.svg
│ │ │ │ ├── database.svg
│ │ │ │ ├── doc.svg
│ │ │ │ ├── empty-folder.svg
│ │ │ │ ├── expression.svg
│ │ │ │ ├── eye-closed.svg
│ │ │ │ ├── eye-dark.svg
│ │ │ │ ├── eye.svg
│ │ │ │ ├── file-text.svg
│ │ │ │ ├── filter.svg
│ │ │ │ ├── folder.svg
│ │ │ │ ├── img.svg
│ │ │ │ ├── inspect.svg
│ │ │ │ ├── light_to_dark.svg
│ │ │ │ ├── moon.svg
│ │ │ │ ├── pdf.svg
│ │ │ │ ├── photo.svg
│ │ │ │ ├── share.svg
│ │ │ │ ├── stars.svg
│ │ │ │ ├── sun.svg
│ │ │ │ ├── trending-down.svg
│ │ │ │ ├── trending-level.svg
│ │ │ │ ├── trending-up.svg
│ │ │ │ ├── txt.svg
│ │ │ │ ├── unknown-file.svg
│ │ │ │ ├── unlink.svg
│ │ │ │ └── xls.svg
│ │ │ ├── TableDeleteColumnIcon.tsx
│ │ │ ├── TableDeleteRowIcon.tsx
│ │ │ ├── TableInsertColumnIcon.tsx
│ │ │ ├── TableInsertRowIcon.tsx
│ │ │ ├── TrashIcon.tsx
│ │ │ ├── TrendingDownIcon.tsx
│ │ │ ├── TrendingLevelIcon.tsx
│ │ │ ├── TrendingUpIcon.tsx
│ │ │ ├── TxtIcon.tsx
│ │ │ ├── UnknownFileIcon.tsx
│ │ │ ├── UnlinkIcon.tsx
│ │ │ ├── UserIcon.tsx
│ │ │ ├── WarningIcon.tsx
│ │ │ └── XlsIcon.tsx
│ │ ├── IconProvider.tsx
│ │ ├── IconRegistryContext.tsx
│ │ ├── IFrame
│ │ │ ├── IFrame.md
│ │ │ ├── IFrame.module.scss
│ │ │ ├── IFrame.spec.ts
│ │ │ ├── IFrame.tsx
│ │ │ └── IFrameNative.tsx
│ │ ├── Image
│ │ │ ├── Image.md
│ │ │ ├── Image.module.scss
│ │ │ ├── Image.spec.ts
│ │ │ ├── Image.tsx
│ │ │ └── ImageNative.tsx
│ │ ├── Input
│ │ │ ├── index.ts
│ │ │ ├── InputAdornment.module.scss
│ │ │ ├── InputAdornment.tsx
│ │ │ ├── InputDivider.module.scss
│ │ │ ├── InputDivider.tsx
│ │ │ ├── InputLabel.module.scss
│ │ │ ├── InputLabel.tsx
│ │ │ ├── PartialInput.module.scss
│ │ │ └── PartialInput.tsx
│ │ ├── InspectButton
│ │ │ ├── InspectButton.module.scss
│ │ │ └── InspectButton.tsx
│ │ ├── Items
│ │ │ ├── Items.md
│ │ │ ├── Items.spec.ts
│ │ │ ├── Items.tsx
│ │ │ └── ItemsNative.tsx
│ │ ├── Link
│ │ │ ├── Link.md
│ │ │ ├── Link.module.scss
│ │ │ ├── Link.spec.ts
│ │ │ ├── Link.tsx
│ │ │ └── LinkNative.tsx
│ │ ├── List
│ │ │ ├── doc-resources
│ │ │ │ └── list-component-data.js
│ │ │ ├── List.md
│ │ │ ├── List.module.scss
│ │ │ ├── List.spec.ts
│ │ │ ├── List.tsx
│ │ │ └── ListNative.tsx
│ │ ├── Logo
│ │ │ ├── doc-resources
│ │ │ │ └── xmlui-logo.svg
│ │ │ ├── Logo.md
│ │ │ ├── Logo.tsx
│ │ │ └── LogoNative.tsx
│ │ ├── Markdown
│ │ │ ├── CodeText.module.scss
│ │ │ ├── CodeText.tsx
│ │ │ ├── Markdown.md
│ │ │ ├── Markdown.module.scss
│ │ │ ├── Markdown.spec.ts
│ │ │ ├── Markdown.tsx
│ │ │ ├── MarkdownNative.tsx
│ │ │ ├── parse-binding-expr.ts
│ │ │ └── utils.ts
│ │ ├── metadata-helpers.ts
│ │ ├── ModalDialog
│ │ │ ├── ConfirmationModalContextProvider.tsx
│ │ │ ├── Dialog.module.scss
│ │ │ ├── Dialog.tsx
│ │ │ ├── ModalDialog.md
│ │ │ ├── ModalDialog.module.scss
│ │ │ ├── ModalDialog.spec.ts
│ │ │ ├── ModalDialog.tsx
│ │ │ ├── ModalDialogNative.tsx
│ │ │ └── ModalVisibilityContext.tsx
│ │ ├── NavGroup
│ │ │ ├── NavGroup.md
│ │ │ ├── NavGroup.module.scss
│ │ │ ├── NavGroup.spec.ts
│ │ │ ├── NavGroup.tsx
│ │ │ ├── NavGroupContext.ts
│ │ │ └── NavGroupNative.tsx
│ │ ├── NavLink
│ │ │ ├── NavLink.md
│ │ │ ├── NavLink.module.scss
│ │ │ ├── NavLink.spec.ts
│ │ │ ├── NavLink.tsx
│ │ │ └── NavLinkNative.tsx
│ │ ├── NavPanel
│ │ │ ├── NavPanel.md
│ │ │ ├── NavPanel.module.scss
│ │ │ ├── NavPanel.spec.ts
│ │ │ ├── NavPanel.tsx
│ │ │ └── NavPanelNative.tsx
│ │ ├── NestedApp
│ │ │ ├── AppWithCodeView.module.scss
│ │ │ ├── AppWithCodeView.tsx
│ │ │ ├── AppWithCodeViewNative.tsx
│ │ │ ├── defaultProps.tsx
│ │ │ ├── logo.svg
│ │ │ ├── NestedApp.module.scss
│ │ │ ├── NestedApp.tsx
│ │ │ ├── NestedAppNative.tsx
│ │ │ ├── Tooltip.module.scss
│ │ │ ├── Tooltip.tsx
│ │ │ └── utils.ts
│ │ ├── NoResult
│ │ │ ├── NoResult.md
│ │ │ ├── NoResult.module.scss
│ │ │ ├── NoResult.spec.ts
│ │ │ ├── NoResult.tsx
│ │ │ └── NoResultNative.tsx
│ │ ├── NumberBox
│ │ │ ├── numberbox-abstractions.ts
│ │ │ ├── NumberBox.md
│ │ │ ├── NumberBox.module.scss
│ │ │ ├── NumberBox.spec.ts
│ │ │ ├── NumberBox.tsx
│ │ │ └── NumberBoxNative.tsx
│ │ ├── Option
│ │ │ ├── Option.md
│ │ │ ├── Option.spec.ts
│ │ │ ├── Option.tsx
│ │ │ ├── OptionNative.tsx
│ │ │ └── OptionTypeProvider.tsx
│ │ ├── PageMetaTitle
│ │ │ ├── PageMetaTilteNative.tsx
│ │ │ ├── PageMetaTitle.md
│ │ │ ├── PageMetaTitle.spec.ts
│ │ │ └── PageMetaTitle.tsx
│ │ ├── Pages
│ │ │ ├── Page.md
│ │ │ ├── Pages.md
│ │ │ ├── Pages.module.scss
│ │ │ ├── Pages.tsx
│ │ │ └── PagesNative.tsx
│ │ ├── Pagination
│ │ │ ├── Pagination.md
│ │ │ ├── Pagination.module.scss
│ │ │ ├── Pagination.spec.ts
│ │ │ ├── Pagination.tsx
│ │ │ └── PaginationNative.tsx
│ │ ├── PositionedContainer
│ │ │ ├── PositionedContainer.module.scss
│ │ │ ├── PositionedContainer.tsx
│ │ │ └── PositionedContainerNative.tsx
│ │ ├── ProfileMenu
│ │ │ ├── ProfileMenu.module.scss
│ │ │ └── ProfileMenu.tsx
│ │ ├── ProgressBar
│ │ │ ├── ProgressBar.md
│ │ │ ├── ProgressBar.module.scss
│ │ │ ├── ProgressBar.spec.ts
│ │ │ ├── ProgressBar.tsx
│ │ │ └── ProgressBarNative.tsx
│ │ ├── Queue
│ │ │ ├── Queue.md
│ │ │ ├── Queue.spec.ts
│ │ │ ├── Queue.tsx
│ │ │ ├── queueActions.ts
│ │ │ └── QueueNative.tsx
│ │ ├── RadioGroup
│ │ │ ├── RadioGroup.md
│ │ │ ├── RadioGroup.module.scss
│ │ │ ├── RadioGroup.spec.ts
│ │ │ ├── RadioGroup.tsx
│ │ │ ├── RadioGroupNative.tsx
│ │ │ ├── RadioItem.tsx
│ │ │ └── RadioItemNative.tsx
│ │ ├── RealTimeAdapter
│ │ │ ├── RealTimeAdapter.tsx
│ │ │ └── RealTimeAdapterNative.tsx
│ │ ├── Redirect
│ │ │ ├── Redirect.md
│ │ │ ├── Redirect.spec.ts
│ │ │ └── Redirect.tsx
│ │ ├── ResponsiveBar
│ │ │ ├── README.md
│ │ │ ├── ResponsiveBar.md
│ │ │ ├── ResponsiveBar.module.scss
│ │ │ ├── ResponsiveBar.spec.ts
│ │ │ ├── ResponsiveBar.tsx
│ │ │ └── ResponsiveBarNative.tsx
│ │ ├── Select
│ │ │ ├── HiddenOption.tsx
│ │ │ ├── OptionContext.ts
│ │ │ ├── Select.md
│ │ │ ├── Select.module.scss
│ │ │ ├── Select.spec.ts
│ │ │ ├── Select.tsx
│ │ │ ├── SelectContext.tsx
│ │ │ └── SelectNative.tsx
│ │ ├── SelectionStore
│ │ │ ├── SelectionStore.md
│ │ │ ├── SelectionStore.tsx
│ │ │ └── SelectionStoreNative.tsx
│ │ ├── Slider
│ │ │ ├── Slider.md
│ │ │ ├── Slider.module.scss
│ │ │ ├── Slider.spec.ts
│ │ │ ├── Slider.tsx
│ │ │ └── SliderNative.tsx
│ │ ├── Slot
│ │ │ ├── Slot.md
│ │ │ ├── Slot.spec.ts
│ │ │ └── Slot.ts
│ │ ├── SlotItem.tsx
│ │ ├── SpaceFiller
│ │ │ ├── SpaceFiller.md
│ │ │ ├── SpaceFiller.module.scss
│ │ │ ├── SpaceFiller.spec.ts
│ │ │ ├── SpaceFiller.tsx
│ │ │ └── SpaceFillerNative.tsx
│ │ ├── Spinner
│ │ │ ├── Spinner.md
│ │ │ ├── Spinner.module.scss
│ │ │ ├── Spinner.spec.ts
│ │ │ ├── Spinner.tsx
│ │ │ └── SpinnerNative.tsx
│ │ ├── Splitter
│ │ │ ├── HSplitter.md
│ │ │ ├── HSplitter.spec.ts
│ │ │ ├── Splitter.md
│ │ │ ├── Splitter.module.scss
│ │ │ ├── Splitter.spec.ts
│ │ │ ├── Splitter.tsx
│ │ │ ├── SplitterNative.tsx
│ │ │ ├── utils.ts
│ │ │ ├── VSplitter.md
│ │ │ └── VSplitter.spec.ts
│ │ ├── Stack
│ │ │ ├── CHStack.md
│ │ │ ├── CHStack.spec.ts
│ │ │ ├── CVStack.md
│ │ │ ├── CVStack.spec.ts
│ │ │ ├── HStack.md
│ │ │ ├── HStack.spec.ts
│ │ │ ├── Stack.md
│ │ │ ├── Stack.module.scss
│ │ │ ├── Stack.spec.ts
│ │ │ ├── Stack.tsx
│ │ │ ├── StackNative.tsx
│ │ │ ├── VStack.md
│ │ │ └── VStack.spec.ts
│ │ ├── StickyBox
│ │ │ ├── StickyBox.md
│ │ │ ├── StickyBox.module.scss
│ │ │ ├── StickyBox.tsx
│ │ │ └── StickyBoxNative.tsx
│ │ ├── Switch
│ │ │ ├── Switch.md
│ │ │ ├── Switch.spec.ts
│ │ │ └── Switch.tsx
│ │ ├── Table
│ │ │ ├── doc-resources
│ │ │ │ └── list-component-data.js
│ │ │ ├── react-table-config.d.ts
│ │ │ ├── Table.md
│ │ │ ├── Table.module.scss
│ │ │ ├── Table.spec.ts
│ │ │ ├── Table.tsx
│ │ │ ├── TableNative.tsx
│ │ │ └── useRowSelection.tsx
│ │ ├── TableOfContents
│ │ │ ├── TableOfContents.module.scss
│ │ │ ├── TableOfContents.spec.ts
│ │ │ ├── TableOfContents.tsx
│ │ │ └── TableOfContentsNative.tsx
│ │ ├── Tabs
│ │ │ ├── TabContext.tsx
│ │ │ ├── TabItem.md
│ │ │ ├── TabItem.tsx
│ │ │ ├── TabItemNative.tsx
│ │ │ ├── Tabs.md
│ │ │ ├── Tabs.module.scss
│ │ │ ├── Tabs.spec.ts
│ │ │ ├── Tabs.tsx
│ │ │ └── TabsNative.tsx
│ │ ├── Text
│ │ │ ├── Text.md
│ │ │ ├── Text.module.scss
│ │ │ ├── Text.spec.ts
│ │ │ ├── Text.tsx
│ │ │ └── TextNative.tsx
│ │ ├── TextArea
│ │ │ ├── TextArea.md
│ │ │ ├── TextArea.module.scss
│ │ │ ├── TextArea.spec.ts
│ │ │ ├── TextArea.tsx
│ │ │ ├── TextAreaNative.tsx
│ │ │ ├── TextAreaResizable.tsx
│ │ │ └── useComposedRef.ts
│ │ ├── TextBox
│ │ │ ├── TextBox.md
│ │ │ ├── TextBox.module.scss
│ │ │ ├── TextBox.spec.ts
│ │ │ ├── TextBox.tsx
│ │ │ └── TextBoxNative.tsx
│ │ ├── Theme
│ │ │ ├── NotificationToast.tsx
│ │ │ ├── Theme.md
│ │ │ ├── Theme.module.scss
│ │ │ ├── Theme.spec.ts
│ │ │ ├── Theme.tsx
│ │ │ └── ThemeNative.tsx
│ │ ├── TimeInput
│ │ │ ├── TimeInput.md
│ │ │ ├── TimeInput.module.scss
│ │ │ ├── TimeInput.spec.ts
│ │ │ ├── TimeInput.tsx
│ │ │ ├── TimeInputNative.tsx
│ │ │ └── utils.ts
│ │ ├── Timer
│ │ │ ├── Timer.md
│ │ │ ├── Timer.spec.ts
│ │ │ ├── Timer.tsx
│ │ │ └── TimerNative.tsx
│ │ ├── Toggle
│ │ │ ├── Toggle.module.scss
│ │ │ └── Toggle.tsx
│ │ ├── ToneChangerButton
│ │ │ ├── ToneChangerButton.md
│ │ │ ├── ToneChangerButton.spec.ts
│ │ │ └── ToneChangerButton.tsx
│ │ ├── ToneSwitch
│ │ │ ├── ToneSwitch.md
│ │ │ ├── ToneSwitch.module.scss
│ │ │ ├── ToneSwitch.spec.ts
│ │ │ ├── ToneSwitch.tsx
│ │ │ └── ToneSwitchNative.tsx
│ │ ├── Tooltip
│ │ │ ├── Tooltip.md
│ │ │ ├── Tooltip.module.scss
│ │ │ ├── Tooltip.spec.ts
│ │ │ ├── Tooltip.tsx
│ │ │ └── TooltipNative.tsx
│ │ ├── Tree
│ │ │ ├── testData.ts
│ │ │ ├── Tree-dynamic.spec.ts
│ │ │ ├── Tree-icons.spec.ts
│ │ │ ├── Tree.md
│ │ │ ├── Tree.spec.ts
│ │ │ ├── TreeComponent.module.scss
│ │ │ ├── TreeComponent.tsx
│ │ │ └── TreeNative.tsx
│ │ ├── TreeDisplay
│ │ │ ├── TreeDisplay.md
│ │ │ ├── TreeDisplay.module.scss
│ │ │ ├── TreeDisplay.tsx
│ │ │ └── TreeDisplayNative.tsx
│ │ ├── ValidationSummary
│ │ │ ├── ValidationSummary.module.scss
│ │ │ └── ValidationSummary.tsx
│ │ └── VisuallyHidden.tsx
│ ├── components-core
│ │ ├── abstractions
│ │ │ ├── ComponentRenderer.ts
│ │ │ ├── LoaderRenderer.ts
│ │ │ ├── standalone.ts
│ │ │ └── treeAbstractions.ts
│ │ ├── action
│ │ │ ├── actions.ts
│ │ │ ├── APICall.tsx
│ │ │ ├── FileDownloadAction.tsx
│ │ │ ├── FileUploadAction.tsx
│ │ │ ├── NavigateAction.tsx
│ │ │ └── TimedAction.tsx
│ │ ├── ApiBoundComponent.tsx
│ │ ├── appContext
│ │ │ ├── date-functions.ts
│ │ │ ├── math-function.ts
│ │ │ └── misc-utils.ts
│ │ ├── AppContext.tsx
│ │ ├── behaviors
│ │ │ ├── Behavior.tsx
│ │ │ └── CoreBehaviors.tsx
│ │ ├── component-hooks.ts
│ │ ├── ComponentDecorator.tsx
│ │ ├── ComponentViewer.tsx
│ │ ├── CompoundComponent.tsx
│ │ ├── constants.ts
│ │ ├── DebugViewProvider.tsx
│ │ ├── descriptorHelper.ts
│ │ ├── devtools
│ │ │ ├── InspectorDialog.module.scss
│ │ │ ├── InspectorDialog.tsx
│ │ │ └── InspectorDialogVisibilityContext.tsx
│ │ ├── EngineError.ts
│ │ ├── event-handlers.ts
│ │ ├── InspectorButton.module.scss
│ │ ├── InspectorContext.tsx
│ │ ├── interception
│ │ │ ├── abstractions.ts
│ │ │ ├── ApiInterceptor.ts
│ │ │ ├── ApiInterceptorProvider.tsx
│ │ │ ├── apiInterceptorWorker.ts
│ │ │ ├── Backend.ts
│ │ │ ├── Errors.ts
│ │ │ ├── IndexedDb.ts
│ │ │ ├── initMock.ts
│ │ │ ├── InMemoryDb.ts
│ │ │ ├── ReadonlyCollection.ts
│ │ │ └── useApiInterceptorContext.tsx
│ │ ├── loader
│ │ │ ├── ApiLoader.tsx
│ │ │ ├── DataLoader.tsx
│ │ │ ├── ExternalDataLoader.tsx
│ │ │ ├── Loader.tsx
│ │ │ ├── MockLoaderRenderer.tsx
│ │ │ └── PageableLoader.tsx
│ │ ├── LoaderComponent.tsx
│ │ ├── markup-check.ts
│ │ ├── parts.ts
│ │ ├── renderers.ts
│ │ ├── rendering
│ │ │ ├── AppContent.tsx
│ │ │ ├── AppRoot.tsx
│ │ │ ├── AppWrapper.tsx
│ │ │ ├── buildProxy.ts
│ │ │ ├── collectFnVarDeps.ts
│ │ │ ├── ComponentAdapter.tsx
│ │ │ ├── ComponentWrapper.tsx
│ │ │ ├── Container.tsx
│ │ │ ├── containers.ts
│ │ │ ├── ContainerWrapper.tsx
│ │ │ ├── ErrorBoundary.module.scss
│ │ │ ├── ErrorBoundary.tsx
│ │ │ ├── InvalidComponent.module.scss
│ │ │ ├── InvalidComponent.tsx
│ │ │ ├── nodeUtils.ts
│ │ │ ├── reducer.ts
│ │ │ ├── renderChild.tsx
│ │ │ ├── StandaloneComponent.tsx
│ │ │ ├── StateContainer.tsx
│ │ │ ├── UnknownComponent.module.scss
│ │ │ ├── UnknownComponent.tsx
│ │ │ └── valueExtractor.ts
│ │ ├── reportEngineError.ts
│ │ ├── RestApiProxy.ts
│ │ ├── script-runner
│ │ │ ├── asyncProxy.ts
│ │ │ ├── AttributeValueParser.ts
│ │ │ ├── bannedFunctions.ts
│ │ │ ├── BindingTreeEvaluationContext.ts
│ │ │ ├── eval-tree-async.ts
│ │ │ ├── eval-tree-common.ts
│ │ │ ├── eval-tree-sync.ts
│ │ │ ├── ParameterParser.ts
│ │ │ ├── process-statement-async.ts
│ │ │ ├── process-statement-common.ts
│ │ │ ├── process-statement-sync.ts
│ │ │ ├── ScriptingSourceTree.ts
│ │ │ ├── simplify-expression.ts
│ │ │ ├── statement-queue.ts
│ │ │ └── visitors.ts
│ │ ├── StandaloneApp.tsx
│ │ ├── StandaloneExtensionManager.ts
│ │ ├── TableOfContentsContext.tsx
│ │ ├── theming
│ │ │ ├── _themes.scss
│ │ │ ├── component-layout-resolver.ts
│ │ │ ├── extendThemeUtils.ts
│ │ │ ├── hvar.ts
│ │ │ ├── layout-resolver.ts
│ │ │ ├── parse-layout-props.ts
│ │ │ ├── StyleContext.tsx
│ │ │ ├── StyleRegistry.ts
│ │ │ ├── ThemeContext.tsx
│ │ │ ├── ThemeProvider.tsx
│ │ │ ├── themes
│ │ │ │ ├── base-utils.ts
│ │ │ │ ├── palette.ts
│ │ │ │ ├── root.ts
│ │ │ │ ├── solid.ts
│ │ │ │ ├── theme-colors.ts
│ │ │ │ └── xmlui.ts
│ │ │ ├── themeVars.module.scss
│ │ │ ├── themeVars.ts
│ │ │ ├── transformThemeVars.ts
│ │ │ └── utils.ts
│ │ ├── utils
│ │ │ ├── actionUtils.ts
│ │ │ ├── audio-utils.ts
│ │ │ ├── base64-utils.ts
│ │ │ ├── compound-utils.ts
│ │ │ ├── css-utils.ts
│ │ │ ├── DataLoaderQueryKeyGenerator.ts
│ │ │ ├── date-utils.ts
│ │ │ ├── extractParam.ts
│ │ │ ├── hooks.tsx
│ │ │ ├── LruCache.ts
│ │ │ ├── mergeProps.ts
│ │ │ ├── misc.ts
│ │ │ ├── request-params.ts
│ │ │ ├── statementUtils.ts
│ │ │ └── treeUtils.ts
│ │ └── xmlui-parser.ts
│ ├── index-standalone.ts
│ ├── index.scss
│ ├── index.ts
│ ├── language-server
│ │ ├── server-common.ts
│ │ ├── server-web-worker.ts
│ │ ├── server.ts
│ │ ├── services
│ │ │ ├── common
│ │ │ │ ├── docs-generation.ts
│ │ │ │ ├── lsp-utils.ts
│ │ │ │ ├── metadata-utils.ts
│ │ │ │ └── syntax-node-utilities.ts
│ │ │ ├── completion.ts
│ │ │ ├── diagnostic.ts
│ │ │ ├── format.ts
│ │ │ └── hover.ts
│ │ └── xmlui-metadata-generated.js
│ ├── logging
│ │ ├── LoggerContext.tsx
│ │ ├── LoggerInitializer.tsx
│ │ ├── LoggerService.ts
│ │ └── xmlui.ts
│ ├── logo.svg
│ ├── parsers
│ │ ├── common
│ │ │ ├── GenericToken.ts
│ │ │ ├── InputStream.ts
│ │ │ └── utils.ts
│ │ ├── scripting
│ │ │ ├── code-behind-collect.ts
│ │ │ ├── Lexer.ts
│ │ │ ├── modules.ts
│ │ │ ├── Parser.ts
│ │ │ ├── ParserError.ts
│ │ │ ├── ScriptingNodeTypes.ts
│ │ │ ├── TokenTrait.ts
│ │ │ ├── TokenType.ts
│ │ │ └── tree-visitor.ts
│ │ ├── style-parser
│ │ │ ├── errors.ts
│ │ │ ├── source-tree.ts
│ │ │ ├── StyleInputStream.ts
│ │ │ ├── StyleLexer.ts
│ │ │ ├── StyleParser.ts
│ │ │ └── tokens.ts
│ │ └── xmlui-parser
│ │ ├── CharacterCodes.ts
│ │ ├── diagnostics.ts
│ │ ├── fileExtensions.ts
│ │ ├── index.ts
│ │ ├── lint.ts
│ │ ├── parser.ts
│ │ ├── ParserError.ts
│ │ ├── scanner.ts
│ │ ├── syntax-kind.ts
│ │ ├── syntax-node.ts
│ │ ├── transform.ts
│ │ ├── utils.ts
│ │ ├── xmlui-serializer.ts
│ │ └── xmlui-tree.ts
│ ├── react-app-env.d.ts
│ ├── syntax
│ │ ├── monaco
│ │ │ ├── grammar.monacoLanguage.ts
│ │ │ ├── index.ts
│ │ │ ├── xmlui-dark.ts
│ │ │ ├── xmlui-light.ts
│ │ │ └── xmluiscript.monacoLanguage.ts
│ │ └── textMate
│ │ ├── index.ts
│ │ ├── xmlui-dark.json
│ │ ├── xmlui-light.json
│ │ ├── xmlui.json
│ │ └── xmlui.tmLanguage.json
│ ├── testing
│ │ ├── assertions.ts
│ │ ├── component-test-helpers.ts
│ │ ├── ComponentDrivers.ts
│ │ ├── drivers
│ │ │ ├── DateInputDriver.ts
│ │ │ ├── index.ts
│ │ │ ├── ModalDialogDriver.ts
│ │ │ ├── NumberBoxDriver.ts
│ │ │ ├── TextBoxDriver.ts
│ │ │ ├── TimeInputDriver.ts
│ │ │ ├── TimerDriver.ts
│ │ │ └── TreeDriver.ts
│ │ ├── fixtures.ts
│ │ ├── index.ts
│ │ ├── infrastructure
│ │ │ ├── index.html
│ │ │ ├── main.tsx
│ │ │ ├── public
│ │ │ │ ├── mockServiceWorker.js
│ │ │ │ ├── resources
│ │ │ │ │ ├── bell.svg
│ │ │ │ │ ├── box.svg
│ │ │ │ │ ├── doc.svg
│ │ │ │ │ ├── eye.svg
│ │ │ │ │ ├── flower-640x480.jpg
│ │ │ │ │ ├── sun.svg
│ │ │ │ │ ├── test-image-100x100.jpg
│ │ │ │ │ └── txt.svg
│ │ │ │ └── serve.json
│ │ │ └── TestBed.tsx
│ │ └── themed-app-test-helpers.ts
│ └── vite-env.d.ts
├── tests
│ ├── components
│ │ ├── CodeBlock
│ │ │ └── hightlight-code.test.ts
│ │ ├── playground-pattern.test.ts
│ │ └── Tree
│ │ └── Tree-states.test.ts
│ ├── components-core
│ │ ├── abstractions
│ │ │ └── treeAbstractions.test.ts
│ │ ├── container
│ │ │ └── buildProxy.test.ts
│ │ ├── interception
│ │ │ ├── orderBy.test.ts
│ │ │ ├── ReadOnlyCollection.test.ts
│ │ │ └── request-param-converter.test.ts
│ │ ├── scripts-runner
│ │ │ ├── AttributeValueParser.test.ts
│ │ │ ├── eval-tree-arrow-async.test.ts
│ │ │ ├── eval-tree-arrow.test.ts
│ │ │ ├── eval-tree-func-decl-async.test.ts
│ │ │ ├── eval-tree-func-decl.test.ts
│ │ │ ├── eval-tree-pre-post.test.ts
│ │ │ ├── eval-tree-regression.test.ts
│ │ │ ├── eval-tree.test.ts
│ │ │ ├── function-proxy.test.ts
│ │ │ ├── parser-regression.test.ts
│ │ │ ├── process-event.test.ts
│ │ │ ├── process-function.test.ts
│ │ │ ├── process-implicit-context.test.ts
│ │ │ ├── process-statement-asgn.test.ts
│ │ │ ├── process-statement-destruct.test.ts
│ │ │ ├── process-statement-regs.test.ts
│ │ │ ├── process-statement-sync.test.ts
│ │ │ ├── process-statement.test.ts
│ │ │ ├── process-switch-sync.test.ts
│ │ │ ├── process-switch.test.ts
│ │ │ ├── process-try-sync.test.ts
│ │ │ ├── process-try.test.ts
│ │ │ └── test-helpers.ts
│ │ ├── test-metadata-handler.ts
│ │ ├── theming
│ │ │ ├── border-segments.test.ts
│ │ │ ├── component-layout.resolver.test.ts
│ │ │ ├── layout-property-parser.test.ts
│ │ │ ├── layout-resolver.test.ts
│ │ │ ├── layout-resolver2.test.ts
│ │ │ ├── layout-vp-override.test.ts
│ │ │ └── padding-segments.test.ts
│ │ └── utils
│ │ ├── date-utils.test.ts
│ │ ├── format-human-elapsed-time.test.ts
│ │ └── LruCache.test.ts
│ ├── language-server
│ │ ├── completion.test.ts
│ │ ├── format.test.ts
│ │ ├── hover.test.ts
│ │ └── mockData.ts
│ └── parsers
│ ├── common
│ │ └── input-stream.test.ts
│ ├── markdown
│ │ └── parse-binding-expression.test.ts
│ ├── parameter-parser.test.ts
│ ├── paremeter-parser.test.ts
│ ├── scripting
│ │ ├── eval-tree-arrow.test.ts
│ │ ├── eval-tree-pre-post.test.ts
│ │ ├── eval-tree.test.ts
│ │ ├── function-proxy.test.ts
│ │ ├── lexer-literals.test.ts
│ │ ├── lexer-misc.test.ts
│ │ ├── module-parse.test.ts
│ │ ├── parser-arrow.test.ts
│ │ ├── parser-assignments.test.ts
│ │ ├── parser-binary.test.ts
│ │ ├── parser-destructuring.test.ts
│ │ ├── parser-errors.test.ts
│ │ ├── parser-expressions.test.ts
│ │ ├── parser-function.test.ts
│ │ ├── parser-literals.test.ts
│ │ ├── parser-primary.test.ts
│ │ ├── parser-regex.test.ts
│ │ ├── parser-statements.test.ts
│ │ ├── parser-unary.test.ts
│ │ ├── process-event.test.ts
│ │ ├── process-implicit-context.test.ts
│ │ ├── process-statement-asgn.test.ts
│ │ ├── process-statement-destruct.test.ts
│ │ ├── process-statement-regs.test.ts
│ │ ├── process-statement-sync.test.ts
│ │ ├── process-statement.test.ts
│ │ ├── process-switch-sync.test.ts
│ │ ├── process-switch.test.ts
│ │ ├── process-try-sync.test.ts
│ │ ├── process-try.test.ts
│ │ ├── simplify-expression.test.ts
│ │ ├── statement-hooks.test.ts
│ │ └── test-helpers.ts
│ ├── style-parser
│ │ ├── generateHvarChain.test.ts
│ │ ├── parseHVar.test.ts
│ │ ├── parser.test.ts
│ │ └── tokens.test.ts
│ └── xmlui
│ ├── lint.test.ts
│ ├── parser.test.ts
│ ├── scanner.test.ts
│ ├── transform.attr.test.ts
│ ├── transform.circular.test.ts
│ ├── transform.element.test.ts
│ ├── transform.errors.test.ts
│ ├── transform.escape.test.ts
│ ├── transform.regression.test.ts
│ ├── transform.script.test.ts
│ ├── transform.test.ts
│ └── xmlui.ts
├── tests-e2e
│ ├── api-bound-component-regression.spec.ts
│ ├── api-call-as-extracted-component.spec.ts
│ ├── assign-to-object-or-array-regression.spec.ts
│ ├── binding-regression.spec.ts
│ ├── children-as-template-context-vars.spec.ts
│ ├── compound-component.spec.ts
│ ├── context-vars-regression.spec.ts
│ ├── data-bindings.spec.ts
│ ├── datasource-and-api-usage-in-var.spec.ts
│ ├── datasource-direct-binding.spec.ts
│ ├── datasource-onLoaded-regression.spec.ts
│ ├── modify-array-item-regression.spec.ts
│ ├── namespaces.spec.ts
│ ├── push-to-array-regression.spec.ts
│ ├── screen-breakpoints.spec.ts
│ ├── scripting.spec.ts
│ ├── state-scope-in-pages.spec.ts
│ └── state-var-scopes.spec.ts
├── tsconfig.json
├── tsdown.config.ts
├── vite.config.ts
└── vitest.config.ts
```
# Files
--------------------------------------------------------------------------------
/xmlui/dev-docs/next/component-dev-guide.md:
--------------------------------------------------------------------------------
```markdown
# XMLUI Component Development Guide
## Component Architecture Overview
XMLUI components follow a specific architectural pattern that separates concerns between React implementation and XMLUI framework integration. Understanding this pattern is crucial for creating components that integrate properly with the XMLUI ecosystem.
## The Two-File Pattern
Every XMLUI component should be split into two files:
### 1. Native Component (`ComponentNameNative.tsx`)
This file contains the actual React component implementation:
```typescript
// ToneSwitchNative.tsx
import React from 'react';
export type ToneSwitchProps = {
/**
* Icon to display for light mode
* @default "sun"
*/
iconLight?: string;
/**
* Icon to display for dark mode
* @default "moon"
*/
iconDark?: string;
};
export function ToneSwitch({
iconLight = "sun",
iconDark = "moon"
}: ToneSwitchProps) {
// React component implementation
return <div>...</div>;
}
```
**Key characteristics:**
- Pure React component
- Exports TypeScript interface for props
- Uses standard React patterns and hooks
- Contains all component logic and rendering
- Can be used standalone outside XMLUI
### 2. XMLUI Renderer (`ComponentName.tsx`)
This file contains the XMLUI integration layer:
```typescript
// ToneSwitch.tsx
import { createComponentRenderer } from "../../components-core/renderers";
import { createMetadata } from "../metadata-helpers";
import { ToneSwitch, type ToneSwitchProps } from "./ToneSwitchNative";
const COMP = "ToneSwitch";
export const ToneSwitchMd = createMetadata({
status: "stable",
description: "`ToneSwitch` enables users to switch themes.",
props: {
iconLight: {
type: "string",
description: "Icon to display for light mode",
defaultValue: "sun",
},
iconDark: {
type: "string",
description: "Icon to display for dark mode",
defaultValue: "moon",
},
},
// ... theme variables, etc.
});
export const toneSwitchComponentRenderer = createComponentRenderer(
COMP,
ToneSwitchMd,
({ node, extractValue }) => {
return <ToneSwitch
iconLight={extractValue(node.props.iconLight)}
iconDark={extractValue(node.props.iconDark)}
/>;
}
);
```
**Key characteristics:**
- Imports the native component
- Defines component metadata for documentation
- Creates the component renderer that bridges XMLUI and React
- Handles prop extraction from XMLUI markup
## Component Registration
### 1. Add to Component Metadata Registry
```typescript
// collectedComponentMetadata.ts
import { ToneSwitchMd } from "./ToneSwitch/ToneSwitch";
export const collectedComponentMetadata = {
// ... other components
ToneSwitch: ToneSwitchMd,
};
```
### 2. Register Component Renderer
```typescript
// ComponentProvider.tsx
import { toneSwitchComponentRenderer } from "./ToneSwitch/ToneSwitch";
// In ComponentRegistry constructor:
if (process.env.VITE_USED_COMPONENTS_ToneSwitch !== "false") {
this.registerCoreComponent(toneSwitchComponentRenderer);
}
```
## Component Documentation Pattern
### Source Documentation (`ComponentName.md`)
Each component should have a corresponding `.md` file in its source directory (`src/components/ComponentName/ComponentName.md`) that contains template-based documentation. This source documentation uses special template markers that get processed during documentation generation.
#### Template Markers
XMLUI uses these optional template patterns for documentation generation:
- **`%-DESC-START` / `%-DESC-END`**: Main component description and examples
- **`%-PROP-START propName` / `%-PROP-END`**: Detailed property documentation
- **`%-EVENT-START eventName` / `%-EVENT-END`**: Event documentation
- **`%-API-START methodName` / `%-API-END`**: API method documentation
Use these to provide additional detail beyond what's automatically generated from the metadata.
The ToneSwitch example shows the minimal approach - just %-DESC-START/END for the main example, letting the props documentation be auto-generated from the metadata. You'd add %-PROP-START iconLight only if you wanted to elaborate on that specific prop with examples or additional context beyond "Icon to display for light mode".
#### Example Source Documentation
```markdown
%-DESC-START
xmlui-pg {4} copy display name="Example: using ToneSwitch"
<App>
<AppHeader>
<SpaceFiller />
<ToneSwitch />
</AppHeader>
<Card
title="Tone Switch"
subtitle="Toggle the switch to change the tone."
/>
</App>
%-DESC-END
```
### Generated Documentation
When you run `npm run generate-all-docs` from `xmlui/xmlui`, the source documentation gets processed and generates the final documentation files in `docs/content/components/`.
The generated documentation includes:
- Component description from `%-DESC-START/END` blocks
- Properties section with details from metadata and `%-PROP-START/END` blocks
- Events section (if any events are defined)
- Exposed methods section (if any API methods are defined)
- Styling section with theme variables table
#### Generated Documentation Example
```markdown
# ToneSwitch [#toneswitch]
ToneSwitch enables the user to switch between light and dark modes using a switch control.
xmlui-pg {4} copy display name="Example: using ToneSwitch"
<App>
<AppHeader>
<SpaceFiller />
<ToneSwitch />
</AppHeader>
<Card
title="Tone Switch"
subtitle="Toggle the switch to change the tone."
/>
</App>
## Properties [#properties]
### iconDark (default: "moon") [#icondark-default-moon]
Icon to display for dark mode
### iconLight (default: "sun") [#iconlight-default-sun]
Icon to display for light mode
## Events [#events]
This component does not have any events.
## Exposed Methods [#exposed-methods]
This component does not expose any methods.
## Styling [#styling]
### Theme Variables [#theme-variables]
| Variable | Default Value (Light) | Default Value (Dark) |
| --- | --- | --- |
| [backgroundColor](../styles-and-themes/common-units/#color)-ToneSwitch-dark | $color-primary-500 | $color-primary-500 |
| [backgroundColor](../styles-and-themes/common-units/#color)-ToneSwitch-light | $color-surface-200 | $color-surface-700 |
```
**Important distinction**: The source `.md` files in `src/components/` are templates that use special markers, while the generated files in `docs/content/components/` are the final documentation that gets published. Always edit the source files, not the generated ones.
## Component Metadata Best Practices
### Props Definition
```typescript
props: {
propName: {
type: "string" | "boolean" | "number",
description: "Clear description of what this prop does",
defaultValue: "default value if any",
required?: true, // if prop is required
},
}
```
### Theme Variables
```typescript
// Include if component has custom styling
themeVars: parseScssVar(styles.themeVars),
limitThemeVarsToComponent: true,
defaultThemeVars: {
[`backgroundColor-${COMP}`]: "$color-surface-200",
[`color-${COMP}`]: "$color-text-primary",
// Dark mode variants
dark: {
[`backgroundColor-${COMP}`]: "$color-surface-700",
}
}
```
## Component Renderer Patterns
### Basic Prop Extraction
```typescript
export const componentRenderer = createComponentRenderer(
COMP,
ComponentMd,
({ node, extractValue }) => {
return <Component
title={extractValue(node.props.title)}
enabled={extractValue.asOptionalBoolean(node.props.enabled)}
count={extractValue.asOptionalNumber(node.props.count)}
/>;
}
);
```
### Common Extract Value Methods
- `extractValue(prop)` - Extract string value
- `extractValue.asOptionalString(prop)` - Extract optional string
- `extractValue.asOptionalBoolean(prop)` - Extract optional boolean
- `extractValue.asOptionalNumber(prop)` - Extract optional number
## Extending Existing Components
### Using inputRenderer Pattern
When extending components like Toggle, use the `inputRenderer` pattern:
```typescript
export function CustomSwitch({ icon }: CustomSwitchProps) {
return (
<Toggle
variant="switch"
inputRenderer={(contextVars) => (
<div className="custom-switch">
<Icon name={contextVars.$checked ? "on" : "off"} />
</div>
)}
/>
);
}
```
**Context Variables Available:**
- `$checked` - Current boolean state
- `$setChecked` - Function to update state
## Theme Integration
### Using Theme Context
```typescript
import { useThemes, useTheme } from "../../components-core/theming/ThemeContext";
function ThemeAwareComponent() {
// For reading/writing theme state
const { activeThemeTone, setActiveThemeTone } = useThemes();
// For accessing theme properties
const theme = useTheme();
return <div data-theme={activeThemeTone}>...</div>;
}
```
## File Structure
```
src/components/MyComponent/
├── MyComponent.tsx # XMLUI renderer & metadata
├── MyComponentNative.tsx # React implementation
├── MyComponent.module.scss # Component styles
├── MyComponent.md # Component documentation template
```
## Documentation Generation Workflow
1. **Create source documentation**: Write your component documentation in `src/components/ComponentName/ComponentName.md` using the template markers
2. **Generate documentation**: Run `npm run generate-all-docs` from `xmlui/xmlui`
3. **Review generated docs**: Check the generated files in `docs/content/components/` to ensure they look correct
4. **Iterate as needed**: Update source documentation and regenerate as needed
The documentation generation process:
- Extracts component metadata from your component renderer
- Processes template markers in source `.md` files
- Combines metadata with template content
- Generates final documentation with proper formatting, property tables, and theme variable documentation
## Common Pitfalls to Avoid
### 1. Don't Mix Concerns
- Keep React logic in Native component
- Keep XMLUI integration in renderer component
- Don't put XMLUI-specific code in Native component
### 2. Always Export Props Type
```typescript
// Good
export type MyComponentProps = { ... };
export function MyComponent(props: MyComponentProps) { ... }
// Bad
export function MyComponent({ title }: { title: string }) { ... }
```
### 3. Handle Prop Extraction Properly
```typescript
// Good - handles undefined props
iconLight={extractValue(node.props.iconLight)}
// Bad - assumes props exist
iconLight={node.props.iconLight}
```
### 4. Include Proper Default Values
```typescript
// Good - consistent defaults
export function Component({
title = "Default Title"
}: ComponentProps) { ... }
// In metadata:
defaultValue: "Default Title"
```
### 5. Don't Edit Generated Documentation
```typescript
// Good - edit source template
// src/components/MyComponent/MyComponent.md
// Bad - editing generated files (gets overwritten)
// docs/content/components/MyComponent.md
```
## Testing Your Component
### 1. Test Native Component Standalone
```typescript
// Can be tested as regular React component
render(<ToneSwitch iconLight="custom" iconDark="custom" />);
```
### 2. Test XMLUI Integration
```xmlui
<ToneSwitch iconLight="brightness" iconDark="nightMode" />
```
### 3. Verify Documentation Generation
- Check that props appear in generated docs
- Verify default values are correct
- Ensure descriptions are helpful
- Test example code snippets
## Documentation Guidelines
### Component Description
- Start with backticks around component name
- Be concise but descriptive
- Focus on what the component does, not how
### Prop Descriptions
- Use clear, actionable language
- Mention default behavior
- Include examples when helpful
- Avoid implementation details
### Examples
- Show basic usage first
- Include common customization scenarios
- Use realistic prop values
- Use `xmlui-pg` blocks for interactive examples
### Template Marker Usage
- Use `%-DESC-START/END` for main description and primary examples
- Use `%-PROP-START propName/END` for detailed property documentation
- Use `%-EVENT-START eventName/END` for event documentation
- Use `%-API-START methodName/END` for API method documentation
## Summary Checklist
Before submitting a new XMLUI component:
- [ ] Split into Native and Renderer files
- [ ] Proper TypeScript interfaces exported
- [ ] Component registered in ComponentProvider
- [ ] Metadata added to collectedComponentMetadata
- [ ] Props properly documented with defaults
- [ ] Theme variables defined if needed
- [ ] Source documentation created with template markers
- [ ] Documentation generated and reviewed
- [ ] Examples include usage scenarios
- [ ] Component follows established patterns
- [ ] No XMLUI-specific code in Native component
- [ ] Proper prop extraction in renderer
- [ ] Generated documentation is accurate and helpful
```
--------------------------------------------------------------------------------
/xmlui/src/components-core/utils/hooks.tsx:
--------------------------------------------------------------------------------
```typescript
import type { MutableRefObject } from "react";
import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import { isEqual } from "lodash-es";
import type { ComponentApi, ContainerState } from "../rendering/ContainerWrapper";
import type { ColorDef } from "./css-utils";
import { shallowCompare, useEvent } from "../utils/misc";
import { useTheme } from "../theming/ThemeContext";
import { EMPTY_OBJECT } from "../constants";
import Color from "color";
/**
* This hook invokes a callback when the size of the specified DOM element changes.
* @param element A DOM element to watch for size changes
* @param callback The callback function to invoke on size changes
*/
export const useResizeObserver = (
element: React.MutableRefObject<Element | undefined | null>,
callback: ResizeObserverCallback,
) => {
const current = element?.current;
const observer = useRef<ResizeObserver>();
useEffect(() => {
// --- We are already observing old element
if (observer?.current && current) {
observer.current.unobserve(current);
}
observer.current = new ResizeObserver(callback);
if (element && element.current && observer.current) {
observer.current.observe(element.current);
}
}, [callback, current, element]);
};
/**
* This hook gets the previous state of the specified value (props, variable used in a React
* function).
*
* @see {@link https://blog.logrocket.com/accessing-previous-props-state-react-hooks/}
*/
export function usePrevious<T>(value: T): ReturnType<typeof useRef<T>>["current"] {
const ref = useRef<T>();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
/**
* This hook tests if the component is used within an iframe.
* @returns True, if the component is used within an iframe; otherwise, false.
*/
export function useIsInIFrame() {
return useMemo(() => {
try {
return window.self !== window.top;
} catch (e) {
return true;
}
}, []);
}
// --- Tests if the document has the focus
const hasFocus = () => typeof document !== "undefined" && document.hasFocus();
/**
* This hook tests if the window has the focus.
* @returns True, if the window has the focus; otherwise, false.
*/
export function useIsWindowFocused() {
const [focused, setFocused] = useState(hasFocus); // Focus for first render
useEffect(() => {
setFocused(hasFocus()); // Focus for additional renders
const onFocus = () => setFocused(true);
const onBlur = () => setFocused(false);
window.addEventListener("focus", onFocus);
window.addEventListener("blur", onBlur);
return () => {
window.removeEventListener("focus", onFocus);
window.removeEventListener("blur", onBlur);
};
}, []);
return focused;
}
/**
* This hook allows running media queries.
* @param query Media query to run
*/
export function useMediaQuery(query: string) {
const [matches, setMatches] = useState<boolean>(false);
useEffect(() => {
if (!window) {
setMatches(false);
return;
}
const matchMedia = window.matchMedia(query);
// Triggered at the first client-side load and if query changes
handleChange();
matchMedia.addEventListener("change", handleChange);
return () => {
matchMedia.removeEventListener("change", handleChange);
};
function handleChange() {
setMatches(matchMedia.matches);
}
}, [query]);
return matches;
}
/**
* This hook runs a callback function when a key is pressed in the document window.
* @param onDocumentKeydown Callback function to run
*/
export function useDocumentKeydown(onDocumentKeydown: (event: KeyboardEvent) => void) {
const onKeyDown = useEvent(onDocumentKeydown);
useEffect(() => {
document.addEventListener("keydown", onKeyDown);
return () => {
document.removeEventListener("keydown", onKeyDown);
};
}, [onKeyDown]);
}
/**
* This hook runs a function when the corresponding component has been mounted.
* @param onMount
*/
export function useOnMount(onMount: any) {
const thizRef = useRef({ mountedFired: false });
useEffect(() => {
if (!thizRef.current.mountedFired) {
thizRef.current.mountedFired = true;
onMount?.();
}
}, [onMount]);
}
/**
* This hook memoizes the specified value. It uses a shallow comparison with the previously
* stored value when checking for changes. So, while a shallow comparison shows equality,
* it returns with the memoized value.
* @param value Value to memoize
*/
export function useShallowCompareMemoize<T extends Record<any, any> | undefined>(value: T) {
const ref = React.useRef<T>(value);
const signalRef = React.useRef<number>(0);
if (!shallowCompare(value, ref.current)) {
ref.current = value;
signalRef.current++;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
return React.useMemo(() => ref.current, [signalRef.current]);
}
/**
* This hook memoizes the specified value. When checking for changes, it uses a deep comparison
* with the previously stored value. So, while a deep comparison shows equality, it returns with
* the memoized value, even if value references differ.
* @param value Value to memoize
*/
export function useDeepCompareMemoize<T extends Record<any, any> | undefined>(value: T) {
const ref = React.useRef<T>(value);
const signalRef = React.useRef<number>(0);
if (!isEqual(value, ref.current)) {
ref.current = value;
signalRef.current += 1;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
return React.useMemo(() => ref.current, [signalRef.current]);
}
export function useColors(...colorNames: (string | ColorDef)[]) {
const { getThemeVar } = useTheme();
// const paramsRef = useRef(colorNames);
// const { themeStyles } = useTheme();
const colors = useMemo(() => {
const ret: Record<string, string> = {};
for (const color of colorNames) {
if (typeof color === "string") {
const col = getThemeVar(color);
ret[color] = Color(col).toString();
} else {
const col = getThemeVar(color.name);
ret[color.name] = Color(col).hex().toString();
}
}
return ret;
}, [colorNames, getThemeVar]);
// useEffect(() => {
// setColors(getColors(...paramsRef.current));
// }, [themeStyles]);
return colors;
}
export function useReferenceTrackedApi(componentState: ContainerState) {
return useShallowCompareMemoize(
useMemo(() => {
const ret: Record<string, ComponentApi> = {};
if (Reflect.ownKeys(componentState).length === 0) {
//skip containers with no registered apis
return EMPTY_OBJECT;
}
for (const componentApiKey of Object.getOwnPropertySymbols(componentState)) {
const value = componentState[componentApiKey];
if (componentApiKey.description) {
ret[componentApiKey.description] = value;
}
}
return ret;
}, [componentState]),
);
}
/**
* This hook uses either useLayoutEffect or useEffect based on the environment
* (client-side or server-side).
*/
export const useIsomorphicLayoutEffect =
typeof document !== "undefined" ? useLayoutEffect : useEffect;
// https://stackoverflow.com/a/49186677
function getScrollParent(element: HTMLElement) {
let style = getComputedStyle(element);
const excludeStaticParent = style.position === "absolute";
const overflowRegex = /(auto|scroll)/;
if (style.position === "fixed") {
return document.body;
}
for (let parent = element; ; parent = parent.parentElement) {
if (!parent) {
return null;
}
style = getComputedStyle(parent);
if (excludeStaticParent && style.position === "static") {
continue;
}
if (overflowRegex.test(style.overflow + style.overflowY + style.overflowX)) {
return parent;
}
}
return document.body;
}
export const useScrollParent = (element?: HTMLElement): HTMLElement => {
const [scrollParent, setScrollParent] = useState<HTMLElement | null>(null);
useIsomorphicLayoutEffect(() => {
setScrollParent(element ? getScrollParent(element) : null);
}, [element]);
return scrollParent;
};
// because safari doesn't support scrollend event...
export const useScrollEventHandler = (
element: HTMLElement | null | undefined,
{
onScrollStart,
onScrollEnd,
}: {
onScrollStart?: () => void;
onScrollEnd?: () => void;
},
) => {
const thisRef = useRef({ scrolling: false });
useIsomorphicLayoutEffect(() => {
let timer;
let listener = () => {
if (!thisRef.current.scrolling) {
onScrollStart?.();
}
thisRef.current.scrolling = true;
clearTimeout(timer);
timer = setTimeout(() => {
thisRef.current.scrolling = false;
onScrollEnd?.();
}, 50);
};
element?.addEventListener("scroll", listener);
return () => {
element?.removeEventListener("scroll", listener);
};
}, [element, onScrollEnd, onScrollStart]);
};
function realBackgroundColor(elem: HTMLElement) {
let transparent = "rgba(0, 0, 0, 0)";
let transparentIE11 = "transparent";
if (!elem) return transparent;
let bg = getComputedStyle(elem).backgroundColor;
if (bg === transparent || bg === transparentIE11) {
return realBackgroundColor(elem.parentElement);
} else {
return bg;
}
}
export const useRealBackground = (element: HTMLElement) => {
const { activeThemeTone, activeThemeId } = useTheme();
const [counter, setCounter] = useState(0);
useEffect(() => {
return setCounter((prev) => prev + 1);
}, [activeThemeTone, activeThemeId]);
return useMemo(() => (element ? realBackgroundColor(element) : "transparent"), [element]);
};
// export const useIsInViewport = (ref, observerOptions) => {
// const [entered, setEntered] = useState(false);
// const observer = useRef(
// new IntersectionObserver(
// ([entry]) => setEntered(entry.isIntersecting),
// observerOptions
// )
// );
//
// useEffect(() => {
// const element = ref.current;
// const ob = observer.current;
//
// // stop observing once the element has entered the viewport for the first time.
// // if (entered) {
// // ob.disconnect();
// // return;
// // }
//
// if (element) ob.observe(element);
//
// return () => ob.disconnect();
// }, [entered, ref]);
//
// return entered;
// };
export const useStartMargin = (
hasOutsideScroll: boolean,
parentRef: MutableRefObject<HTMLElement | null | undefined>,
scrollRef: MutableRefObject<HTMLElement | null | undefined>,
) => {
const [startMargin, setStartMargin] = useState<number>(0);
const calculateStartMargin = useEvent(() => {
if (!hasOutsideScroll) {
return 0;
}
const precedingElement = parentRef.current;
const scrollContainer = scrollRef.current;
if (precedingElement && scrollContainer) {
const precedingRect = precedingElement.getBoundingClientRect();
const scrollContainerRect = scrollContainer.getBoundingClientRect();
// It's important that scrollContainer is indeed the element whose scrollTop is relevant
const scrollTopOfScrollContainer = scrollContainer.scrollTop;
const calculatedMargin =
precedingRect.top - scrollContainerRect.top + scrollTopOfScrollContainer;
return calculatedMargin;
}
return 0;
});
useResizeObserver(scrollRef, () => {
setStartMargin(calculateStartMargin());
});
return startMargin;
};
export function useHasExplicitHeight(parentRef: React.MutableRefObject<HTMLDivElement | null>) {
const [hasHeight, setHasHeight] = useState(false);
useLayoutEffect(() => {
if (parentRef.current) {
const computedStyles = window.getComputedStyle(parentRef.current);
const hasMaxHeight = computedStyles.maxHeight !== "none";
// Get the original computed height
const originalHeight = window.getComputedStyle(parentRef.current).height;
// Store original inline style to restore it later
const originalInlineHeight = parentRef.current.style.height || "";
// Temporarily set height to auto and get the new computed height
parentRef.current.style.height = "auto";
const autoHeight = window.getComputedStyle(parentRef.current).height;
// Restore the original inline style immediately
parentRef.current.style.height = originalInlineHeight;
// If the original height is different from what the browser
// calculates for 'auto', it means a height was explicitly set.
// We also check if the inline style itself had a value.
let hasHeight = originalHeight !== autoHeight || !!originalInlineHeight;
const isFlex = computedStyles.display === "flex";
setHasHeight(hasMaxHeight || hasHeight || isFlex);
}
}, [parentRef]);
return hasHeight;
}
```
--------------------------------------------------------------------------------
/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;
};
```