This is page 20 of 188. Use http://codebase.md/xmlui-org/xmlui/tools/vscode/resources/assets/img/%7BimageSrc%7D?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .changeset
│ ├── config.json
│ ├── cyan-tools-design.md
│ ├── every-moments-teach.md
│ ├── full-symbols-accept.md
│ └── tricky-zoos-crash.md
├── .eslintrc.cjs
├── .github
│ ├── build-checklist.png
│ ├── ISSUE_TEMPLATE
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ └── workflows
│ ├── deploy-blog-optimized.yml
│ ├── deploy-blog-swa.yml
│ ├── deploy-blog.yml
│ ├── deploy-docs-optimized.yml
│ ├── deploy-docs-swa.yml
│ ├── deploy-docs.yml
│ ├── prepare-versions.yml
│ ├── release-packages.yml
│ ├── run-all-tests.yml
│ └── run-smoke-tests.yml
├── .gitignore
├── .prettierrc.js
├── .vscode
│ ├── launch.json
│ └── settings.json
├── blog
│ ├── .gitignore
│ ├── .gitkeep
│ ├── CHANGELOG.md
│ ├── extensions.ts
│ ├── index.html
│ ├── index.ts
│ ├── package.json
│ ├── public
│ │ ├── blog
│ │ │ ├── images
│ │ │ │ ├── an-advanced-codefence.gif
│ │ │ │ ├── an-advanced-codefence.mp4
│ │ │ │ ├── blog-page-component.png
│ │ │ │ ├── blog-scrabble.png
│ │ │ │ ├── codefence-runner.png
│ │ │ │ ├── integrated-blog-search.png
│ │ │ │ ├── lorem-ipsum.png
│ │ │ │ ├── playground-checkbox-source.png
│ │ │ │ ├── playground.png
│ │ │ │ ├── use-xmlui-mcp-to-find-a-howto.png
│ │ │ │ └── xmlui-demo-gallery.png
│ │ │ ├── introducing-xmlui.md
│ │ │ ├── lorem-ipsum.md
│ │ │ ├── newest-post.md
│ │ │ ├── older-post.md
│ │ │ ├── xmlui-playground.md
│ │ │ └── xmlui-powered-blog.md
│ │ ├── mockServiceWorker.js
│ │ ├── resources
│ │ │ ├── favicon.ico
│ │ │ ├── files
│ │ │ │ └── for-download
│ │ │ │ └── xmlui
│ │ │ │ └── xmlui-standalone.umd.js
│ │ │ ├── github.svg
│ │ │ ├── llms.txt
│ │ │ ├── logo-dark.svg
│ │ │ ├── logo.svg
│ │ │ ├── pg-popout.svg
│ │ │ ├── rss.svg
│ │ │ └── xmlui-logo.svg
│ │ ├── serve.json
│ │ ├── staticwebapp.config.json
│ │ └── web.config
│ ├── scripts
│ │ ├── download-latest-xmlui.js
│ │ ├── generate-rss.js
│ │ ├── get-releases.js
│ │ └── utils.js
│ ├── src
│ │ ├── components
│ │ │ ├── BlogOverview.xmlui
│ │ │ ├── BlogPage.xmlui
│ │ │ └── PageNotFound.xmlui
│ │ ├── config.ts
│ │ ├── Main.xmlui
│ │ └── themes
│ │ └── blog-theme.ts
│ └── tsconfig.json
├── CONTRIBUTING.md
├── docs
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── ComponentRefLinks.txt
│ ├── content
│ │ ├── _meta.json
│ │ ├── components
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── APICall.md
│ │ │ ├── App.md
│ │ │ ├── AppHeader.md
│ │ │ ├── AppState.md
│ │ │ ├── AutoComplete.md
│ │ │ ├── Avatar.md
│ │ │ ├── Backdrop.md
│ │ │ ├── Badge.md
│ │ │ ├── BarChart.md
│ │ │ ├── Bookmark.md
│ │ │ ├── Breakout.md
│ │ │ ├── Button.md
│ │ │ ├── Card.md
│ │ │ ├── Carousel.md
│ │ │ ├── ChangeListener.md
│ │ │ ├── Checkbox.md
│ │ │ ├── CHStack.md
│ │ │ ├── ColorPicker.md
│ │ │ ├── Column.md
│ │ │ ├── ContentSeparator.md
│ │ │ ├── CVStack.md
│ │ │ ├── DataSource.md
│ │ │ ├── DateInput.md
│ │ │ ├── DatePicker.md
│ │ │ ├── DonutChart.md
│ │ │ ├── DropdownMenu.md
│ │ │ ├── EmojiSelector.md
│ │ │ ├── ExpandableItem.md
│ │ │ ├── FileInput.md
│ │ │ ├── FileUploadDropZone.md
│ │ │ ├── FlowLayout.md
│ │ │ ├── Footer.md
│ │ │ ├── Form.md
│ │ │ ├── FormItem.md
│ │ │ ├── FormSection.md
│ │ │ ├── Fragment.md
│ │ │ ├── H1.md
│ │ │ ├── H2.md
│ │ │ ├── H3.md
│ │ │ ├── H4.md
│ │ │ ├── H5.md
│ │ │ ├── H6.md
│ │ │ ├── Heading.md
│ │ │ ├── HSplitter.md
│ │ │ ├── HStack.md
│ │ │ ├── Icon.md
│ │ │ ├── IFrame.md
│ │ │ ├── Image.md
│ │ │ ├── Items.md
│ │ │ ├── LabelList.md
│ │ │ ├── Legend.md
│ │ │ ├── LineChart.md
│ │ │ ├── Link.md
│ │ │ ├── List.md
│ │ │ ├── Logo.md
│ │ │ ├── Markdown.md
│ │ │ ├── MenuItem.md
│ │ │ ├── MenuSeparator.md
│ │ │ ├── ModalDialog.md
│ │ │ ├── NavGroup.md
│ │ │ ├── NavLink.md
│ │ │ ├── NavPanel.md
│ │ │ ├── NoResult.md
│ │ │ ├── NumberBox.md
│ │ │ ├── Option.md
│ │ │ ├── Page.md
│ │ │ ├── PageMetaTitle.md
│ │ │ ├── Pages.md
│ │ │ ├── Pagination.md
│ │ │ ├── PasswordInput.md
│ │ │ ├── PieChart.md
│ │ │ ├── ProgressBar.md
│ │ │ ├── Queue.md
│ │ │ ├── RadioGroup.md
│ │ │ ├── RealTimeAdapter.md
│ │ │ ├── Redirect.md
│ │ │ ├── Select.md
│ │ │ ├── Slider.md
│ │ │ ├── Slot.md
│ │ │ ├── SpaceFiller.md
│ │ │ ├── Spinner.md
│ │ │ ├── Splitter.md
│ │ │ ├── Stack.md
│ │ │ ├── StickyBox.md
│ │ │ ├── SubMenuItem.md
│ │ │ ├── Switch.md
│ │ │ ├── TabItem.md
│ │ │ ├── Table.md
│ │ │ ├── TableOfContents.md
│ │ │ ├── Tabs.md
│ │ │ ├── Text.md
│ │ │ ├── TextArea.md
│ │ │ ├── TextBox.md
│ │ │ ├── Theme.md
│ │ │ ├── TimeInput.md
│ │ │ ├── Timer.md
│ │ │ ├── ToneChangerButton.md
│ │ │ ├── ToneSwitch.md
│ │ │ ├── Tooltip.md
│ │ │ ├── Tree.md
│ │ │ ├── VSplitter.md
│ │ │ ├── VStack.md
│ │ │ ├── xmlui-animations
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── Animation.md
│ │ │ │ ├── FadeAnimation.md
│ │ │ │ ├── FadeInAnimation.md
│ │ │ │ ├── FadeOutAnimation.md
│ │ │ │ ├── ScaleAnimation.md
│ │ │ │ └── SlideInAnimation.md
│ │ │ ├── xmlui-pdf
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Pdf.md
│ │ │ ├── xmlui-spreadsheet
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Spreadsheet.md
│ │ │ └── xmlui-website-blocks
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── Carousel.md
│ │ │ ├── HelloMd.md
│ │ │ ├── HeroSection.md
│ │ │ └── ScrollToTop.md
│ │ └── extensions
│ │ ├── _meta.json
│ │ ├── xmlui-animations
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── Animation.md
│ │ │ ├── FadeAnimation.md
│ │ │ ├── FadeInAnimation.md
│ │ │ ├── FadeOutAnimation.md
│ │ │ ├── ScaleAnimation.md
│ │ │ └── SlideInAnimation.md
│ │ └── xmlui-website-blocks
│ │ ├── _meta.json
│ │ ├── _overview.md
│ │ ├── Carousel.md
│ │ ├── 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
│ │ ├── 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
--------------------------------------------------------------------------------
/docs/public/pages/howto/chain-a-refetch.md:
--------------------------------------------------------------------------------
```markdown
1 | # Chain a DataSource refetch from an APICall.execute
2 |
3 | `APICall.execute` returns a Promise, you can call `.then` to do something else.
4 |
5 | ```xmlui-pg copy display {54} name="Click the Like button"
6 | ---comp display
7 | <Component name="SocialButton">
8 | <Button
9 | borderRadius="50%"
10 | icon="{$props.icon}"
11 | variant="outlined"
12 | themeColor="{$props.themeColor || 'secondary'}"
13 | size="xs"
14 | onClick="{emitEvent('click')}" />
15 | </Component>
16 | ---app display
17 | <App>
18 | <APICall
19 | id="favoritePost"
20 | method="post"
21 | url="/api/posts/{$param}/favorite" />
22 | <APICall
23 | id="unfavoritePost"
24 | method="post"
25 | url="/api/posts/{$param}/unfavorite" />
26 | <DataSource
27 | id="timelineData"
28 | url="/api/timeline"
29 | method="GET" />
30 | <VStack gap="$space-4" padding="$space-4">
31 | <Text variant="h3">Social Media Timeline</Text>
32 | <Items data="{timelineData}">
33 | <Card padding="$space-3" marginBottom="$space-2">
34 | <VStack gap="$space-2">
35 | <Text variant="h6">{$item.author}</Text>
36 | <Text>{$item.content}</Text>
37 | <HStack gap="$space-4" verticalAlignment="center">
38 | <HStack gap="$space-1" verticalAlignment="center">
39 | <SocialButton icon="reply" />
40 | <Text variant="caption">{$item.replies_count}</Text>
41 | </HStack>
42 | <HStack gap="$space-1" verticalAlignment="center">
43 | <SocialButton icon="trending-up" />
44 | <Text variant="caption">{$item.reblogs_count}</Text>
45 | </HStack>
46 | <HStack gap="$space-1" verticalAlignment="center">
47 | <SocialButton
48 | icon="like"
49 | themeColor="{$item.favourited ? 'attention' : 'secondary'}">
50 | <event name="click">
51 | if ($item.favourited) {
52 | // execute returns a Promise
53 | unfavoritePost.execute($item.id).then(() => timelineData.refetch());
54 | } else {
55 | favoritePost.execute($item.id).then(() => timelineData.refetch());
56 | }
57 | </event>
58 | </SocialButton>
59 | <Text variant="caption">{$item.favourites_count}</Text>
60 | </HStack>
61 | </HStack>
62 | </VStack>
63 | </Card>
64 | </Items>
65 | </VStack>
66 | </App>
67 | ---api
68 | {
69 | "apiUrl": "/api",
70 | "initialize": "$state.posts = [
71 | {
72 | id: '1',
73 | content: 'This is a great post about XMLUI!',
74 | author: 'John Developer',
75 | favourited: false,
76 | favourites_count: 5,
77 | replies_count: 2,
78 | reblogs_count: 1
79 | },
80 | {
81 | id: '2',
82 | content: 'Learning how to chain API calls is so useful.',
83 | author: 'Jane Designer',
84 | favourited: true,
85 | favourites_count: 12,
86 | replies_count: 4,
87 | reblogs_count: 3
88 | }
89 | ]",
90 | "operations": {
91 | "get-timeline": {
92 | "url": "/timeline",
93 | "method": "get",
94 | "handler": "return $state.posts"
95 | },
96 | "favorite-post": {
97 | "url": "/posts/:id/favorite",
98 | "method": "post",
99 | "pathParamTypes": {
100 | "id": "string"
101 | },
102 | "handler": "
103 | const post = $state.posts.find(p => p.id === $pathParams.id);
104 | if (post) {
105 | post.favourited = true;
106 | post.favourites_count += 1;
107 | }
108 | "
109 | },
110 | "unfavorite-post": {
111 | "url": "/posts/:id/unfavorite",
112 | "method": "post",
113 | "pathParamTypes": {
114 | "id": "string"
115 | },
116 | "handler": "
117 | const post = $state.posts.find(p => p.id === $pathParams.id);
118 | if (post) {
119 | post.favourited = false;
120 | post.favourites_count -= 1;
121 | }
122 | "
123 | }
124 | }
125 | }
126 | ```
127 |
```
--------------------------------------------------------------------------------
/xmlui/tests/components-core/scripts-runner/eval-tree-arrow.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { describe, expect, it } from "vitest";
2 |
3 | import { evalBindingExpression } from "../../../src/components-core/script-runner/eval-tree-sync";
4 | import {createEvalContext} from "./test-helpers";
5 |
6 | describe("Evaluate arrow expressions (exp)", () => {
7 | it("Arrow #1", () => {
8 | // --- Arrange
9 | const source = "(x => 2 * x)(4)";
10 | const context = createEvalContext({});
11 |
12 | // --- Act
13 | const value = evalBindingExpression(source, context);
14 |
15 | // --- Arrange
16 | expect(value).equal(8);
17 | });
18 |
19 | it("Arrow #2", () => {
20 | // --- Arrange
21 | const source = "((x, y) => x + y)(1, 2)";
22 | const context = createEvalContext({});
23 |
24 | // --- Act
25 | const value = evalBindingExpression(source, context);
26 |
27 | // --- Arrange
28 | expect(value).equal(3);
29 | });
30 |
31 | it("Arrow #3", () => {
32 | // --- Arrange
33 | const source = "((x, y) => { return x + y })(1, 2)";
34 | const context = createEvalContext({});
35 |
36 | // --- Act
37 | const value = evalBindingExpression(source, context);
38 |
39 | // --- Arrange
40 | expect(value).equal(3);
41 | });
42 |
43 | it("Arrow #4", () => {
44 | // --- Arrange
45 | const source = "(x => (++x.h))(count)";
46 | const context = createEvalContext({
47 | localContext: {
48 | count: { h: 3 }
49 | }
50 | });
51 |
52 | // --- Act
53 | const value = evalBindingExpression(source, context);
54 |
55 | // --- Arrange
56 | expect(value).equal(4);
57 | });
58 |
59 | it("Arrow #5", () => {
60 | // --- Arrange
61 | const source = "(x => x += 2)(count)";
62 | const context = createEvalContext({
63 | localContext: {
64 | count: 3
65 | }
66 | });
67 |
68 | // --- Act
69 | const value = evalBindingExpression(source, context);
70 |
71 | // --- Arrange
72 | expect(value).equal(5);
73 | });
74 |
75 | it("Arrow #6", () => {
76 | // --- Arrange
77 | const source = "(x => x += 2)(count + 4)";
78 | const context = createEvalContext({
79 | localContext: {
80 | count: 3
81 | }
82 | });
83 |
84 | // --- Act
85 | const value = evalBindingExpression(source, context);
86 |
87 | // --- Arrange
88 | expect(value).equal(9);
89 | });
90 |
91 | it("Arrow #7", () => {
92 | // --- Arrange
93 | const source = "[1,2,3,4,5].filter(x => x % 2 === 0)[1]";
94 | const context = createEvalContext({
95 | localContext: {
96 | count: 3
97 | }
98 | });
99 |
100 | // --- Act
101 | const value = evalBindingExpression(source, context);
102 |
103 | // --- Arrange
104 | expect(value).equal(4);
105 | });
106 |
107 | it("Arrow #8", () => {
108 | // --- Arrange
109 | const source = "containsArray.array.filter(item => item % 2 === 0)[1]";
110 | const context = createEvalContext({
111 | localContext: {
112 | containsArray: {
113 | array: [5, 4, 3, 2, 1]
114 | }
115 | }
116 | });
117 |
118 | // --- Act
119 | const value = evalBindingExpression(source, context);
120 |
121 | // --- Arrange
122 | expect(value).equal(2);
123 | });
124 |
125 | it("Arrow #9", () => {
126 | // --- Arrange
127 | const source = "array.reduce((acc, item) => acc + item, 0)";
128 | const context = createEvalContext({
129 | localContext: {
130 | array: [5, 4, 3, 2, 1]
131 | }
132 | });
133 |
134 | // --- Act
135 | const value = evalBindingExpression(source, context);
136 |
137 | // --- Arrange
138 | expect(value).equal(15);
139 | });
140 |
141 | it("Arrow with rest #1", () => {
142 | // --- Arrange
143 | const source = "((...a) => a[0] + a[1])(1, 2)";
144 | const context = createEvalContext({});
145 |
146 | // --- Act
147 | const value = evalBindingExpression(source, context);
148 |
149 | // --- Arrange
150 | expect(value).equal(3);
151 | });
152 |
153 | it("Arrow with rest #2", () => {
154 | // --- Arrange
155 | const source = "((x, ...a) => x + a[0] + a[1])(1, 2, 3)";
156 | const context = createEvalContext({});
157 |
158 | // --- Act
159 | const value = evalBindingExpression(source, context);
160 |
161 | // --- Arrange
162 | expect(value).equal(6);
163 | });
164 | });
165 |
```
--------------------------------------------------------------------------------
/xmlui/src/components/AppHeader/AppHeader.module.scss:
--------------------------------------------------------------------------------
```scss
1 | @use "../../components-core/theming/themes" as t;
2 |
3 | // --- This code snippet is required to collect the theme variables used in this module
4 | $component: "AppHeader";
5 | $themeVars: ();
6 | @function createThemeVar($componentVariable) {
7 | $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global;
8 | @return t.getThemeVar($themeVars, $componentVariable);
9 | }
10 |
11 | // --- Theme vars for paddings and borders
12 | $themeVars: t.composePaddingVars($themeVars, $component);
13 | $themeVars: t.composePaddingVars($themeVars, "logo-#{$component}");
14 | $themeVars: t.composeBorderVars($themeVars, $component);
15 |
16 | // --- Other
17 | $width-logo-AppHeader: createThemeVar("width-logo-#{$component}");
18 | $alignment-content-AppHeader: createThemeVar("alignment-content-#{$component}");
19 |
20 | // Variables for @layer section
21 | $height-AppHeader: createThemeVar("height-#{$component}");
22 | $backgroundColor-AppHeader: createThemeVar("backgroundColor-#{$component}");
23 | $maxWidth-content-AppHeader: createThemeVar("maxWidth-content-#{$component}");
24 | $maxWidth-AppHeader: createThemeVar("maxWidth-#{$component}");
25 | $padding-drawerToggle-AppHeader: createThemeVar("padding-drawerToggle-#{$component}");
26 |
27 | @layer components {
28 | .header {
29 | position: relative;
30 | height: $height-AppHeader;
31 | box-sizing: content-box;
32 | background-color: $backgroundColor-AppHeader;
33 | @include t.borderVars($themeVars, $component);
34 | }
35 |
36 | .headerInner {
37 | height: 100%;
38 | flex: 1;
39 | gap: t.$space-4;
40 | flex-shrink: 0;
41 | display: flex;
42 | flex-direction: row;
43 | align-items: center;
44 | max-width: $maxWidth-content-AppHeader;
45 | padding-inline: t.getThemeVar($themeVars, "paddingHorizontal-#{$component}");
46 | @include t.paddingVars($themeVars, $component);
47 |
48 | &.full {
49 | max-width: $maxWidth-AppHeader;
50 | }
51 | width: 100%;
52 | margin: 0 auto;
53 | }
54 |
55 | .childrenWrapper {
56 | --stack-gap-default: #{t.$space-2};
57 | display: flex;
58 | flex-direction: row;
59 | flex: 1;
60 | min-width: 0;
61 | height: 100%;
62 | align-items: center;
63 | gap: var(--stack-gap-default);
64 | justify-content: $alignment-content-AppHeader;
65 | }
66 |
67 | .subNavPanelSlot{
68 | display: flex;
69 | flex-direction: row;
70 | }
71 |
72 | .logoAndTitle {
73 | display: flex;
74 | align-items: center;
75 | gap: t.$space-4;
76 | height: 100%;
77 |
78 | &:not(:empty) {
79 | padding-right: t.$space-2;
80 | }
81 |
82 | &:empty {
83 | display: none;
84 | }
85 | }
86 |
87 | .logoContainer:not(:empty) {
88 | flex-shrink: 0;
89 | display: flex;
90 | width: $width-logo-AppHeader;
91 | height: 100%;
92 | align-items: center;
93 | @include t.paddingVars($themeVars, "logo-#{$component}");
94 | }
95 |
96 | .customLogoContainer {
97 | display: flex;
98 | height: 100%;
99 | align-items: center;
100 |
101 | & > img {
102 | height: 100%;
103 | }
104 | }
105 |
106 | .rightItems {
107 | --stack-gap-default: #{t.$space-2};
108 | gap: var(--stack-gap-default);
109 | height: 100%;
110 |
111 | &:not(:empty) {
112 | padding-left: t.$space-4;
113 | }
114 |
115 | display: flex;
116 | flex-direction: row;
117 | align-items: center;
118 | }
119 |
120 | .appHub {
121 | text-decoration: none;
122 | margin-right: t.$space-4;
123 | width: 40px;
124 | padding: t.$space-2;
125 | color: t.$textColor-subtitle;
126 | cursor: pointer;
127 |
128 | &:hover {
129 | color: t.$textColor-secondary;
130 | }
131 |
132 | svg {
133 | width: 100%;
134 | height: 100%;
135 | }
136 | }
137 |
138 | .drawerToggle.drawerToggle{
139 | padding: $padding-drawerToggle-AppHeader !important;
140 | display: none;
141 | @include t.withMaxScreenSize(0) {
142 | display: block;
143 | }
144 | }
145 |
146 | .logoLink{
147 | padding: 0;
148 | height: 100%;
149 | &>:first-child {
150 | height: 100%;
151 | }
152 | }
153 | }
154 |
155 | // --- We export the theme variables to add them to the component renderer
156 | :export {
157 | themeVars: t.json-stringify($themeVars);
158 | }
159 |
```
--------------------------------------------------------------------------------
/xmlui/src/components/Markdown/Markdown.md:
--------------------------------------------------------------------------------
```markdown
1 | %-DESC-START
2 |
3 | **Key features:**
4 | - **Rich formatting**: Support for headings, bold, italic, lists, links, images, blockquotes, and code blocks
5 | - **Dynamic content**: Use @{} binding expressions to inject variables and function results
6 | - **File loading**: Load markdown content from external files using the `data` property
7 |
8 | ## Acquiring content
9 |
10 | You can specify Markdown content in these ways.
11 |
12 | ### The content property
13 |
14 | Render Markdown content that you calculate or get from other components.
15 |
16 | ### The data property
17 |
18 | Render Markdown content from an URL.
19 |
20 | ### Nested text
21 |
22 | Render Markdown content that you place directly in a Markdown component.
23 |
24 | ## Whitespace and special characters
25 |
26 | Whitespace is significant in Markdown, for example headers using the `#` syntax must begin in column 1.
27 |
28 | These special XML characters are significant too.
29 |
30 | ```
31 | < (less than) - Must be escaped as <
32 | > (greater than) - Must be escaped as >
33 | & (ampersand) - Must be escaped as &
34 | " (double quote) - Must be escaped as " in attributes
35 | ' (single quote/apostrophe) - Must be escaped as ' in attributes
36 | ```
37 |
38 | You can use a CDATA section to avoid having to escape these characters individually.
39 |
40 | ```
41 | <Markdown>
42 | <![CDATA[
43 | ]]>
44 | </Markdown>
45 | ```
46 |
47 | Or, as we have done in this page, you can use a code fence (a block delimited by triple backtics) to preserve them.
48 |
49 | ## Supported elements
50 |
51 | The `Markdown` component supports these basic elements.
52 |
53 | - Heading
54 | - Bold
55 | - Italic
56 | - Strikethrough
57 | - Blockquote
58 | - Ordered List
59 | - Unordered List
60 | - Code
61 | - Horizontal Rule
62 | - Link
63 | - Image
64 | - Table
65 |
66 | See [this markdown guide](https://www.markdownguide.org/cheat-sheet/).
67 |
68 | ## Binding Expressions
69 |
70 | Our `Markdown` component is capable of evaluating binding expressions just as other XMLUI components.
71 | Use the @{} syntax to wrap expressions that need to be evaluated.
72 |
73 | Objects, functions and arrays will be stringified if you place them in `Markdown`.
74 |
75 | Function calls are executed and their return values inlined as strings into markdown.
76 |
77 | ```xmlui-pg copy {5-9} name="Example: binding expressions syntax"
78 | <App>
79 | <variable name="x" value="{() => { return 'testing' }}" />
80 | <Markdown>
81 | <![CDATA[
82 | Empty elements are removed: @{}
83 |
84 | Nested objects and functions are handled: @{ { a: 1, b: () => {} } }
85 |
86 | Function calls are executed: @{x()}
87 | ]]>
88 | </Markdown>
89 | </App>
90 | ```
91 |
92 | %-DESC-END
93 |
94 | %-STYLE-START
95 | The component itself cannot be styled, but the components that render the final text have customizable style variables.
96 |
97 | [`Text`](/components/Text#styling)
98 | [`Heading`](/components/Heading#styling)
99 | [`Link`](/components/Link#styling)
100 | [`Image`](/components/Image#styling)
101 | [`Checkbox`](/components/Checkbox#styling)
102 |
103 |
104 | %-STYLE-END
105 |
106 | %-PROP-START content
107 |
108 | Use this property when the text you provide is not static but a result of calculations (you assemble the text or get it from other components).
109 |
110 |
111 | %-PROP-END
112 |
113 | %-PROP-START removeIndents
114 |
115 | ```xmlui-pg copy display name="Example: removeIndents property"
116 | <App layout="horizontal-sticky" padding="1rem">
117 | <Markdown removeIndents="true">
118 | <![CDATA[
119 | # My Adventure in Markdown Land
120 |
121 | ## The Beginning
122 |
123 | In the bustling city of Markdownville, I embarked on a journey to
124 | discover the secrets of Markdown. My adventure started in the heart
125 | of the city, where the first rule of Markdown was inscribed in stone.
126 | ]]>
127 | </Markdown>
128 | </App>
129 | ```
130 |
131 | %-PROP-END
132 |
133 | %-PROP-START showHeadingAnchors
134 |
135 | If this property is not set, the engine checks if `showHeadingAnchors` flag is turned on in the global configuration (in the `appGlobals` configuration object) and displays the heading anchor accordingly.
136 |
137 | %-PROP-END
```
--------------------------------------------------------------------------------
/docs/content/components/FileUploadDropZone.md:
--------------------------------------------------------------------------------
```markdown
1 | # FileUploadDropZone [#fileuploaddropzone]
2 |
3 | `FileUploadDropZone` enables users to upload files by dragging and dropping files from their local file system onto a designated area within the UI.
4 |
5 | ## Using `FileUploadDropZone` [#using-fileuploaddropzone]
6 |
7 | The component provides a surface on which you can drag files or paste files from the clipboard. The following example demonstrates how to use the component.
8 |
9 | ```xmlui-pg copy display name="Example: using FileUploadDropZone" height="200px"
10 | ---app copy display
11 | <App>
12 | <H3>The cyan area below is a FileUploadDropZone</H3>
13 | <FileUploadDropZone backgroundColor="cyan" height="100px"
14 | onUpload="
15 | (files) => {
16 | console.log(files);
17 | files.map(file => toast('file ' + file.path + ' uploaded'))}" />
18 | </App>
19 | ---desc
20 | You can try it by dragging one or more files to the cyan surface. When you drop the file(s), the app triggers the `upload` event and displays a dialog for each file.
21 |
22 | You can also paste files from the clipboard: click the drop zone (cyan area) and then use the keyboard shortcut set on your OS.
23 | ```
24 |
25 | ## Properties [#properties]
26 |
27 | ### `acceptedFileTypes` [#acceptedfiletypes]
28 |
29 | Accepted file MIME types, separated by commas. For example: 'image/*,application/pdf'.
30 |
31 | ### `allowPaste` (default: true) [#allowpaste-default-true]
32 |
33 | This property indicates if the drop zone accepts files pasted from the clipboard (`true`) or only dragged files (`false`).
34 |
35 | This property indicates if the drop zone accepts files pasted from the clipboard (`true`) or only dragged files (`false`).
36 |
37 | The following example sets this property to `false` and, thus, it turns off pasting files:
38 |
39 | ```xmlui-pg copy display name="Example: allowPaste" height="200px"
40 | ---app copy display
41 | <App>
42 | <H3>You cannot paste files from the clipboard</H3>
43 | <FileUploadDropZone backgroundColor="cyan" height="100px"
44 | allowPaste="false"
45 | onUpload="(files) => files.map(file => toast('file ' + file.path + ' uploaded'))" />
46 | </App>
47 | ---desc
48 | Try it! When you copy a file to a clipboard, you cannot paste it with the keyboard shortcut of your OS.
49 | ```
50 |
51 | ### `enabled` (default: true) [#enabled-default-true]
52 |
53 | If set to `false`, the drop zone will be disabled and users will not be able to upload files.
54 |
55 | ### `maxFiles` [#maxfiles]
56 |
57 | The maximum number of files that can be selected.
58 |
59 | ### `text` (default: "Drop files here") [#text-default-drop-files-here]
60 |
61 | With this property, you can change the default text to display when files are dragged over the drop zone.
62 |
63 | ## Events [#events]
64 |
65 | ### `upload` [#upload]
66 |
67 | This component accepts files for upload but does not perform the actual operation. It fires the `upload` event and passes the list files to upload in the method's argument. You can use the passed file information to implement the upload (according to the protocol your backend supports).
68 |
69 | Each item passed in the event argument is an instance of [File](https://developer.mozilla.org/en-US/docs/Web/API/File).
70 |
71 | ## Exposed Methods [#exposed-methods]
72 |
73 | This component does not expose any methods.
74 |
75 | ## Styling [#styling]
76 |
77 | ### Theme Variables [#theme-variables]
78 |
79 | | Variable | Default Value (Light) | Default Value (Dark) |
80 | | --- | --- | --- |
81 | | [backgroundColor](../styles-and-themes/common-units/#color)-dropping-FileUploadDropZone | $backgroundColor--selected | $backgroundColor--selected |
82 | | [backgroundColor](../styles-and-themes/common-units/#color)-FileUploadDropZone | $backgroundColor | $backgroundColor |
83 | | [opacity](../styles-and-themes/common-units/#opacity)-dropping-FileUploadDropZone | 0.5 | 0.5 |
84 | | [textColor](../styles-and-themes/common-units/#color)-FileUploadDropZone | $textColor | $textColor |
85 |
```
--------------------------------------------------------------------------------
/xmlui/src/components-core/script-runner/BindingTreeEvaluationContext.ts:
--------------------------------------------------------------------------------
```typescript
1 | import type { LogicalThread } from "../../abstractions/scripting/LogicalThread";
2 | import type { ActionExecutionContext } from "../../abstractions/ActionDefs";
3 | import type { ArrowExpression, Statement } from "./ScriptingSourceTree";
4 | import type { BlockScope } from "../../abstractions/scripting/BlockScope";
5 |
6 | /**
7 | * A function that resolves a module name to the text of the module
8 | */
9 | export type ModuleResolver = (sourceModule: string, moduleName: string) => string | null;
10 |
11 | // This type represents the context in which binding expressions and statements should be evaluated
12 | export type BindingTreeEvaluationContext = {
13 | // --- Container scope
14 | localContext?: any;
15 |
16 | // --- Function to obtain the current working copy of the local context
17 | getLocalContext?: () => any;
18 |
19 | // --- Application context scope
20 | appContext?: any;
21 |
22 | // --- The main execution thread;
23 | mainThread?: LogicalThread;
24 |
25 | // --- The cancellation token to signal the cancellation of an operation
26 | cancellationToken?: CancellationToken;
27 |
28 | // --- Execution timeout in milliseconds
29 | timeout?: number;
30 |
31 | // --- Evaluation options
32 | options?: EvalTreeOptions;
33 |
34 | // --- Start time of the synchronous statement processing
35 | startTick?: number;
36 |
37 | // --- The values of event arguments to process in an ArrowExpressionStatement
38 | eventArgs?: any[];
39 |
40 | // --- Cached closure contexts for arrow expressions
41 | closureContexts?: Map<ArrowExpression, BlockScope[]>
42 |
43 | // --- Use this context wrapper with function that support implicit context
44 | implicitContextGetter?: ImplicitContextGetter;
45 |
46 | // --- Function to call on updating a localContext property (directly or indirectly)
47 | onUpdateHook?: (updateFn: () => any) => Promise<any>;
48 |
49 | // --- Call this method when a non-local variable is accessed
50 | onWillAccess?: (scope: any, index: string | number) => void | Promise<void>;
51 |
52 | // --- Call this method when a non-local variable is updated
53 | onWillUpdate?: (scope: any, index: string | number, updateType: UpdateType) => void | Promise<void>;
54 |
55 | // --- Call this method after a non-local variable has been updated
56 | onDidUpdate?: (scope: any, index: string | number, updateType: UpdateType) => void | Promise<void>;
57 |
58 | // --- Sign that a particular statement has started
59 | onStatementStarted?(evalContext: BindingTreeEvaluationContext, stmt: Statement): void | Promise<void>;
60 |
61 | // --- Sign that a particular statement has completed
62 | onStatementCompleted?(evalContext: BindingTreeEvaluationContext, stmt: Statement): void | Promise<void>;
63 | };
64 |
65 | // --- The type of non-local variable update
66 | type UpdateType = "assignment" | "pre-post" | "function-call";
67 |
68 | /**
69 | * A token that signals the cancellation of an operation
70 | */
71 | class CancellationToken {
72 | private _cancelled = false;
73 |
74 | public get cancelled (): boolean {
75 | return this._cancelled;
76 | }
77 |
78 | public cancel (): void {
79 | this._cancelled = true;
80 | }
81 | }
82 |
83 | // Evaluation options to use with binding tree evaluation
84 | export type EvalTreeOptions = {
85 | defaultToOptionalMemberAccess?: boolean;
86 | };
87 |
88 | // This function gets an object to pass as an implicit context when invoking a function on "objectWithFunction"
89 | type ImplicitContextGetter = (objectWithFunction: any) => ActionExecutionContext;
90 |
91 | /**
92 | * Creates an evaluation context with the given parts
93 | * @param parts Parts of the evaluation context
94 | * @returns New evaluation context
95 | */
96 | export function createEvalContext (parts: Partial<BindingTreeEvaluationContext>): BindingTreeEvaluationContext {
97 | return {
98 | ...{
99 | mainThread: {
100 | childThreads: [],
101 | blocks: [{ vars: {} }],
102 | loops: [],
103 | breakLabelValue: -1
104 | },
105 | localContext: {}
106 | },
107 | ...parts
108 | };
109 | }
110 |
```
--------------------------------------------------------------------------------
/xmlui/src/components/AppState/AppState.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import { createComponentRenderer } from "../../components-core/renderers";
2 | import { createMetadata, d } from "../metadata-helpers";
3 | import { AppState, defaultProps } from "./AppStateNative";
4 |
5 | const COMP = "AppState";
6 |
7 | export const AppStateMd = createMetadata({
8 | status: "stable",
9 | description:
10 | "`AppState` is an invisible component that provides global state management " +
11 | "across your entire application. Unlike component variables that are scoped " +
12 | "locally, AppState allows any component to access and update shared state " +
13 | "without prop drilling.",
14 | events: {
15 | didUpdate: d(
16 | "This event is fired when the AppState value is updated. The event provides " +
17 | "the new state value as its parameter.",
18 | ),
19 | },
20 | props: {
21 | bucket: {
22 | description:
23 | `This property is the identifier of the bucket to which the \`${COMP}\` instance is bound. ` +
24 | `Multiple \`${COMP}\` instances with the same bucket will share the same state object: any ` +
25 | `of them updating the state will cause the other instances to view the new, updated state.`,
26 | valueType: "string",
27 | defaultValue: defaultProps.bucket,
28 | },
29 | initialValue: {
30 | description:
31 | `This property contains the initial state value. Though you can use multiple \`${COMP}\`` +
32 | `component instances for the same bucket with their \`initialValue\` set, it may result ` +
33 | `in faulty app logic. When xmlui instantiates an \`${COMP}\` with an explicit initial ` +
34 | `value, that value is immediately merged with the existing state. ` +
35 | `The issue may come from the behavior that \`initialValue\` is set (merged) only when a component mounts. ` +
36 | `By default, the bucket's initial state is undefined.`,
37 | },
38 | },
39 | apis: {
40 | update: {
41 | signature: "update(newState: Record<string, any>)",
42 | description:
43 | "This method updates the application state object bound to the `AppState` instance.",
44 | parameters: {
45 | newState: "An object that specifies the new state value.",
46 | },
47 | },
48 | appendToList: {
49 | signature: "appendToList(key: string, id: any)",
50 | description:
51 | "This method appends an item to an array in the application state object bound to the" +
52 | " `AppState` instance.",
53 | parameters: {
54 | key: "The key of the array in the state object.",
55 | id: "The item to append to the array.",
56 | },
57 | },
58 | removeFromList: {
59 | signature: "removeFromList(key: string, id: any)",
60 | description:
61 | "This method removes an item from an array in the application state object bound to the" +
62 | " `AppState` instance.",
63 | parameters: {
64 | key: "The key of the array in the state object.",
65 | id: "The item to remove from the array.",
66 | },
67 | },
68 | listIncludes: {
69 | signature: "listIncludes(key: string, id: any)",
70 | description:
71 | "This method checks if an array in the application state object contains a specific item.",
72 | parameters: {
73 | key: "The key of the array in the state object.",
74 | id: "The item to check for in the array.",
75 | },
76 | },
77 | },
78 | nonVisual: true,
79 | });
80 |
81 | export const appStateComponentRenderer = createComponentRenderer(
82 | COMP,
83 | AppStateMd,
84 | ({ node, extractValue, updateState, registerComponentApi, lookupEventHandler }) => {
85 | return (
86 | <AppState
87 | bucket={extractValue(node.props.bucket)}
88 | initialValue={extractValue(node.props.initialValue)}
89 | updateState={updateState}
90 | registerComponentApi={registerComponentApi}
91 | onDidUpdate={lookupEventHandler("didUpdate")}
92 | />
93 | );
94 | },
95 | );
96 |
```
--------------------------------------------------------------------------------
/xmlui/src/components/Charts/LineChart/LineChart.md:
--------------------------------------------------------------------------------
```markdown
1 | %-DESC-START
2 |
3 | The LineChart component accommodates the size of its parent unless you set it explicitly:
4 |
5 | ```xmlui-pg copy display height="300px" name="Example: dimension determined by the parent" /Card height="240px" width="75%"/
6 | <Card height="240px" width="75%">
7 | <LineChart
8 | data="{[
9 | { 'sprint': 'Sprint 1', 'A': 44 },
10 | { 'sprint': 'Sprint 2', 'A': 32 },
11 | { 'sprint': 'Sprint 3', 'A': 48 },
12 | { 'sprint': 'Sprint 4', 'A': 72 }
13 | ]}"
14 | yKeys="{['A']}"
15 | xKey="sprint"
16 | />
17 | </Card>
18 | ```
19 |
20 | ```xmlui-pg copy display height="300px" name="Example: dimension overwritten by LineChart" /height="240px"/ /height="200px"/
21 | <Card height="240px">
22 | <LineChart
23 | height="200px"
24 | data="{[
25 | { 'sprint': 'Sprint 1', 'A': 44 },
26 | { 'sprint': 'Sprint 2', 'A': 32 },
27 | { 'sprint': 'Sprint 3', 'A': 48 },
28 | { 'sprint': 'Sprint 4', 'A': 72 }
29 | ]}"
30 | yKeys="{['A']}"
31 | xKey="sprint"
32 | />
33 | </Card>
34 | ```
35 |
36 |
37 | **Key features:**
38 | - **Flexible orientation**: Choose horizontal or vertical bar layouts
39 | - **Multiple data series**: Display several metrics on the same chart with different colored bars
40 | - **Stacked vs grouped**: Stack bars on top of each other or place them side by side
41 | - **Custom formatting**: Use `tickFormatter` to format axis labels and [`LabelList`](/components/LabelList) for data labels
42 |
43 | %-DESC-END
44 |
45 | %-PROP-START tickFormatterY
46 |
47 | ```xmlui-pg copy display height="320px" name="Example: tickFormatterY" /tickFormatterY/
48 | <App>
49 | <LineChart
50 | height="240px"
51 | data="{[
52 | { 'sprint': 'Sprint 1', 'A': 44 },
53 | { 'sprint': 'Sprint 2', 'A': 32 },
54 | { 'sprint': 'Sprint 3', 'A': 48 },
55 | { 'sprint': 'Sprint 4', 'A': 72 }
56 | ]}"
57 | yKeys="{['A']}"
58 | xKey="sprint"
59 | tickFormatterY="{(value) => '$' + value}"
60 | />
61 | </App>
62 | ```
63 |
64 | %-PROP-END
65 |
66 |
67 | %-PROP-START tickFormatterX
68 |
69 | ```xmlui-pg copy display height="320px" name="Example: tickFormatterX" /tickFormatterX/
70 | <App>
71 | <LineChart
72 | height="240px"
73 | data="{[
74 | { 'sprint': 'Sprint 1', 'A': 44 },
75 | { 'sprint': 'Sprint 2', 'A': 32 },
76 | { 'sprint': 'Sprint 3', 'A': 48 },
77 | { 'sprint': 'Sprint 4', 'A': 72 }
78 | ]}"
79 | yKeys="{['A']}"
80 | xKey="sprint"
81 | tickFormatterX="{(value) => '(' + value + ')'}"
82 | />
83 | </App>
84 | ```
85 |
86 | %-PROP-END
87 |
88 | %-PROP-START tooltipTemplate
89 |
90 | ```xmlui-pg copy display height="320px" name="Example: tooltipTemplate" /tooltipTemplate/
91 | <App>
92 | <LineChart
93 | height="240px"
94 | data="{[
95 | { 'sprint': 'Sprint 1', 'A': 44, 'B': 28 },
96 | { 'sprint': 'Sprint 2', 'A': 32, 'B': 41 },
97 | { 'sprint': 'Sprint 3', 'A': 48, 'B': 35 },
98 | { 'sprint': 'Sprint 4', 'A': 72, 'B': 58 }
99 | ]}"
100 | yKeys="{['A', 'B']}"
101 | xKey="sprint"
102 | >
103 | <property name="tooltipTemplate">
104 | <VStack backgroundColor='white' padding="$space-2">
105 | <Text fontWeight='bold'>{$tooltip.label}</Text>
106 | <Items data="{$tooltip.payload}">
107 | <HStack gap="$space-2" verticalAlignment="center">
108 | <Stack
109 | width="8px"
110 | height="8px"
111 | backgroundColor="{$item.color}" />
112 | <Text>{$item.label}: {$item.value}</Text>
113 | </HStack>
114 | </Items>
115 | </VStack>
116 | </property>
117 | </LineChart>
118 | </App>
119 | ```
120 |
121 | The `tooltipTemplate` prop allows you to customize the appearance and content of chart tooltips. The template receives a `$tooltip` context variable containing:
122 |
123 | - `$tooltip.label`: The label for the data point (typically the yKey value)
124 | - `$tooltip.payload`: An object containing all data values for the hovered point
125 | - `$tooltip.active`: Boolean indicating if the tooltip is currently active
126 |
127 | %-PROP-END
128 |
```
--------------------------------------------------------------------------------
/docs/content/components/Image.md:
--------------------------------------------------------------------------------
```markdown
1 | # Image [#image]
2 |
3 | `Image` displays pictures from URLs or local sources with built-in responsive sizing, aspect ratio control, and accessibility features. It handles different image formats and provides options for lazy loading and click interactions.
4 |
5 | ## Properties [#properties]
6 |
7 | ### `alt` [#alt]
8 |
9 | This optional property specifies an alternate text for the image.
10 |
11 | This is useful in two cases:
12 | 1. Accessibility: screen readers read the prop value to users so they know what the image is about.
13 | 2. The text is also displayed when the image can't be loaded for some reason (network errors, content blocking, etc.).
14 |
15 | ```xmlui-pg copy display name="Example: alt"
16 | <App>
17 | <Image
18 | src="cantFindIt.jpg"
19 | alt="This image depicts a wonderful scene not for human eyes" />
20 | </App>
21 | ```
22 |
23 | ### `aspectRatio` [#aspectratio]
24 |
25 | This property sets a preferred aspect ratio for the image, which will be used in calculating auto sizes and other layout functions. If this value is not used, the original aspect ratio is kept. The value can be a number of a string (such as "16/9").
26 |
27 | ```xmlui-pg copy display name="Example: aspectRatio"
28 | <App>
29 | <Image
30 | src="/resources/images/components/image/breakfast.jpg"
31 | aspectRatio="200 / 150" />
32 | </App>
33 | ```
34 |
35 | ### `data` [#data]
36 |
37 | This property contains the binary data that represents the image.
38 |
39 | ### `fit` (default: "contain") [#fit-default-contain]
40 |
41 | This property sets how the image content should be resized to fit its container.
42 |
43 | | Name | Value |
44 | | --------- | ----- |
45 | | `contain` | The replaced content is scaled to maintain its aspect ratio while fitting within the image's container. The entire image is made to fill the container. |
46 | | `cover` | The image is sized to maintain its aspect ratio while filling the element's entire content box. If the image's aspect ratio does not match the aspect ratio of its container, then the image will be clipped to fit. |
47 |
48 | ```xmlui-pg copy display name="Example: fit" {5,9}
49 | <App>
50 | <HStack padding="1rem" height="280px" gap="1rem">
51 | <Image
52 | src="/resources/images/components/image/breakfast.jpg"
53 | fit="contain"
54 | width="240px" />
55 | <Image
56 | src="/resources/images/components/image/breakfast.jpg"
57 | fit="cover"
58 | width="240px" />
59 | </HStack>
60 | </App>
61 | ```
62 |
63 | ### `inline` (default: false) [#inline-default-false]
64 |
65 | When set to true, the image will be displayed as an inline element instead of a block element.
66 |
67 | ### `lazyLoad` (default: false) [#lazyload-default-false]
68 |
69 | Lazy loading instructs the browser to load the image only when it is imminently needed (e.g. user scrolls to it).
70 |
71 | Lazy loading instructs the browser to load the image only when it is imminently needed (e.g. user scrolls to it).
72 | The default value is eager (\`false\`).
73 |
74 | ### `src` [#src]
75 |
76 | This property is used to indicate the source (path) of the image to display. When not defined, no image is displayed.
77 |
78 | ## Events [#events]
79 |
80 | ### `click` [#click]
81 |
82 | This event is triggered when the Image is clicked.
83 |
84 | This event is triggered when the image is clicked.
85 |
86 | ```xmlui-pg copy {6} display name="Example: click"
87 | <App>
88 | <Stack height="280px" width="400px">
89 | <Image
90 | src="/resources/images/components/image/breakfast.jpg"
91 | fit="cover"
92 | onClick="toast('Image clicked')"
93 | />
94 | </Stack>
95 | </App>
96 | ```
97 |
98 | ## Exposed Methods [#exposed-methods]
99 |
100 | This component does not expose any methods.
101 |
102 | ## Styling [#styling]
103 |
104 | ### Theme Variables [#theme-variables]
105 |
106 | | Variable | Default Value (Light) | Default Value (Dark) |
107 | | --- | --- | --- |
108 | | [borderColor](../styles-and-themes/common-units/#color)-Image | *none* | *none* |
109 | | [borderRadius](../styles-and-themes/common-units/#border-rounding)-Image | *none* | *none* |
110 |
```
--------------------------------------------------------------------------------
/xmlui/src/components/NumberBox/numberbox-abstractions.ts:
--------------------------------------------------------------------------------
```typescript
1 | export const NUMBERBOX_MAX_VALUE = 999999999999999;
2 | export const DECIMAL_SEPARATOR = ".";
3 | export const EXPONENTIAL_SEPARATOR = "e";
4 | export const INT_REGEXP = /^-?\d+$/;
5 | export const FLOAT_REGEXP = /^-?\d+(\.\d+)?([eE][+-]?\d+)?$/;
6 | export const DEFAULT_STEP = 1;
7 |
8 | export type empty = null | undefined;
9 |
10 | export function isEmptyLike(value: string | number | empty): value is empty {
11 | return typeof value === "undefined" || value === null || value === "";
12 | }
13 |
14 | export function mapToRepresentation(value: string | number | empty) {
15 | if (typeof value === "string") return value;
16 | if (typeof value === "number") return value.toString();
17 | return "";
18 | }
19 |
20 | export function isOutOfBounds(value: number, min: number, max: number) {
21 | return value < min || value > max;
22 | }
23 |
24 | export function clamp(value: number, min: number, max: number) {
25 | let clamped = value;
26 | if (value < min) clamped = min;
27 | if (value > max) clamped = max;
28 | return clamped;
29 | }
30 |
31 | export function toUsableNumber(value: string | number | empty, isInteger = false): number | empty {
32 | const isUsable = isInteger ? isUsableInteger : isUsableFloat;
33 | if (!isUsable(value)) return null;
34 |
35 | if (typeof value === "string") {
36 | value = isInteger ? Number.parseInt(value) : +value;
37 | }
38 |
39 | return value;
40 | }
41 |
42 | /**
43 | * Check whether the input value is a usable number for operations.
44 | * Passes if it's of type number or a non-empty string that evaluates to a number.
45 | */
46 | export function isUsableFloat(value: string | number | empty) {
47 | if (typeof value === "string" && value.length > 0) {
48 | return !Number.isNaN(+value) && naiveFloatBounding(value);
49 | }
50 | return typeof value === "number";
51 | }
52 |
53 | // TEMP
54 | // Rounding and arithmetic with large floats is a hassle if loss of precision is apparent.
55 | // Just bound the incoming floating point value to the max value available.
56 | // This is an edge case but makes it so that we stay consistent and can do arithmetic with the spinner box.
57 | function naiveFloatBounding(value: string) {
58 | const integerPart = value.split(".")[0];
59 | return Math.abs(Number.parseInt(integerPart)) <= NUMBERBOX_MAX_VALUE;
60 | }
61 |
62 | /**
63 | * Check whether the input value is a usable integer for operations.
64 | * Passes if it's of type number and is an integer
65 | * or a non-empty string that evaluates to an integer.
66 | */
67 | export function isUsableInteger(value: string | number | empty) {
68 | if (
69 | typeof value === "string" &&
70 | value.length > 0 &&
71 | ![EXPONENTIAL_SEPARATOR, DECIMAL_SEPARATOR].some((item) => value.includes(item))
72 | ) {
73 | return Number.isSafeInteger(+value);
74 | } else if (typeof value === "number") {
75 | return Number.isSafeInteger(value);
76 | }
77 | return false;
78 | }
79 |
80 | // TODO:
81 |
82 | class NumberFormatter2 {
83 | private formatter: Intl.NumberFormat;
84 | private model: {
85 | value: number;
86 | formatted: string;
87 | stripped: string;
88 |
89 | group: string;
90 | decimal: string;
91 | sign: string;
92 | //exponent: string;
93 | }
94 | locale: string;
95 | options?: Intl.NumberFormatOptions;
96 |
97 | constructor(locale: string, options?: Intl.NumberFormatOptions) {
98 | this.locale = locale;
99 | this.options = options;
100 | this.formatter = new Intl.NumberFormat(locale, options);
101 | }
102 |
103 | set input(value: string | number | empty) {
104 |
105 | }
106 |
107 | private buildModel() {
108 | const parts = this.formatter.formatToParts(1234.5);
109 | this.model.group = parts.find((p) => p.type === "group")?.value || "";
110 | this.model.decimal = parts.find((p) => p.type === "decimal")?.value || "";
111 | }
112 |
113 | parse(value: string): number {
114 | return 0;
115 | }
116 |
117 | format(value: number): string {
118 | return this.formatter.format(value);
119 | }
120 |
121 | /**
122 | * Strip all non-numeric characters but keep the type of string
123 | * @param value
124 | */
125 | sanitize(value: string): string {
126 | return "";
127 | }
128 | }
129 |
```
--------------------------------------------------------------------------------
/xmlui/scripts/generate-docs/create-theme-files.mjs:
--------------------------------------------------------------------------------
```
1 | import { join } from "path";
2 | import { existsSync, mkdirSync } from "fs";
3 | import { collectedThemes, collectedComponentMetadata } from "../../dist/metadata/xmlui-metadata.js";
4 | import { ERROR_HANDLING } from "./constants.mjs";
5 | import { handleFatalError, validateDependencies } from "./error-handling.mjs";
6 | import { createScopedLogger } from "./logging-standards.mjs";
7 | import { pathResolver } from "./configuration-management.mjs";
8 | import {
9 | processComponentThemeVars,
10 | iterateObjectEntries,
11 | writeFileWithLogging,
12 | } from "./pattern-utilities.mjs";
13 |
14 | const OUTPUT_DIR = pathResolver.getOutputPaths().themes;
15 | const logger = createScopedLogger("ThemeGenerator");
16 |
17 | /**
18 | * Counts the number of theme variables, excluding those that start with "---"
19 | * @param {Object} themeVars - The theme variables object
20 | * @returns {number} - The count of non-separator theme variables
21 | */
22 | function countThemeVars(themeVars) {
23 | if (!themeVars || typeof themeVars !== "object") {
24 | return 0;
25 | }
26 |
27 | return Object.keys(themeVars).filter((key) => !key.startsWith("---")).length;
28 | }
29 |
30 | async function generateThemeFiles() {
31 | logger.operationStart("theme file generation");
32 |
33 | try {
34 | // --- Create the output folder
35 | if (!existsSync(OUTPUT_DIR)) {
36 | mkdirSync(OUTPUT_DIR, { recursive: true });
37 | }
38 |
39 | // Validate required dependencies
40 | validateDependencies({
41 | THEME_INFO: collectedThemes,
42 | COMPONENT_METADATA: collectedComponentMetadata,
43 | });
44 |
45 | const rootTheme = collectedThemes.root;
46 |
47 | // Extract theme variable information from components using utility
48 | const themeVarsData = processComponentThemeVars(collectedComponentMetadata, logger);
49 |
50 | // Write theme files with error handling
51 | let totalThemeVars = 0;
52 | let exportedThemeCount = 0;
53 |
54 | await iterateObjectEntries(
55 | collectedThemes,
56 | async (themeName, themeData) => {
57 | // Skip the abstract root theme
58 | if (themeName === "root") return;
59 |
60 | // Prepare the complete theme vars object
61 | const completeThemeVars = {
62 | "--- App-bound root theme variables": "",
63 | ...rootTheme.themeVars,
64 | "--- App-bound theme-specific variables": "",
65 | ...themeData.themeVars,
66 | "--- Component-bound theme variables": "",
67 | ...themeVarsData.base,
68 | light: { ...themeData.themeVars.light, ...themeVarsData.light },
69 | dark: { ...themeData.themeVars.dark, ...themeVarsData.dark },
70 | };
71 |
72 | // Count theme vars (excluding separators)
73 | const themeVarCount = countThemeVars(completeThemeVars);
74 | totalThemeVars += themeVarCount;
75 | exportedThemeCount++;
76 |
77 | const themePath = join(OUTPUT_DIR, `${themeName}.json`);
78 | const themeContent = JSON.stringify(
79 | {
80 | ...themeData,
81 | themeVars: completeThemeVars,
82 | },
83 | null,
84 | 2,
85 | );
86 |
87 | await writeFileWithLogging(themePath, themeContent, logger);
88 |
89 | logger.info(`Theme '${themeName}' exported with ${themeVarCount} theme variables`);
90 | },
91 | { async: true },
92 | );
93 |
94 | // Display summary of all exported themes
95 | if (exportedThemeCount > 0) {
96 | logger.info(`\n=== Theme Export Summary ===`);
97 | logger.info(
98 | `Exported ${exportedThemeCount} theme files with a total of ${totalThemeVars} theme variables`,
99 | );
100 | logger.info(
101 | `Average of ${Math.round(totalThemeVars / exportedThemeCount)} theme variables per theme file`,
102 | );
103 | }
104 |
105 | logger.operationComplete("theme file generation");
106 | } catch (error) {
107 | handleFatalError(error, ERROR_HANDLING.EXIT_CODES.GENERAL_ERROR, "theme file generation");
108 | }
109 | }
110 |
111 | // Execute the main function
112 | generateThemeFiles();
113 |
```
--------------------------------------------------------------------------------
/xmlui/tests-e2e/data-bindings.spec.ts:
--------------------------------------------------------------------------------
```typescript
1 | import type { ApiInterceptorDefinition } from "../src";
2 | import { expect, test } from "../src/testing/fixtures";
3 |
4 | const CustomComponent = [
5 | `
6 | <Component name="CustomComponent">
7 | <Text testId="dataValue">{$props.data}</Text>
8 | </Component>
9 | `,
10 | ];
11 | const apiInterceptor: ApiInterceptorDefinition = {
12 | operations: {
13 | "load-api-data": {
14 | url: "/api/data",
15 | method: "get",
16 | handler: `() => { return 'STRING_DATA_FROM_API'; }`,
17 | },
18 | },
19 | };
20 |
21 | test("raw_data binds as data", async ({ page, initTestBed }) => {
22 | await initTestBed(`<CustomComponent raw_data="RUNNING"/>`, {
23 | components: CustomComponent,
24 | });
25 | await expect(page.getByTestId("dataValue")).toHaveText("RUNNING");
26 | });
27 |
28 | test("inline datasource as data", async ({ page, initTestBed }) => {
29 | await initTestBed(
30 | `
31 | <CustomComponent>
32 | <property name="data">
33 | <DataSource url="/api/data"/>
34 | </property>
35 | </CustomComponent>
36 | `,
37 | {
38 | components: CustomComponent,
39 | apiInterceptor: apiInterceptor,
40 | },
41 | );
42 | await expect(page.getByTestId("dataValue")).toHaveText("STRING_DATA_FROM_API");
43 | });
44 |
45 | test("datasource reference as data", async ({ page, initTestBed }) => {
46 | await initTestBed(
47 | `
48 | <Fragment>
49 | <DataSource url="/api/data" id="someDataSource"/>
50 | <CustomComponent data="{someDataSource}"/>
51 | </Fragment>
52 | `,
53 | {
54 | components: CustomComponent,
55 | apiInterceptor: apiInterceptor,
56 | },
57 | );
58 | await expect(page.getByTestId("dataValue")).toHaveText("STRING_DATA_FROM_API");
59 | });
60 |
61 | test("datasource reference as any property", async ({ page, initTestBed }) => {
62 | await initTestBed(
63 | `
64 | <Fragment>
65 | <DataSource url="/api/data" id="someDataSource"/>
66 | <Text testId="dataValue" value="{someDataSource}"/>
67 | </Fragment>
68 | `,
69 | {
70 | components: CustomComponent,
71 | apiInterceptor: apiInterceptor,
72 | },
73 | );
74 | await expect(page.getByTestId("dataValue")).toHaveText("STRING_DATA_FROM_API");
75 | });
76 |
77 | test("datasource reference outside of implicit container", async ({ page, initTestBed }) => {
78 | await initTestBed(
79 | `
80 | <Fragment>
81 | <DataSource url="/api/data" id="someDataSource"/>
82 | <Stack backgroundColor="lightgreen" var.something="something that makes it an implicit container">
83 | <CustomComponent data="{someDataSource}"/>
84 | </Stack>
85 | </Fragment>
86 | `,
87 | {
88 | components: CustomComponent,
89 | apiInterceptor: apiInterceptor,
90 | },
91 | );
92 | await expect(page.getByTestId("dataValue")).toHaveText("STRING_DATA_FROM_API");
93 | });
94 |
95 | test("datasource value as data", async ({ page, initTestBed }) => {
96 | await initTestBed(
97 | `
98 | <Fragment>
99 | <DataSource url="/api/data" id="someDataSource"/>
100 | <CustomComponent data="{someDataSource.value}"/>
101 | </Fragment>
102 | `,
103 | {
104 | components: CustomComponent,
105 | apiInterceptor: apiInterceptor,
106 | },
107 | );
108 | await expect(page.getByTestId("dataValue")).toHaveText("STRING_DATA_FROM_API");
109 | });
110 |
111 | test("data url", async ({ page, initTestBed }) => {
112 | await initTestBed(`<CustomComponent data="/api/data"/>`, {
113 | components: CustomComponent,
114 | apiInterceptor: apiInterceptor,
115 | });
116 | await expect(page.getByTestId("dataValue")).toHaveText("STRING_DATA_FROM_API");
117 | });
118 |
119 | test("data url from binding expression", async ({ page, initTestBed }) => {
120 | await initTestBed(
121 | `
122 | <Fragment>
123 | <script>
124 | var dataUrl = "/api/data";
125 | </script>
126 | <CustomComponent data="{dataUrl}"/>
127 | </Fragment>
128 | `,
129 | {
130 | components: CustomComponent,
131 | apiInterceptor: apiInterceptor,
132 | },
133 | );
134 | await expect(page.getByTestId("dataValue")).toHaveText("STRING_DATA_FROM_API");
135 | });
136 |
```
--------------------------------------------------------------------------------
/xmlui/src/components-core/ComponentDecorator.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import type React from "react";
2 | import {
3 | cloneElement,
4 | forwardRef,
5 | useCallback,
6 | useLayoutEffect,
7 | useRef,
8 | useState,
9 | } from "react";
10 | import { composeRefs } from "@radix-ui/react-compose-refs";
11 | import { useShallowCompareMemoize } from "./utils/hooks";
12 |
13 | // --- Describes the properties of the decorator component
14 | interface DecoratorProps {
15 | // --- Attribute name and value pairs to add to the component's DOM node
16 | attr: Record<string, any>;
17 |
18 | // --- If true, only the ref'd child will have the attributes added
19 | allowOnlyRefdChild?: boolean;
20 |
21 | // --- Callback function to be called when the target component is mounted
22 | onTargetMounted?: () => void;
23 |
24 | // --- The component to decorate
25 | children: React.ReactElement;
26 | }
27 |
28 | const HIDDEN_STYLE: React.CSSProperties = { position: "absolute", width: 0, display: "none" };
29 |
30 | /**
31 | * This component decorates the DOM element of a component with a set of
32 | * attributes. We use this feature to assign helper attributes to the app's
33 | * xmlui component nodes for testing, debugging, and other development-related
34 | * purposes.
35 | */
36 | const ComponentDecorator = forwardRef((props: DecoratorProps, forwardedRef) => {
37 | // the concept:
38 | // we want to add attributes to the component's DOM node even if that component doesn't handle refs
39 | // to find the actual dom node, we use either the ref passed to the component, or the ref of the previous or next sibling
40 | // with those sibling refs we can find the actual dom node (via nextElementSibling)
41 | // we are making sure that the next and previous elements are not the same, to avoid adding attributes to the wrong element
42 | const prevSiblingRef = useRef(null);
43 | const nextSiblingRef = useRef(null);
44 | const { onTargetMounted } = props;
45 |
46 | const foundNode = useRef(null);
47 | const itemRef = useRef<HTMLElement | null>(null);
48 | const [handlesItemRefs, setHandlesItemRefs] = useState(false);
49 | const itemRefCallback = useCallback(
50 | (node: any) => {
51 | itemRef.current = node;
52 | if (node !== null) {
53 | onTargetMounted?.();
54 | }
55 | setHandlesItemRefs(true);
56 | },
57 | [onTargetMounted],
58 | );
59 | const [_, setForceRender] = useState(0);
60 |
61 | const shallowAttrs = useShallowCompareMemoize(props.attr);
62 | const shouldRenderHelperSpan = !props.allowOnlyRefdChild && !handlesItemRefs;
63 |
64 | // --- When the component mounts, we add the attributes to the component's DOM node
65 | useLayoutEffect(() => {
66 | let node;
67 | if (handlesItemRefs) {
68 | node = itemRef.current;
69 | } else {
70 | if (shouldRenderHelperSpan) {
71 | if (foundNode.current) {
72 | node = foundNode.current;
73 | } else {
74 | node =
75 | (prevSiblingRef.current.nextElementSibling === nextSiblingRef.current
76 | ? null
77 | : prevSiblingRef.current.nextElementSibling) || null;
78 | foundNode.current = node;
79 | setForceRender((prev) => prev++);
80 | }
81 | }
82 | }
83 | if (node) {
84 | Object.entries(shallowAttrs).forEach(([key, value]) => {
85 | if (value !== undefined) {
86 | node.setAttribute?.(key, value);
87 | } else {
88 | node.removeAttribute?.(key);
89 | }
90 | });
91 | }
92 | }, [shouldRenderHelperSpan, shallowAttrs, handlesItemRefs]);
93 |
94 | return (
95 | <>
96 | {!foundNode.current && shouldRenderHelperSpan && (
97 | <span style={HIDDEN_STYLE} ref={prevSiblingRef} />
98 | )}
99 | {cloneElement(props.children, {
100 | ref: forwardedRef ? composeRefs(itemRefCallback, forwardedRef) : itemRefCallback,
101 | })}
102 | {!foundNode.current && shouldRenderHelperSpan && (
103 | <span style={HIDDEN_STYLE} ref={nextSiblingRef} />
104 | )}
105 | </>
106 | );
107 | });
108 | ComponentDecorator.displayName = "ComponentDecorator";
109 |
110 | export default ComponentDecorator;
111 |
```
--------------------------------------------------------------------------------
/xmlui/src/components/ModalDialog/ModalDialog.module.scss:
--------------------------------------------------------------------------------
```scss
1 | @use "../../components-core/theming/themes" as t;
2 |
3 | // --- This code snippet is required to collect the theme variables used in this module
4 | $component: "ModalDialog";
5 | $themeVars: ();
6 | @function createThemeVar($componentVariable) {
7 | $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global;
8 | @return t.getThemeVar($themeVars, $componentVariable);
9 | }
10 |
11 | // --- Theme vars for paddings
12 | $themeVars: t.composePaddingVars($themeVars, $component);
13 | $themeVars: t.composePaddingVars($themeVars, "overlay-#{$component}");
14 | $padding-ModalDialog: createThemeVar("padding-#{$component}");
15 | $backgroundColor-ModalDialog: createThemeVar("Dialog:backgroundColor-#{$component}");
16 | $backgroundColor-overlay-ModalDialog: createThemeVar("Dialog:backgroundColor-overlay-#{$component}");
17 | $borderRadius-ModalDialog: createThemeVar("Dialog:borderRadius-#{$component}");
18 | $fontFamily-ModalDialog: createThemeVar("Dialog:fontFamily-#{$component}");
19 | $textColor-ModalDialog: createThemeVar("Dialog:textColor-#{$component}");
20 | $minWidth-ModalDialog: createThemeVar("Dialog:minWidth-#{$component}");
21 | $maxWidth-ModalDialog: createThemeVar("Dialog:maxWidth-#{$component}");
22 | $marginBottom-title-ModalDialog: createThemeVar("Dialog:marginBottom-title-#{$component}");
23 |
24 | @layer components {
25 | .overlay {
26 | position: absolute;
27 | display: grid;
28 | place-items: center;
29 | overflow-y: auto;
30 | inset: 0;
31 | padding: t.$space-4;
32 | }
33 |
34 | .fullScreen {
35 | padding: 0;
36 | width: 100%;
37 | height: 100%;
38 | display: block;
39 | .content {
40 | overflow: auto;
41 | width: 100%;
42 | height: 100%;
43 | max-width: 100%;
44 | max-height: 100%;
45 | padding: 0;
46 | border-radius: 0;
47 | }
48 | }
49 |
50 | .overlayBg {
51 | background-color: $backgroundColor-overlay-ModalDialog;
52 | inset: 0;
53 | z-index: 1000;
54 | animation: overlayShow 150ms cubic-bezier(0.16, 1, 0.3, 1);
55 | position: fixed;
56 |
57 | &.nested {
58 | position: absolute;
59 | top: 0;
60 | display: flex;
61 | align-items: center;
62 | justify-content: center;
63 | height: 100%;
64 | overflow: hidden;
65 | }
66 | }
67 |
68 | .content {
69 | background-color: $backgroundColor-ModalDialog;
70 | border-radius: $borderRadius-ModalDialog;
71 | font-family: $fontFamily-ModalDialog;
72 | color: $textColor-ModalDialog;
73 | @include t.paddingVars($themeVars, $component);
74 | box-shadow: t.$boxShadow-spread;
75 | transform: translate(0);
76 | width: 90vw;
77 | max-height: 100%;
78 | max-width: $maxWidth-ModalDialog;
79 | min-width: $minWidth-ModalDialog;
80 | z-index: 1000;
81 | isolation: isolate;
82 | position: relative;
83 | display: flex;
84 | flex-direction: column;
85 | gap: t.$space-4;
86 | }
87 |
88 | .contentAnimation {
89 | animation: contentShow 150ms cubic-bezier(0.16, 1, 0.3, 1);
90 | }
91 |
92 | .content:focus {
93 | outline: none;
94 | }
95 |
96 | .dialogTitle {
97 | flex: 1;
98 | margin-bottom: $marginBottom-title-ModalDialog;
99 | font-size: t.$fontSize-2xl;
100 | }
101 |
102 | .innerContent {
103 | display: flex;
104 | flex-direction: column;
105 | min-height: 0;
106 | gap: var(--stack-gap-default);
107 | flex: 1;
108 | }
109 |
110 | @keyframes overlayShow {
111 | from {
112 | opacity: 0;
113 | }
114 | to {
115 | opacity: 1;
116 | }
117 | }
118 |
119 | @keyframes contentShow {
120 | from {
121 | opacity: 0;
122 | transform: translateY(2%) scale(0.96);
123 | }
124 | to {
125 | opacity: 1;
126 | transform: translateY(0) scale(1);
127 | }
128 | }
129 |
130 | .closeButton {
131 | position: absolute;
132 | right: 0.5rem;
133 | top: 0.4rem;
134 | }
135 |
136 | @media (max-width: 70em) {
137 | .dialog, .content {
138 | max-width: 90%;
139 | }
140 | }
141 |
142 | @media (max-width: 50em) {
143 | .dialog, .content {
144 | width: 100%;
145 | max-width: calc(100% - #{t.$space-6});
146 | min-width: 0 !important;
147 | }
148 | }
149 | }
150 |
151 | // --- We export the theme variables to add them to the component renderer
152 | :export {
153 | themeVars: t.json-stringify($themeVars);
154 | }
155 |
```
--------------------------------------------------------------------------------
/xmlui/src/components/FormItem/ItemWithLabel.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import type { CSSProperties, ForwardedRef, ReactElement, ReactNode } from "react";
2 | import { cloneElement, forwardRef, useId } from "react";
3 | import classnames from "classnames";
4 | import { Slot } from "@radix-ui/react-slot";
5 |
6 | import styles from "./FormItem.module.scss";
7 |
8 | import type { LabelPosition } from "../abstractions";
9 | import { Spinner } from "../Spinner/SpinnerNative";
10 | import { PART_LABELED_ITEM, PART_LABEL } from "../../components-core/parts";
11 |
12 | // Component part names
13 |
14 | type ItemWithLabelProps = {
15 | id?: string;
16 | labelPosition?: LabelPosition;
17 | style?: CSSProperties;
18 | className?: string;
19 | labelStyle?: CSSProperties;
20 | label?: string;
21 | labelWidth?: string;
22 | labelBreak?: boolean;
23 | enabled?: boolean;
24 | required?: boolean;
25 | children: ReactNode;
26 | validationInProgress?: boolean;
27 | shrinkToLabel?: boolean;
28 | onFocus?: (ev: React.FocusEvent<HTMLInputElement>) => void;
29 | onBlur?: (ev: React.FocusEvent<HTMLInputElement>) => void;
30 | isInputTemplateUsed?: boolean;
31 | onLabelClick?: () => void;
32 | validationResult?: ReactNode;
33 | layoutContext?: any;
34 | testId?: string;
35 | };
36 | export const defaultProps: Pick<ItemWithLabelProps, "labelBreak"> = {
37 | labelBreak: true,
38 | };
39 |
40 | const numberRegex = /^[0-9]+$/;
41 |
42 | export const ItemWithLabel = forwardRef(function ItemWithLabel(
43 | {
44 | id,
45 | labelPosition = "top",
46 | style = {},
47 | className,
48 | label,
49 | labelBreak = defaultProps.labelBreak,
50 | labelWidth,
51 | enabled = true,
52 | required = false,
53 | children,
54 | validationInProgress = false,
55 | shrinkToLabel = false,
56 | onFocus,
57 | onBlur,
58 | labelStyle,
59 | validationResult,
60 | isInputTemplateUsed = false,
61 | onLabelClick,
62 | layoutContext,
63 | ...rest
64 | }: ItemWithLabelProps,
65 | ref: ForwardedRef<HTMLDivElement>,
66 | ) {
67 | const generatedId = useId();
68 | const inputId = id || generatedId;
69 | if (label === undefined && !validationResult) {
70 | return (
71 | <Slot
72 | {...rest}
73 | data-part-id={PART_LABELED_ITEM}
74 | style={style}
75 | className={className}
76 | id={inputId}
77 | ref={ref}
78 | >
79 | {children}
80 | </Slot>
81 | );
82 | }
83 | return (
84 | <div {...rest} ref={ref} style={style} className={classnames(className, styles.itemWithLabel)}>
85 | <div
86 | className={classnames(styles.container, {
87 | [styles.top]: labelPosition === "top",
88 | [styles.bottom]: labelPosition === "bottom",
89 | [styles.start]: labelPosition === "start",
90 | [styles.end]: labelPosition === "end",
91 | [styles.shrinkToLabel]: shrinkToLabel,
92 | })}
93 | >
94 | {label && (
95 | <label
96 | data-part-id={PART_LABEL}
97 | htmlFor={inputId}
98 | onClick={onLabelClick || (() => document.getElementById(inputId)?.focus())}
99 | style={{
100 | ...labelStyle,
101 | width: labelWidth && numberRegex.test(labelWidth) ? `${labelWidth}px` : labelWidth,
102 | flexShrink: labelWidth !== undefined ? 0 : undefined,
103 | }}
104 | className={classnames(styles.inputLabel, {
105 | [styles.required]: required,
106 | [styles.disabled]: !enabled,
107 | [styles.labelBreak]: labelBreak,
108 | })}
109 | >
110 | {label} {required && <span className={styles.requiredMark}>*</span>}
111 | {validationInProgress && (
112 | <Spinner
113 | style={{ height: "1em", width: "1em", marginLeft: "1em", alignSelf: "center" }}
114 | />
115 | )}
116 | </label>
117 | )}
118 | {cloneElement(children as ReactElement, {
119 | id: !isInputTemplateUsed ? inputId : undefined,
120 | style: undefined,
121 | className: undefined,
122 | "data-part-id": PART_LABELED_ITEM,
123 | })}
124 | </div>
125 | {validationResult}
126 | </div>
127 | );
128 | });
129 |
```
--------------------------------------------------------------------------------
/docs/public/pages/reactive-intro.md:
--------------------------------------------------------------------------------
```markdown
1 | # Reactive data binding
2 |
3 | Let's load that same London tube data into a [Select](/components/Select) component.
4 |
5 | ```xmlui-pg height="280px" name="pick a station"
6 | ---app display
7 | <App>
8 | <Select id="lines" initialValue="bakerloo" dropdownHeight="200px">
9 | <Items data="https://api.tfl.gov.uk/line/mode/tube/status">
10 | <Option value="{$item.id}" label="{$item.name}" />
11 | </Items>
12 | </Select>
13 | </App>
14 | ```
15 |
16 | The `Select` uses the same API as the `List`. It contains an <a href="/components/Items">Items</a> component which is another way to loop through a sequence and embed a component that receives each item. In this case what's embedded is a selectable <a href="/components/Option">Option</a> which again receives the `$item` variable.
17 |
18 | Nothing happens yet when you select a tube line. Let's wire up the selection to display details for the selected line in a <a href="/components/Table">Table</a>.
19 |
20 | ```xmlui-pg name="pick a station"
21 | ---app display /lines/ /tubeStations/
22 | <App>
23 | <Select id="lines" initialValue="bakerloo">
24 | <Items data="https://api.tfl.gov.uk/line/mode/tube/status">
25 | <Option value="{$item.id}" label="{$item.name}" />
26 | </Items>
27 | </Select>
28 |
29 | <DataSource
30 | id="tubeStations"
31 | when="{lines.value}"
32 | url="https://api.tfl.gov.uk/Line/{lines.value}/Route/Sequence/inbound"
33 | resultSelector="stations"/>
34 |
35 | <Table data="{tubeStations}" height="280px">
36 | <Column bindTo="name" />
37 | <Column bindTo="modes" />
38 | </Table>
39 | </App>
40 | ```
41 |
42 | The <a href="/components/DataSource">DataSource</a> component works like the `data` attribute we used with `List` and `Items`: it fetches from a REST endpoint. Unlike `List`,`Select`, and `Table`, `DataSource` isn't a visible component. It works behind the scenes to capture data for use by visible components.
43 |
44 | In this case the returned data object is big and complex, and we only want to display data from the `stations` object nested within it.
45 | The `resultSelector` property on the `DataSource` targets the nested `stations` object so we can feed just that data into the table.
46 |
47 |
48 | ## Reactive magic
49 |
50 | The `Select` is wired to the `Table`. When you make a new selection, the table fills with details for the selected line. Try it!
51 |
52 | How does this work? Note how the `Select` declares the property `id="lines"`.
53 |
54 | ```xmlui /lines/
55 | <Select id="lines" initialValue="bakerloo" width="30%">
56 | ```
57 |
58 | That makes `lines` a variable accessible other XMLUI components, and `lines.value` holds the value of the current selection.
59 |
60 | Now look at the `url` property of the `DataSource`.
61 |
62 | ```xmlui /{lines.value}/
63 | <DataSource
64 | id="tubeStations"
65 | url="https://api.tfl.gov.uk/Line/{lines.value}/Route/Sequence/inbound"
66 | resultSelector="stations"/>
67 | ```
68 |
69 | It embeds a reference to `lines.value`. When you loaded this page, that URL fetched details for the initial value of the `Select`. Changing the selection changes `lines.value` which causes the `DataSource` to fetch a new batch of details. Likewise the `Table`'s `data` property refers to `tubeStations` (the `DataSource` id) so it automatically displays the new data.
70 |
71 | There's a name for this pattern: reactive data binding. It's what spreadsheets do when a change in one cell propagates to others that refer to it. And it's what the popular framework React enables for web apps. React, as you may know, is a complex beast that only expert programmers can tame. Fortunately the expert programmers who build XMLUI have done that for you. When you build apps declaratively with XMLUI you enjoy the benefit of reactive data binding without the burden of React's complexity. You don't need to write code to make this magic happen, it's automatic!
72 |
73 | So far we've seen examples of built-in XMLUI components. But it's easy to make your own too, in the next chapter we'll see how.
74 |
```
--------------------------------------------------------------------------------
/xmlui/src/parsers/xmlui-parser/ParserError.ts:
--------------------------------------------------------------------------------
```typescript
1 | // The common root class of all parser error objects
2 | export class ParserError extends Error {
3 | constructor(
4 | message: string,
5 | public code?: string,
6 | ) {
7 | super(`${code ? `${code}: ` : ""}${message}`);
8 |
9 | // --- Set the prototype explicitly.
10 | Object.setPrototypeOf(this, ParserError.prototype);
11 | }
12 | }
13 |
14 | // Describes the structure of error messages
15 | export interface ParserErrorMessage {
16 | code: ErrorCodes;
17 | text: string;
18 | position: number;
19 | line: number;
20 | column: number;
21 | }
22 |
23 | export type ErrorCodes =
24 | | "U001"
25 | | "U002"
26 | | "U003"
27 | | "U004"
28 | | "U005"
29 | | "U006"
30 | | "U007"
31 | | "U008"
32 | | "U009"
33 | | "U010"
34 | | "U011"
35 | | "U012"
36 | | "U013"
37 | | "U014"
38 | | "U015"
39 | | "T001"
40 | | "T002"
41 | | "T003"
42 | | "T004"
43 | | "T005"
44 | | "T006"
45 | | "T007"
46 | | "T008"
47 | | "T009"
48 | | "T010"
49 | | "T011"
50 | | "T012"
51 | | "T013"
52 | | "T014"
53 | | "T015"
54 | | "T016"
55 | | "T017"
56 | | "T018"
57 | | "T019"
58 | | "T020"
59 | | "T021"
60 | | "T022"
61 | | "T023"
62 | | "T024"
63 | | "T025"
64 | | "T026"
65 | | "T027"
66 | | "T028"
67 | | "T029";
68 |
69 | // Error message type description
70 | type ErrorText = Record<ErrorCodes, string>;
71 |
72 | // The error messages of error codes
73 | export const errorMessages: ErrorText = {
74 | U001: "Unexpected token: {0}.",
75 | U002: "A component definition can have exactly one XMLUI element.",
76 | U003: "A '<' token expected.",
77 | U004: "A node identifier expected.",
78 | U005: "A '</' token expected.",
79 | U006: "A '>' or '/>' token expected.",
80 | U007: "An '{0}' ID expected in the closing tag but '{1}' received.",
81 | U008: "A '>' token expected.",
82 | U009: "An attribute identifier expected.",
83 | U010: "An '=' token expected.",
84 | U011: "An attribute value expected.",
85 | U012: "Duplicated attribute: '{0}'.",
86 | U013: "Attribute name cannot start with an uppercase letter.",
87 | U014: "An '{0}' ID expected in the closing tag's namespace but '{1}' received.",
88 | U015: "Unexpected token in text element: {0}.",
89 |
90 | T001: "A component definition must have exactly one XMLUI element.",
91 | T002: "A component definition's name must start with an uppercase letter.",
92 | T003: "A reusable component must have a non-empty name.",
93 | T004: "A reusable component's name must start with an uppercase letter.",
94 | T005: "A reusable component must have at least one nested component definition.",
95 | T006: "A reusable component definition cannot nest another one.",
96 | T007: `Invalid attribute name: '{0}'`,
97 | T008: `Event attribute names should not start with 'on' prefix: '{0}'`,
98 | T009: `Invalid node name '{0}' in a component definition`,
99 | T010: `The '{0}' element does not accept a text child`,
100 | T011: "Only 'name', 'value', and type hint attributes are accepted in '{0}'.",
101 | T012: "The 'name' attribute in '{0}' is required.",
102 | T013: "A loader element must have an id.",
103 | T014: "A loader element must not have '{0}'.",
104 | T015: "The uses element must define only a non-empty 'value' attribute.",
105 | T016: "Only 'field' or 'item' are accepted as a child element.",
106 | T017: "Cannot mix 'field' and 'item' nodes within an element.",
107 | T018: "The '{0}' node cannot have a 'name' attribute.",
108 | T019: "The 'value' attribute in '{0}' is required.",
109 | T020: "Cannot mix nested components and non-component children.",
110 | T021: "Invalid reusable component attribute '{0}'.",
111 | T022: "The 'script' tag must not have any attribute.",
112 | T023: "A 'script' tag cannot nest other child nodes, only text.",
113 | T024: "Cannot put a reusable component definitions into a slot.",
114 | T025: "Duplicate xmlns found: '{0}'.",
115 | T026: "The top level component's name cannot have a namespace.",
116 | T027: "Cannot resolve namespace '{0}'. It was not defined in any of the ancestor components.",
117 | T028: "Incorrect namespace value '{0}'. {1}",
118 | T029: "Incorrect scheme specified before ':' (colon) in namespace {0}. Delete it to get the default '{1}'.",
119 | };
120 |
```
--------------------------------------------------------------------------------
/xmlui/tests/language-server/mockData.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { MetadataProvider, ComponentMetadataCollection } from "../../src/language-server/services/common/metadata-utils";
2 |
3 | export const mockMetadata = {
4 | "Stack": {
5 | "description": "`Stack` is a layout container displaying children in a horizontal or vertical stack.",
6 | "props": {
7 | "gap": {
8 | "description": "Optional size value indicating the gap between child elements.",
9 | "valueType": "string",
10 | "defaultValue": "$gap-normal"
11 | },
12 | "reverse": {
13 | "description": "Optional boolean property to reverse the order of child elements.",
14 | "valueType": "boolean",
15 | "defaultValue": false
16 | },
17 | "wrapContent": {
18 | "description": "Optional boolean which wraps the content if set to true and the available space is not big enough. Works only with horizontal orientations.",
19 | "valueType": "boolean",
20 | "defaultValue": false
21 | },
22 | "orientation": {
23 | "description": "An optional property that governs the Stack's orientation (whether the Stack lays out its children in a row or a column).",
24 | "availableValues": [
25 | "horizontal",
26 | "vertical"
27 | ],
28 | "valueType": "string",
29 | "defaultValue": "vertical"
30 | },
31 | "horizontalAlignment": {
32 | "description": "Manages the horizontal content alignment for each child element in the Stack.",
33 | "availableValues": [
34 | "start",
35 | "center",
36 | "end"
37 | ],
38 | "valueType": "string",
39 | "defaultValue": "start"
40 | },
41 | "verticalAlignment": {
42 | "description": "Manages the vertical content alignment for each child element in the Stack.",
43 | "availableValues": [
44 | "start",
45 | "center",
46 | "end"
47 | ],
48 | "valueType": "string",
49 | "defaultValue": "start"
50 | },
51 | "hoverContainer": {
52 | "description": "Reserved for future use",
53 | "isInternal": true
54 | },
55 | "visibleOnHover": {
56 | "description": "Reserved for future use",
57 | "isInternal": true
58 | }
59 | },
60 | "events": {
61 | "click": {
62 | "description": "This event is triggered when the Stack is clicked."
63 | },
64 | "mounted": {
65 | "description": "Reserved for future use",
66 | "isInternal": true
67 | }
68 | },
69 | },
70 | "Button": {
71 | "description": "Button is an interactive element that triggers an action when clicked.",
72 | "status": "stable",
73 | "props": {
74 | "label": {
75 | "description": "This property is an optional string to set a label for the Button. If no label is specified and an icon is set, the Button will modify its styling to look like a small icon button. When the Button has nested children, it will display them and ignore the value of the `label` prop.",
76 | "type": "string"
77 | },
78 | "variant": {
79 | "description": "The button variant determines the level of emphasis the button should possess.",
80 | "isRequired": false,
81 | "type": "string",
82 | "availableValues": [
83 | {
84 | "value": "solid",
85 | "description": "A button with a border and a filled background."
86 | },
87 | {
88 | "value": "outlined",
89 | "description": "The button is displayed with a border and a transparent background."
90 | },
91 | {
92 | "value": "ghost",
93 | "description": "A button with no border and fill. Only the label is visible; the background is colored when hovered or clicked."
94 | }
95 | ],
96 | "defaultValue": "solid"
97 | },
98 | },
99 | "events": {
100 | "click": {
101 | "description": "This event is triggered when the Button is clicked."
102 | },
103 | },
104 | },
105 | }
106 |
107 | export const mockMetadataProvider = new MetadataProvider(mockMetadata as ComponentMetadataCollection);
108 |
```