This is page 13 of 139. Use http://codebase.md/xmlui-org/xmlui/mockApiDef.js?page={x} to view the full context. # Directory Structure ``` ├── .changeset │ ├── config.json │ └── cool-queens-look.md ├── .eslintrc.cjs ├── .github │ ├── build-checklist.png │ ├── ISSUE_TEMPLATE │ │ ├── bug_report.md │ │ └── feature_request.md │ └── workflows │ ├── deploy-blog.yml │ ├── deploy-docs-optimized.yml │ ├── deploy-docs.yml │ ├── prepare-versions.yml │ ├── release-packages.yml │ ├── run-all-tests.yml │ └── run-smoke-tests.yml ├── .gitignore ├── .prettierrc.js ├── .vscode │ ├── launch.json │ └── settings.json ├── blog │ ├── .gitignore │ ├── .gitkeep │ ├── CHANGELOG.md │ ├── extensions.ts │ ├── index.html │ ├── index.ts │ ├── package.json │ ├── public │ │ ├── blog │ │ │ ├── images │ │ │ │ ├── blog-page-component.png │ │ │ │ ├── blog-scrabble.png │ │ │ │ ├── integrated-blog-search.png │ │ │ │ └── lorem-ipsum.png │ │ │ ├── lorem-ipsum.md │ │ │ ├── newest-post.md │ │ │ ├── older-post.md │ │ │ └── welcome-to-the-xmlui-blog.md │ │ ├── mockServiceWorker.js │ │ ├── netlify.toml │ │ ├── resources │ │ │ ├── favicon.ico │ │ │ ├── files │ │ │ │ └── for-download │ │ │ │ └── xmlui │ │ │ │ └── xmlui-standalone.umd.js │ │ │ ├── github.svg │ │ │ ├── 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 │ │ │ └── PageNotFound.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 ├── 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 │ │ │ │ ├── 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 │ │ │ │ ├── modify-a-value-reported-in-a-column.md │ │ │ │ ├── paginate-a-list.md │ │ │ │ ├── pass-data-to-a-modal-dialog.md │ │ │ │ ├── react-to-button-click-not-keystrokes.md │ │ │ │ ├── set-the-initial-value-of-a-select-from-fetched-data.md │ │ │ │ ├── share-a-modaldialog-across-components.md │ │ │ │ ├── sync-selections-between-table-and-list-views.md │ │ │ │ ├── update-ui-optimistically.md │ │ │ │ ├── use-built-in-form-validation.md │ │ │ │ └── use-the-same-modaldialog-to-add-or-edit.md │ │ │ ├── howto.md │ │ │ ├── intro.md │ │ │ ├── layout.md │ │ │ ├── markup.md │ │ │ ├── mcp.md │ │ │ ├── modal-dialogs.md │ │ │ ├── news-and-reviews.md │ │ │ ├── reactive-intro.md │ │ │ ├── refactoring.md │ │ │ ├── routing-and-links.md │ │ │ ├── samples │ │ │ │ ├── color-palette.xmlui │ │ │ │ ├── color-values.xmlui │ │ │ │ ├── shadow-sizes.xmlui │ │ │ │ ├── spacing-sizes.xmlui │ │ │ │ ├── swatch.xmlui │ │ │ │ ├── theme-gallery-brief.xmlui │ │ │ │ └── theme-gallery.xmlui │ │ │ ├── scoping.md │ │ │ ├── scripting.md │ │ │ ├── styles-and-themes │ │ │ │ ├── common-units.md │ │ │ │ ├── layout-props.md │ │ │ │ ├── theme-variable-defaults.md │ │ │ │ ├── theme-variables.md │ │ │ │ └── themes.md │ │ │ ├── template-properties.md │ │ │ ├── test.md │ │ │ ├── tutorial-01.md │ │ │ ├── tutorial-02.md │ │ │ ├── tutorial-03.md │ │ │ ├── tutorial-04.md │ │ │ ├── tutorial-05.md │ │ │ ├── tutorial-06.md │ │ │ ├── tutorial-07.md │ │ │ ├── tutorial-08.md │ │ │ ├── tutorial-09.md │ │ │ ├── tutorial-10.md │ │ │ ├── tutorial-11.md │ │ │ ├── tutorial-12.md │ │ │ ├── universal-properties.md │ │ │ ├── user-defined-components.md │ │ │ ├── vscode.md │ │ │ ├── working-with-markdown.md │ │ │ ├── working-with-text.md │ │ │ ├── xmlui-animations │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ ├── Animation.md │ │ │ │ ├── FadeAnimation.md │ │ │ │ ├── FadeInAnimation.md │ │ │ │ ├── FadeOutAnimation.md │ │ │ │ ├── ScaleAnimation.md │ │ │ │ └── SlideInAnimation.md │ │ │ ├── xmlui-charts │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ ├── BarChart.md │ │ │ │ ├── DonutChart.md │ │ │ │ ├── LabelList.md │ │ │ │ ├── Legend.md │ │ │ │ ├── LineChart.md │ │ │ │ └── PieChart.md │ │ │ ├── xmlui-pdf │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ └── Pdf.md │ │ │ └── xmlui-spreadsheet │ │ │ ├── _meta.json │ │ │ ├── _overview.md │ │ │ └── Spreadsheet.md │ │ ├── resources │ │ │ ├── devdocs │ │ │ │ ├── debug-proxy-object-2.png │ │ │ │ ├── debug-proxy-object.png │ │ │ │ ├── table_editor_01.png │ │ │ │ ├── table_editor_02.png │ │ │ │ ├── table_editor_03.png │ │ │ │ ├── table_editor_04.png │ │ │ │ ├── table_editor_05.png │ │ │ │ ├── table_editor_06.png │ │ │ │ ├── table_editor_07.png │ │ │ │ ├── table_editor_08.png │ │ │ │ ├── table_editor_09.png │ │ │ │ ├── table_editor_10.png │ │ │ │ ├── table_editor_11.png │ │ │ │ ├── table-editor-01.png │ │ │ │ ├── table-editor-02.png │ │ │ │ ├── table-editor-03.png │ │ │ │ ├── table-editor-04.png │ │ │ │ ├── table-editor-06.png │ │ │ │ ├── table-editor-07.png │ │ │ │ ├── table-editor-08.png │ │ │ │ ├── table-editor-09.png │ │ │ │ └── xmlui-rendering-of-tiptap-markdown.png │ │ │ ├── favicon.ico │ │ │ ├── files │ │ │ │ ├── clients.json │ │ │ │ ├── daily-revenue.json │ │ │ │ ├── dashboard-stats.json │ │ │ │ ├── demo.xmlui │ │ │ │ ├── demo.xmlui.xs │ │ │ │ ├── downloads │ │ │ │ │ └── downloads.json │ │ │ │ ├── for-download │ │ │ │ │ ├── index-with-api.html │ │ │ │ │ ├── index.html │ │ │ │ │ ├── mockApi.js │ │ │ │ │ ├── start-darwin.sh │ │ │ │ │ ├── start-linux.sh │ │ │ │ │ ├── start.bat │ │ │ │ │ └── xmlui │ │ │ │ │ └── xmlui-standalone.umd.js │ │ │ │ ├── getting-started │ │ │ │ │ ├── cl-tutorial-final.zip │ │ │ │ │ ├── cl-tutorial.zip │ │ │ │ │ ├── cl-tutorial2.zip │ │ │ │ │ ├── cl-tutorial3.zip │ │ │ │ │ ├── cl-tutorial4.zip │ │ │ │ │ ├── cl-tutorial5.zip │ │ │ │ │ ├── cl-tutorial6.zip │ │ │ │ │ ├── getting-started.zip │ │ │ │ │ ├── hello-xmlui.zip │ │ │ │ │ ├── xmlui-empty.zip │ │ │ │ │ └── xmlui-starter.zip │ │ │ │ ├── howto │ │ │ │ │ └── component-icons │ │ │ │ │ └── up-arrow.svg │ │ │ │ ├── invoices.json │ │ │ │ ├── monthly-status.json │ │ │ │ ├── news-and-reviews.json │ │ │ │ ├── products.json │ │ │ │ ├── releases.json │ │ │ │ ├── tutorials │ │ │ │ │ ├── datasource │ │ │ │ │ │ └── api.ts │ │ │ │ │ └── p2do │ │ │ │ │ ├── api.ts │ │ │ │ │ └── todo-logo.svg │ │ │ │ └── xmlui.json │ │ │ ├── github.svg │ │ │ ├── images │ │ │ │ ├── apiaction-tutorial │ │ │ │ │ ├── add-success.png │ │ │ │ │ ├── apiaction-param.png │ │ │ │ │ ├── change-completed.png │ │ │ │ │ ├── change-in-progress.png │ │ │ │ │ ├── confirm-delete.png │ │ │ │ │ ├── data-error.png │ │ │ │ │ ├── data-progress.png │ │ │ │ │ ├── data-success.png │ │ │ │ │ ├── display-1.png │ │ │ │ │ ├── item-deleted.png │ │ │ │ │ ├── item-updated.png │ │ │ │ │ ├── missing-api-key.png │ │ │ │ │ ├── new-item-added.png │ │ │ │ │ └── test-message.png │ │ │ │ ├── chat-api │ │ │ │ │ └── domain-model.svg │ │ │ │ ├── components │ │ │ │ │ ├── image │ │ │ │ │ │ └── breakfast.jpg │ │ │ │ │ ├── markdown │ │ │ │ │ │ └── colors.png │ │ │ │ │ └── modal │ │ │ │ │ ├── deep_link_dialog_1.jpg │ │ │ │ │ └── deep_link_dialog_2.jpg │ │ │ │ ├── create-apps │ │ │ │ │ ├── collapsed-vertical.png │ │ │ │ │ ├── using-forms-warning-dialog.png │ │ │ │ │ └── using-forms.png │ │ │ │ ├── datasource-tutorial │ │ │ │ │ ├── data-with-header.png │ │ │ │ │ ├── filtered-data.png │ │ │ │ │ ├── filtered-items.png │ │ │ │ │ ├── initial-page-items.png │ │ │ │ │ ├── list-items.png │ │ │ │ │ ├── next-page-items.png │ │ │ │ │ ├── no-data.png │ │ │ │ │ ├── pagination-1.jpg │ │ │ │ │ ├── pagination-1.png │ │ │ │ │ ├── polling-1.png │ │ │ │ │ ├── refetch-data.png │ │ │ │ │ ├── slow-loading.png │ │ │ │ │ ├── test-message.png │ │ │ │ │ ├── Thumbs.db │ │ │ │ │ ├── unconventional-data.png │ │ │ │ │ └── unfiltered-items.png │ │ │ │ ├── flower.jpg │ │ │ │ ├── get-started │ │ │ │ │ ├── add-new-contact.png │ │ │ │ │ ├── app-modified.png │ │ │ │ │ ├── app-start.png │ │ │ │ │ ├── app-with-boxes.png │ │ │ │ │ ├── app-with-toast.png │ │ │ │ │ ├── boilerplate-structure.png │ │ │ │ │ ├── cl-initial.png │ │ │ │ │ ├── cl-start.png │ │ │ │ │ ├── contact-counts.png │ │ │ │ │ ├── contact-dialog-title.png │ │ │ │ │ ├── contact-dialog.png │ │ │ │ │ ├── contact-menus.png │ │ │ │ │ ├── contact-predicates.png │ │ │ │ │ ├── context-menu.png │ │ │ │ │ ├── dashboard-numbers.png │ │ │ │ │ ├── default-contact-list.png │ │ │ │ │ ├── delete-contact.png │ │ │ │ │ ├── delete-task.png │ │ │ │ │ ├── detailed-template.png │ │ │ │ │ ├── edit-contact-details.png │ │ │ │ │ ├── edited-contact-saved.png │ │ │ │ │ ├── empty-sections.png │ │ │ │ │ ├── filter-completed.png │ │ │ │ │ ├── fullwidth-desktop.png │ │ │ │ │ ├── fullwidth-mobile.png │ │ │ │ │ ├── initial-table.png │ │ │ │ │ ├── items-and-badges.png │ │ │ │ │ ├── loading-message.png │ │ │ │ │ ├── new-contact-button.png │ │ │ │ │ ├── new-contact-saved.png │ │ │ │ │ ├── no-empty-sections.png │ │ │ │ │ ├── personal-todo-initial.png │ │ │ │ │ ├── piechart.png │ │ │ │ │ ├── review-today.png │ │ │ │ │ ├── rudimentary-dashboard.png │ │ │ │ │ ├── section-collapsed.png │ │ │ │ │ ├── sectioned-items.png │ │ │ │ │ ├── sections-ordered.png │ │ │ │ │ ├── spacex-list-with-links.png │ │ │ │ │ ├── spacex-list.png │ │ │ │ │ ├── start-personal-todo-1.png │ │ │ │ │ ├── submit-new-contact.png │ │ │ │ │ ├── submit-new-task.png │ │ │ │ │ ├── syntax-highlighting.png │ │ │ │ │ ├── table-with-badge.png │ │ │ │ │ ├── template-with-card.png │ │ │ │ │ ├── test-emulated-api.png │ │ │ │ │ ├── Thumbs.db │ │ │ │ │ ├── todo-logo.png │ │ │ │ │ └── xmlui-tools.png │ │ │ │ ├── HelloApp.png │ │ │ │ ├── HelloApp2.png │ │ │ │ ├── logos │ │ │ │ │ ├── xmlui1.svg │ │ │ │ │ ├── xmlui2.svg │ │ │ │ │ ├── xmlui3.svg │ │ │ │ │ ├── xmlui4.svg │ │ │ │ │ ├── xmlui5.svg │ │ │ │ │ ├── xmlui6.svg │ │ │ │ │ └── xmlui7.svg │ │ │ │ ├── pdf │ │ │ │ │ └── dummy-pdf.jpg │ │ │ │ ├── rendering-engine │ │ │ │ │ ├── AppEngine-flow.svg │ │ │ │ │ ├── Component.svg │ │ │ │ │ ├── CompoundComponent.svg │ │ │ │ │ ├── RootComponent.svg │ │ │ │ │ └── tree-with-containers.svg │ │ │ │ ├── reviewers-guide │ │ │ │ │ ├── AppEngine-flow.svg │ │ │ │ │ └── incbutton-in-action.png │ │ │ │ ├── tools │ │ │ │ │ └── boilerplate-structure.png │ │ │ │ ├── try.svg │ │ │ │ ├── tutorial │ │ │ │ │ ├── app-chat-history.png │ │ │ │ │ ├── app-content-placeholder.png │ │ │ │ │ ├── app-header-and-content.png │ │ │ │ │ ├── app-links-channel-selected.png │ │ │ │ │ ├── app-links-click.png │ │ │ │ │ ├── app-navigation.png │ │ │ │ │ ├── finished-ex01.png │ │ │ │ │ ├── finished-ex02.png │ │ │ │ │ ├── hello.png │ │ │ │ │ ├── splash-screen-advanced.png │ │ │ │ │ ├── splash-screen-after-click.png │ │ │ │ │ ├── splash-screen-centered.png │ │ │ │ │ ├── splash-screen-events.png │ │ │ │ │ ├── splash-screen-expression.png │ │ │ │ │ ├── splash-screen-reuse-after.png │ │ │ │ │ ├── splash-screen-reuse-before.png │ │ │ │ │ └── splash-screen.png │ │ │ │ └── tutorial-01.png │ │ │ ├── llms.txt │ │ │ ├── logo-dark.svg │ │ │ ├── logo.svg │ │ │ ├── pg-popout.svg │ │ │ └── xmlui-logo.svg │ │ ├── serve.json │ │ └── web.config │ ├── scripts │ │ ├── download-latest-xmlui.js │ │ ├── generate-rss.js │ │ ├── get-releases.js │ │ └── utils.js │ ├── src │ │ ├── components │ │ │ ├── BlogOverview.xmlui │ │ │ ├── BlogPage.xmlui │ │ │ ├── Boxes.xmlui │ │ │ ├── Breadcrumb.xmlui │ │ │ ├── ChangeLog.xmlui │ │ │ ├── ColorPalette.xmlui │ │ │ ├── DocumentLinks.xmlui │ │ │ ├── DocumentPage.xmlui │ │ │ ├── DocumentPageNoTOC.xmlui │ │ │ ├── Icons.xmlui │ │ │ ├── IncButton.xmlui │ │ │ ├── IncButton2.xmlui │ │ │ ├── NameValue.xmlui │ │ │ ├── PageNotFound.xmlui │ │ │ ├── PaletteItem.xmlui │ │ │ ├── Palettes.xmlui │ │ │ ├── SectionHeader.xmlui │ │ │ ├── TBD.xmlui │ │ │ ├── Test.xmlui │ │ │ ├── ThemesIntro.xmlui │ │ │ ├── ThousandThemes.xmlui │ │ │ ├── TubeStops.xmlui │ │ │ ├── TubeStops.xmlui.xs │ │ │ └── TwoColumnCode.xmlui │ │ ├── config.ts │ │ ├── Main.xmlui │ │ └── themes │ │ ├── docs-theme.ts │ │ ├── earthtone.ts │ │ ├── xmlui-gray-on-default.ts │ │ ├── xmlui-green-on-default.ts │ │ └── xmlui-orange-on-default.ts │ └── tsconfig.json ├── LICENSE ├── package-lock.json ├── package.json ├── packages │ ├── xmlui-animations │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── Animation.tsx │ │ │ ├── AnimationNative.tsx │ │ │ ├── FadeAnimation.tsx │ │ │ ├── FadeInAnimation.tsx │ │ │ ├── FadeOutAnimation.tsx │ │ │ ├── index.tsx │ │ │ ├── ScaleAnimation.tsx │ │ │ └── SlideInAnimation.tsx │ │ └── tsconfig.json │ ├── xmlui-devtools │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── devtools │ │ │ │ ├── DevTools.tsx │ │ │ │ ├── DevToolsNative.module.scss │ │ │ │ ├── DevToolsNative.tsx │ │ │ │ ├── ModalDialog.module.scss │ │ │ │ ├── ModalDialog.tsx │ │ │ │ ├── ModalVisibilityContext.tsx │ │ │ │ ├── Tooltip.module.scss │ │ │ │ ├── Tooltip.tsx │ │ │ │ └── utils.ts │ │ │ ├── editor │ │ │ │ └── Editor.tsx │ │ │ └── index.tsx │ │ ├── tsconfig.json │ │ └── vite.config-overrides.ts │ ├── xmlui-hello-world │ │ ├── .gitignore │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── HelloWorld.module.scss │ │ │ ├── HelloWorld.tsx │ │ │ ├── HelloWorldNative.tsx │ │ │ └── index.tsx │ │ └── tsconfig.json │ ├── xmlui-os-frames │ │ ├── .gitignore │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── IPhoneFrame.module.scss │ │ │ ├── IPhoneFrame.tsx │ │ │ ├── MacOSAppFrame.module.scss │ │ │ ├── MacOSAppFrame.tsx │ │ │ ├── WindowsAppFrame.module.scss │ │ │ └── WindowsAppFrame.tsx │ │ └── tsconfig.json │ ├── xmlui-pdf │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ ├── components │ │ │ │ └── Pdf.xmlui │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── LazyPdfNative.tsx │ │ │ ├── Pdf.module.scss │ │ │ └── Pdf.tsx │ │ └── tsconfig.json │ ├── xmlui-playground │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── hooks │ │ │ │ ├── usePlayground.ts │ │ │ │ └── useToast.ts │ │ │ ├── index.tsx │ │ │ ├── playground │ │ │ │ ├── Box.module.scss │ │ │ │ ├── Box.tsx │ │ │ │ ├── CodeSelector.tsx │ │ │ │ ├── ConfirmationDialog.module.scss │ │ │ │ ├── ConfirmationDialog.tsx │ │ │ │ ├── Editor.tsx │ │ │ │ ├── Header.module.scss │ │ │ │ ├── Header.tsx │ │ │ │ ├── Playground.tsx │ │ │ │ ├── PlaygroundContent.module.scss │ │ │ │ ├── PlaygroundContent.tsx │ │ │ │ ├── PlaygroundNative.module.scss │ │ │ │ ├── PlaygroundNative.tsx │ │ │ │ ├── Preview.module.scss │ │ │ │ ├── Preview.tsx │ │ │ │ ├── Select.module.scss │ │ │ │ ├── StandalonePlayground.tsx │ │ │ │ ├── StandalonePlaygroundNative.module.scss │ │ │ │ ├── StandalonePlaygroundNative.tsx │ │ │ │ ├── ThemeSwitcher.module.scss │ │ │ │ ├── ThemeSwitcher.tsx │ │ │ │ ├── ToneSwitcher.tsx │ │ │ │ ├── Tooltip.module.scss │ │ │ │ ├── Tooltip.tsx │ │ │ │ └── utils.ts │ │ │ ├── providers │ │ │ │ ├── Toast.module.scss │ │ │ │ └── ToastProvider.tsx │ │ │ ├── state │ │ │ │ └── store.ts │ │ │ ├── themes │ │ │ │ └── theme.ts │ │ │ └── utils │ │ │ └── helpers.ts │ │ └── tsconfig.json │ ├── xmlui-search │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── Search.module.scss │ │ │ └── Search.tsx │ │ └── tsconfig.json │ ├── xmlui-spreadsheet │ │ ├── .gitignore │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── Spreadsheet.tsx │ │ │ └── SpreadsheetNative.tsx │ │ └── tsconfig.json │ └── xmlui-website-blocks │ ├── .gitignore │ ├── CHANGELOG.md │ ├── demo │ │ ├── components │ │ │ ├── HeroBackgroundBreakoutPage.xmlui │ │ │ ├── HeroBackgroundsPage.xmlui │ │ │ ├── HeroContentsPage.xmlui │ │ │ ├── HeroTextAlignPage.xmlui │ │ │ ├── HeroTextPage.xmlui │ │ │ └── HeroTonesPage.xmlui │ │ ├── Main.xmlui │ │ └── themes │ │ └── default.ts │ ├── index.html │ ├── index.ts │ ├── meta │ │ └── componentsMetadata.ts │ ├── package.json │ ├── public │ │ └── resources │ │ ├── building.jpg │ │ └── xmlui-logo.svg │ ├── src │ │ ├── Carousel │ │ │ ├── Carousel.module.scss │ │ │ ├── Carousel.tsx │ │ │ ├── CarouselContext.tsx │ │ │ └── CarouselNative.tsx │ │ ├── FancyButton │ │ │ ├── FancyButton.module.scss │ │ │ ├── FancyButton.tsx │ │ │ └── FancyButton.xmlui │ │ ├── Hello │ │ │ ├── Hello.tsx │ │ │ ├── Hello.xmlui │ │ │ └── Hello.xmlui.xs │ │ ├── HeroSection │ │ │ ├── HeroSection.module.scss │ │ │ ├── HeroSection.tsx │ │ │ └── HeroSectionNative.tsx │ │ ├── index.tsx │ │ ├── ScrollToTop │ │ │ ├── ScrollToTop.module.scss │ │ │ ├── ScrollToTop.tsx │ │ │ └── ScrollToTopNative.tsx │ │ └── vite-env.d.ts │ └── tsconfig.json ├── README.md ├── tools │ ├── codefence │ │ └── xmlui-code-fence-docs.md │ ├── create-app │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── create-app.ts │ │ ├── helpers │ │ │ ├── copy.ts │ │ │ ├── get-pkg-manager.ts │ │ │ ├── git.ts │ │ │ ├── install.ts │ │ │ ├── is-folder-empty.ts │ │ │ ├── is-writeable.ts │ │ │ ├── make-dir.ts │ │ │ └── validate-pkg.ts │ │ ├── index.ts │ │ ├── package.json │ │ ├── templates │ │ │ ├── default │ │ │ │ └── ts │ │ │ │ ├── gitignore │ │ │ │ ├── index.html │ │ │ │ ├── index.ts │ │ │ │ ├── public │ │ │ │ │ ├── mockServiceWorker.js │ │ │ │ │ ├── resources │ │ │ │ │ │ ├── favicon.ico │ │ │ │ │ │ └── xmlui-logo.svg │ │ │ │ │ └── serve.json │ │ │ │ └── src │ │ │ │ ├── components │ │ │ │ │ ├── ApiAware.xmlui │ │ │ │ │ ├── Home.xmlui │ │ │ │ │ ├── IncButton.xmlui │ │ │ │ │ └── PagePanel.xmlui │ │ │ │ ├── config.ts │ │ │ │ └── Main.xmlui │ │ │ ├── index.ts │ │ │ └── types.ts │ │ └── tsconfig.json │ ├── create-xmlui-hello-world │ │ ├── index.js │ │ └── package.json │ └── vscode │ ├── .gitignore │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ ├── .vscodeignore │ ├── build.sh │ ├── CHANGELOG.md │ ├── esbuild.js │ ├── eslint.config.mjs │ ├── formatter-docs.md │ ├── generate-test-sample.sh │ ├── LICENSE.md │ ├── package-lock.json │ ├── package.json │ ├── README.md │ ├── resources │ │ ├── xmlui-logo.png │ │ └── xmlui-markup-syntax-highlighting.png │ ├── src │ │ ├── extension.ts │ │ └── server.ts │ ├── syntaxes │ │ └── xmlui.tmLanguage.json │ ├── test-samples │ │ └── sample.xmlui │ ├── tsconfig.json │ └── tsconfig.tsbuildinfo ├── turbo.json └── xmlui ├── .gitignore ├── bin │ ├── bootstrap.js │ ├── build-lib.ts │ ├── build.ts │ ├── index.ts │ ├── preview.ts │ ├── start.ts │ ├── vite-xmlui-plugin.ts │ └── viteConfig.ts ├── CHANGELOG.md ├── conventions │ ├── component-qa-checklist.md │ ├── copilot-conventions.md │ ├── create-xmlui-components.md │ ├── mermaid.md │ ├── testing-conventions.md │ └── xmlui-in-a-nutshell.md ├── dev-docs │ ├── accessibility.md │ ├── build-system.md │ ├── build-xmlui.md │ ├── component-behaviors.md │ ├── containers.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 │ ├── state-management.md │ └── xmlui-repo.md ├── package.json ├── playwright.config.ts ├── scripts │ ├── coverage-only.js │ ├── e2e-test-summary.js │ ├── generate-docs │ │ ├── build-downloads-map.mjs │ │ ├── build-pages-map.mjs │ │ ├── components-config.json │ │ ├── configuration-management.mjs │ │ ├── constants.mjs │ │ ├── create-theme-files.mjs │ │ ├── DocsGenerator.mjs │ │ ├── error-handling.mjs │ │ ├── extensions-config.json │ │ ├── folders.mjs │ │ ├── generate-summary-files.mjs │ │ ├── get-docs.mjs │ │ ├── input-handler.mjs │ │ ├── logger.mjs │ │ ├── logging-standards.mjs │ │ ├── MetadataProcessor.mjs │ │ ├── pattern-utilities.mjs │ │ └── utils.mjs │ ├── get-langserver-metadata.mjs │ ├── inline-links.mjs │ └── README-e2e-summary.md ├── src │ ├── abstractions │ │ ├── _conventions.md │ │ ├── ActionDefs.ts │ │ ├── AppContextDefs.ts │ │ ├── ComponentDefs.ts │ │ ├── ContainerDefs.ts │ │ ├── ExtensionDefs.ts │ │ ├── FunctionDefs.ts │ │ ├── RendererDefs.ts │ │ ├── scripting │ │ │ ├── BlockScope.ts │ │ │ ├── Compilation.ts │ │ │ ├── LogicalThread.ts │ │ │ ├── LoopScope.ts │ │ │ ├── modules.ts │ │ │ ├── ScriptParserError.ts │ │ │ ├── Token.ts │ │ │ ├── TryScope.ts │ │ │ └── TryScopeExp.ts │ │ └── ThemingDefs.ts │ ├── components │ │ ├── _conventions.md │ │ ├── abstractions.ts │ │ ├── Accordion │ │ │ ├── Accordion.md │ │ │ ├── Accordion.module.scss │ │ │ ├── Accordion.spec.ts │ │ │ ├── Accordion.tsx │ │ │ ├── AccordionContext.tsx │ │ │ ├── AccordionItem.tsx │ │ │ ├── AccordionItemNative.tsx │ │ │ └── AccordionNative.tsx │ │ ├── Animation │ │ │ └── AnimationNative.tsx │ │ ├── APICall │ │ │ ├── APICall.md │ │ │ ├── APICall.spec.ts │ │ │ ├── APICall.tsx │ │ │ └── APICallNative.tsx │ │ ├── App │ │ │ ├── App.md │ │ │ ├── App.module.scss │ │ │ ├── App.spec.ts │ │ │ ├── App.tsx │ │ │ ├── AppLayoutContext.ts │ │ │ ├── AppNative.tsx │ │ │ ├── AppStateContext.ts │ │ │ ├── doc-resources │ │ │ │ ├── condensed-sticky.xmlui │ │ │ │ ├── condensed.xmlui │ │ │ │ ├── horizontal-sticky.xmlui │ │ │ │ ├── horizontal.xmlui │ │ │ │ ├── vertical-full-header.xmlui │ │ │ │ ├── vertical-sticky.xmlui │ │ │ │ └── vertical.xmlui │ │ │ ├── IndexerContext.ts │ │ │ ├── LinkInfoContext.ts │ │ │ ├── SearchContext.tsx │ │ │ ├── Sheet.module.scss │ │ │ └── Sheet.tsx │ │ ├── AppHeader │ │ │ ├── AppHeader.md │ │ │ ├── AppHeader.module.scss │ │ │ ├── AppHeader.spec.ts │ │ │ ├── AppHeader.tsx │ │ │ └── AppHeaderNative.tsx │ │ ├── AppState │ │ │ ├── AppState.md │ │ │ ├── AppState.spec.ts │ │ │ ├── AppState.tsx │ │ │ └── AppStateNative.tsx │ │ ├── AutoComplete │ │ │ ├── AutoComplete.md │ │ │ ├── AutoComplete.module.scss │ │ │ ├── AutoComplete.spec.ts │ │ │ ├── AutoComplete.tsx │ │ │ ├── AutoCompleteContext.tsx │ │ │ └── AutoCompleteNative.tsx │ │ ├── Avatar │ │ │ ├── Avatar.md │ │ │ ├── Avatar.module.scss │ │ │ ├── Avatar.spec.ts │ │ │ ├── Avatar.tsx │ │ │ └── AvatarNative.tsx │ │ ├── Backdrop │ │ │ ├── Backdrop.md │ │ │ ├── Backdrop.module.scss │ │ │ ├── Backdrop.spec.ts │ │ │ ├── Backdrop.tsx │ │ │ └── BackdropNative.tsx │ │ ├── Badge │ │ │ ├── Badge.md │ │ │ ├── Badge.module.scss │ │ │ ├── Badge.spec.ts │ │ │ ├── Badge.tsx │ │ │ └── BadgeNative.tsx │ │ ├── Bookmark │ │ │ ├── Bookmark.md │ │ │ ├── Bookmark.module.scss │ │ │ ├── Bookmark.spec.ts │ │ │ ├── Bookmark.tsx │ │ │ └── BookmarkNative.tsx │ │ ├── Breakout │ │ │ ├── Breakout.module.scss │ │ │ ├── Breakout.spec.ts │ │ │ ├── Breakout.tsx │ │ │ └── BreakoutNative.tsx │ │ ├── Button │ │ │ ├── Button-style.spec.ts │ │ │ ├── Button.md │ │ │ ├── Button.module.scss │ │ │ ├── Button.spec.ts │ │ │ ├── Button.tsx │ │ │ └── ButtonNative.tsx │ │ ├── Card │ │ │ ├── Card.md │ │ │ ├── Card.module.scss │ │ │ ├── Card.spec.ts │ │ │ ├── Card.tsx │ │ │ └── CardNative.tsx │ │ ├── Carousel │ │ │ ├── Carousel.md │ │ │ ├── Carousel.module.scss │ │ │ ├── Carousel.spec.ts │ │ │ ├── Carousel.tsx │ │ │ ├── CarouselContext.tsx │ │ │ ├── CarouselItem.tsx │ │ │ ├── CarouselItemNative.tsx │ │ │ └── CarouselNative.tsx │ │ ├── ChangeListener │ │ │ ├── ChangeListener.md │ │ │ ├── ChangeListener.spec.ts │ │ │ ├── ChangeListener.tsx │ │ │ └── ChangeListenerNative.tsx │ │ ├── chart-color-schemes.ts │ │ ├── Charts │ │ │ ├── AreaChart │ │ │ │ ├── AreaChart.md │ │ │ │ ├── AreaChart.spec.ts │ │ │ │ ├── AreaChart.tsx │ │ │ │ └── AreaChartNative.tsx │ │ │ ├── BarChart │ │ │ │ ├── BarChart.md │ │ │ │ ├── BarChart.module.scss │ │ │ │ ├── BarChart.spec.ts │ │ │ │ ├── BarChart.tsx │ │ │ │ └── BarChartNative.tsx │ │ │ ├── DonutChart │ │ │ │ ├── DonutChart.spec.ts │ │ │ │ └── DonutChart.tsx │ │ │ ├── LabelList │ │ │ │ ├── LabelList.spec.ts │ │ │ │ ├── LabelList.tsx │ │ │ │ ├── LabelListNative.module.scss │ │ │ │ └── LabelListNative.tsx │ │ │ ├── Legend │ │ │ │ ├── Legend.spec.ts │ │ │ │ ├── Legend.tsx │ │ │ │ └── LegendNative.tsx │ │ │ ├── LineChart │ │ │ │ ├── LineChart.md │ │ │ │ ├── LineChart.module.scss │ │ │ │ ├── LineChart.spec.ts │ │ │ │ ├── LineChart.tsx │ │ │ │ └── LineChartNative.tsx │ │ │ ├── PieChart │ │ │ │ ├── PieChart.md │ │ │ │ ├── PieChart.spec.ts │ │ │ │ ├── PieChart.tsx │ │ │ │ ├── PieChartNative.module.scss │ │ │ │ └── PieChartNative.tsx │ │ │ ├── RadarChart │ │ │ │ ├── RadarChart.md │ │ │ │ ├── RadarChart.spec.ts │ │ │ │ ├── RadarChart.tsx │ │ │ │ └── RadarChartNative.tsx │ │ │ ├── Tooltip │ │ │ │ ├── TooltipContent.module.scss │ │ │ │ ├── TooltipContent.spec.ts │ │ │ │ └── TooltipContent.tsx │ │ │ └── utils │ │ │ ├── abstractions.ts │ │ │ └── ChartProvider.tsx │ │ ├── Checkbox │ │ │ ├── Checkbox.md │ │ │ ├── Checkbox.spec.ts │ │ │ └── Checkbox.tsx │ │ ├── CodeBlock │ │ │ ├── CodeBlock.module.scss │ │ │ ├── CodeBlock.spec.ts │ │ │ ├── CodeBlock.tsx │ │ │ ├── CodeBlockNative.tsx │ │ │ └── highlight-code.ts │ │ ├── collectedComponentMetadata.ts │ │ ├── ColorPicker │ │ │ ├── ColorPicker.md │ │ │ ├── ColorPicker.module.scss │ │ │ ├── ColorPicker.spec.ts │ │ │ ├── ColorPicker.tsx │ │ │ └── ColorPickerNative.tsx │ │ ├── Column │ │ │ ├── Column.md │ │ │ ├── Column.tsx │ │ │ ├── ColumnNative.tsx │ │ │ ├── doc-resources │ │ │ │ └── list-component-data.js │ │ │ └── TableContext.tsx │ │ ├── component-utils.ts │ │ ├── ComponentProvider.tsx │ │ ├── ComponentRegistryContext.tsx │ │ ├── container-helpers.tsx │ │ ├── ContentSeparator │ │ │ ├── ContentSeparator.md │ │ │ ├── ContentSeparator.module.scss │ │ │ ├── ContentSeparator.spec.ts │ │ │ ├── ContentSeparator.tsx │ │ │ └── ContentSeparatorNative.tsx │ │ ├── DataSource │ │ │ ├── DataSource.md │ │ │ └── DataSource.tsx │ │ ├── DateInput │ │ │ ├── DateInput.md │ │ │ ├── DateInput.module.scss │ │ │ ├── DateInput.spec.ts │ │ │ ├── DateInput.tsx │ │ │ └── DateInputNative.tsx │ │ ├── DatePicker │ │ │ ├── DatePicker.md │ │ │ ├── DatePicker.module.scss │ │ │ ├── DatePicker.spec.ts │ │ │ ├── DatePicker.tsx │ │ │ └── DatePickerNative.tsx │ │ ├── DropdownMenu │ │ │ ├── DropdownMenu.md │ │ │ ├── DropdownMenu.module.scss │ │ │ ├── DropdownMenu.spec.ts │ │ │ ├── DropdownMenu.tsx │ │ │ ├── DropdownMenuNative.tsx │ │ │ ├── MenuItem.md │ │ │ └── SubMenuItem.md │ │ ├── EmojiSelector │ │ │ ├── EmojiSelector.md │ │ │ ├── EmojiSelector.spec.ts │ │ │ ├── EmojiSelector.tsx │ │ │ └── EmojiSelectorNative.tsx │ │ ├── ExpandableItem │ │ │ ├── ExpandableItem.module.scss │ │ │ ├── ExpandableItem.spec.ts │ │ │ ├── ExpandableItem.tsx │ │ │ └── ExpandableItemNative.tsx │ │ ├── FileInput │ │ │ ├── FileInput.md │ │ │ ├── FileInput.module.scss │ │ │ ├── FileInput.spec.ts │ │ │ ├── FileInput.tsx │ │ │ └── FileInputNative.tsx │ │ ├── FileUploadDropZone │ │ │ ├── FileUploadDropZone.md │ │ │ ├── FileUploadDropZone.module.scss │ │ │ ├── FileUploadDropZone.spec.ts │ │ │ ├── FileUploadDropZone.tsx │ │ │ └── FileUploadDropZoneNative.tsx │ │ ├── FlowLayout │ │ │ ├── FlowLayout.md │ │ │ ├── FlowLayout.module.scss │ │ │ ├── FlowLayout.spec.ts │ │ │ ├── FlowLayout.spec.ts-snapshots │ │ │ │ └── Edge-cases-boxShadow-is-not-clipped-1-non-smoke-darwin.png │ │ │ ├── FlowLayout.tsx │ │ │ └── FlowLayoutNative.tsx │ │ ├── Footer │ │ │ ├── Footer.md │ │ │ ├── Footer.module.scss │ │ │ ├── Footer.spec.ts │ │ │ ├── Footer.tsx │ │ │ └── FooterNative.tsx │ │ ├── Form │ │ │ ├── Form.md │ │ │ ├── Form.module.scss │ │ │ ├── Form.spec.ts │ │ │ ├── Form.tsx │ │ │ ├── formActions.ts │ │ │ ├── FormContext.ts │ │ │ └── FormNative.tsx │ │ ├── FormItem │ │ │ ├── FormItem.md │ │ │ ├── FormItem.module.scss │ │ │ ├── FormItem.spec.ts │ │ │ ├── FormItem.tsx │ │ │ ├── FormItemNative.tsx │ │ │ ├── HelperText.module.scss │ │ │ ├── HelperText.tsx │ │ │ ├── ItemWithLabel.tsx │ │ │ └── Validations.ts │ │ ├── FormSection │ │ │ ├── FormSection.md │ │ │ ├── FormSection.ts │ │ │ └── FormSection.xmlui │ │ ├── Fragment │ │ │ ├── Fragment.spec.ts │ │ │ └── Fragment.tsx │ │ ├── Heading │ │ │ ├── abstractions.ts │ │ │ ├── H1.md │ │ │ ├── H1.spec.ts │ │ │ ├── H2.md │ │ │ ├── H2.spec.ts │ │ │ ├── H3.md │ │ │ ├── H3.spec.ts │ │ │ ├── H4.md │ │ │ ├── H4.spec.ts │ │ │ ├── H5.md │ │ │ ├── H5.spec.ts │ │ │ ├── H6.md │ │ │ ├── H6.spec.ts │ │ │ ├── Heading.md │ │ │ ├── Heading.module.scss │ │ │ ├── Heading.spec.ts │ │ │ ├── Heading.tsx │ │ │ └── HeadingNative.tsx │ │ ├── HoverCard │ │ │ ├── HoverCard.tsx │ │ │ └── HovercardNative.tsx │ │ ├── HtmlTags │ │ │ ├── HtmlTags.module.scss │ │ │ ├── HtmlTags.spec.ts │ │ │ └── HtmlTags.tsx │ │ ├── Icon │ │ │ ├── AdmonitionDanger.tsx │ │ │ ├── AdmonitionInfo.tsx │ │ │ ├── AdmonitionNote.tsx │ │ │ ├── AdmonitionTip.tsx │ │ │ ├── AdmonitionWarning.tsx │ │ │ ├── ApiIcon.tsx │ │ │ ├── ArrowDropDown.module.scss │ │ │ ├── ArrowDropDown.tsx │ │ │ ├── ArrowDropUp.module.scss │ │ │ ├── ArrowDropUp.tsx │ │ │ ├── ArrowLeft.module.scss │ │ │ ├── ArrowLeft.tsx │ │ │ ├── ArrowRight.module.scss │ │ │ ├── ArrowRight.tsx │ │ │ ├── Attach.tsx │ │ │ ├── Binding.module.scss │ │ │ ├── Binding.tsx │ │ │ ├── BoardIcon.tsx │ │ │ ├── BoxIcon.tsx │ │ │ ├── CheckIcon.tsx │ │ │ ├── ChevronDownIcon.tsx │ │ │ ├── ChevronLeft.tsx │ │ │ ├── ChevronRight.tsx │ │ │ ├── ChevronUpIcon.tsx │ │ │ ├── CodeFileIcon.tsx │ │ │ ├── CodeSandbox.tsx │ │ │ ├── CompactListIcon.tsx │ │ │ ├── ContentCopyIcon.tsx │ │ │ ├── DarkToLightIcon.tsx │ │ │ ├── DatabaseIcon.module.scss │ │ │ ├── DatabaseIcon.tsx │ │ │ ├── DocFileIcon.tsx │ │ │ ├── DocIcon.tsx │ │ │ ├── DotMenuHorizontalIcon.tsx │ │ │ ├── DotMenuIcon.tsx │ │ │ ├── EmailIcon.tsx │ │ │ ├── EmptyFolderIcon.tsx │ │ │ ├── ErrorIcon.tsx │ │ │ ├── ExpressionIcon.tsx │ │ │ ├── FillPlusCricleIcon.tsx │ │ │ ├── FilterIcon.tsx │ │ │ ├── FolderIcon.tsx │ │ │ ├── GlobeIcon.tsx │ │ │ ├── HomeIcon.tsx │ │ │ ├── HyperLinkIcon.tsx │ │ │ ├── Icon.md │ │ │ ├── Icon.module.scss │ │ │ ├── Icon.spec.ts │ │ │ ├── Icon.tsx │ │ │ ├── IconNative.tsx │ │ │ ├── ImageFileIcon.tsx │ │ │ ├── Inspect.tsx │ │ │ ├── LightToDark.tsx │ │ │ ├── LinkIcon.tsx │ │ │ ├── ListIcon.tsx │ │ │ ├── LooseListIcon.tsx │ │ │ ├── MoonIcon.tsx │ │ │ ├── MoreOptionsIcon.tsx │ │ │ ├── NoSortIcon.tsx │ │ │ ├── PDFIcon.tsx │ │ │ ├── PenIcon.tsx │ │ │ ├── PhoneIcon.tsx │ │ │ ├── PhotoIcon.tsx │ │ │ ├── PlusIcon.tsx │ │ │ ├── SearchIcon.tsx │ │ │ ├── ShareIcon.tsx │ │ │ ├── SortAscendingIcon.tsx │ │ │ ├── SortDescendingIcon.tsx │ │ │ ├── StarsIcon.tsx │ │ │ ├── SunIcon.tsx │ │ │ ├── svg │ │ │ │ ├── admonition_danger.svg │ │ │ │ ├── admonition_info.svg │ │ │ │ ├── admonition_note.svg │ │ │ │ ├── admonition_tip.svg │ │ │ │ ├── admonition_warning.svg │ │ │ │ ├── api.svg │ │ │ │ ├── arrow-dropdown.svg │ │ │ │ ├── arrow-left.svg │ │ │ │ ├── arrow-right.svg │ │ │ │ ├── arrow-up.svg │ │ │ │ ├── attach.svg │ │ │ │ ├── binding.svg │ │ │ │ ├── box.svg │ │ │ │ ├── bulb.svg │ │ │ │ ├── code-file.svg │ │ │ │ ├── code-sandbox.svg │ │ │ │ ├── dark_to_light.svg │ │ │ │ ├── database.svg │ │ │ │ ├── doc.svg │ │ │ │ ├── empty-folder.svg │ │ │ │ ├── expression.svg │ │ │ │ ├── eye-closed.svg │ │ │ │ ├── eye-dark.svg │ │ │ │ ├── eye.svg │ │ │ │ ├── file-text.svg │ │ │ │ ├── filter.svg │ │ │ │ ├── folder.svg │ │ │ │ ├── img.svg │ │ │ │ ├── inspect.svg │ │ │ │ ├── light_to_dark.svg │ │ │ │ ├── moon.svg │ │ │ │ ├── pdf.svg │ │ │ │ ├── photo.svg │ │ │ │ ├── share.svg │ │ │ │ ├── stars.svg │ │ │ │ ├── sun.svg │ │ │ │ ├── trending-down.svg │ │ │ │ ├── trending-level.svg │ │ │ │ ├── trending-up.svg │ │ │ │ ├── txt.svg │ │ │ │ ├── unknown-file.svg │ │ │ │ ├── unlink.svg │ │ │ │ └── xls.svg │ │ │ ├── TableDeleteColumnIcon.tsx │ │ │ ├── TableDeleteRowIcon.tsx │ │ │ ├── TableInsertColumnIcon.tsx │ │ │ ├── TableInsertRowIcon.tsx │ │ │ ├── TrashIcon.tsx │ │ │ ├── TrendingDownIcon.tsx │ │ │ ├── TrendingLevelIcon.tsx │ │ │ ├── TrendingUpIcon.tsx │ │ │ ├── TxtIcon.tsx │ │ │ ├── UnknownFileIcon.tsx │ │ │ ├── UnlinkIcon.tsx │ │ │ ├── UserIcon.tsx │ │ │ ├── WarningIcon.tsx │ │ │ └── XlsIcon.tsx │ │ ├── IconProvider.tsx │ │ ├── IconRegistryContext.tsx │ │ ├── IFrame │ │ │ ├── IFrame.md │ │ │ ├── IFrame.module.scss │ │ │ ├── IFrame.spec.ts │ │ │ ├── IFrame.tsx │ │ │ └── IFrameNative.tsx │ │ ├── Image │ │ │ ├── Image.md │ │ │ ├── Image.module.scss │ │ │ ├── Image.spec.ts │ │ │ ├── Image.tsx │ │ │ └── ImageNative.tsx │ │ ├── Input │ │ │ ├── index.ts │ │ │ ├── InputAdornment.module.scss │ │ │ ├── InputAdornment.tsx │ │ │ ├── InputDivider.module.scss │ │ │ ├── InputDivider.tsx │ │ │ ├── InputLabel.module.scss │ │ │ ├── InputLabel.tsx │ │ │ ├── PartialInput.module.scss │ │ │ └── PartialInput.tsx │ │ ├── InspectButton │ │ │ ├── InspectButton.module.scss │ │ │ └── InspectButton.tsx │ │ ├── Items │ │ │ ├── Items.md │ │ │ ├── Items.spec.ts │ │ │ ├── Items.tsx │ │ │ └── ItemsNative.tsx │ │ ├── Link │ │ │ ├── Link.md │ │ │ ├── Link.module.scss │ │ │ ├── Link.spec.ts │ │ │ ├── Link.tsx │ │ │ └── LinkNative.tsx │ │ ├── List │ │ │ ├── doc-resources │ │ │ │ └── list-component-data.js │ │ │ ├── List.md │ │ │ ├── List.module.scss │ │ │ ├── List.spec.ts │ │ │ ├── List.tsx │ │ │ └── ListNative.tsx │ │ ├── Logo │ │ │ ├── doc-resources │ │ │ │ └── xmlui-logo.svg │ │ │ ├── Logo.md │ │ │ ├── Logo.tsx │ │ │ └── LogoNative.tsx │ │ ├── Markdown │ │ │ ├── CodeText.module.scss │ │ │ ├── CodeText.tsx │ │ │ ├── Markdown.md │ │ │ ├── Markdown.module.scss │ │ │ ├── Markdown.spec.ts │ │ │ ├── Markdown.tsx │ │ │ ├── MarkdownNative.tsx │ │ │ ├── parse-binding-expr.ts │ │ │ └── utils.ts │ │ ├── metadata-helpers.ts │ │ ├── ModalDialog │ │ │ ├── ConfirmationModalContextProvider.tsx │ │ │ ├── Dialog.module.scss │ │ │ ├── Dialog.tsx │ │ │ ├── ModalDialog.md │ │ │ ├── ModalDialog.module.scss │ │ │ ├── ModalDialog.spec.ts │ │ │ ├── ModalDialog.tsx │ │ │ ├── ModalDialogNative.tsx │ │ │ └── ModalVisibilityContext.tsx │ │ ├── NavGroup │ │ │ ├── NavGroup.md │ │ │ ├── NavGroup.module.scss │ │ │ ├── NavGroup.spec.ts │ │ │ ├── NavGroup.tsx │ │ │ ├── NavGroupContext.ts │ │ │ └── NavGroupNative.tsx │ │ ├── NavLink │ │ │ ├── NavLink.md │ │ │ ├── NavLink.module.scss │ │ │ ├── NavLink.spec.ts │ │ │ ├── NavLink.tsx │ │ │ └── NavLinkNative.tsx │ │ ├── NavPanel │ │ │ ├── NavPanel.md │ │ │ ├── NavPanel.module.scss │ │ │ ├── NavPanel.spec.ts │ │ │ ├── NavPanel.tsx │ │ │ └── NavPanelNative.tsx │ │ ├── NestedApp │ │ │ ├── AppWithCodeView.module.scss │ │ │ ├── AppWithCodeView.tsx │ │ │ ├── AppWithCodeViewNative.tsx │ │ │ ├── defaultProps.tsx │ │ │ ├── logo.svg │ │ │ ├── NestedApp.module.scss │ │ │ ├── NestedApp.tsx │ │ │ ├── NestedAppNative.tsx │ │ │ ├── Tooltip.module.scss │ │ │ ├── Tooltip.tsx │ │ │ └── utils.ts │ │ ├── NoResult │ │ │ ├── NoResult.md │ │ │ ├── NoResult.module.scss │ │ │ ├── NoResult.spec.ts │ │ │ ├── NoResult.tsx │ │ │ └── NoResultNative.tsx │ │ ├── NumberBox │ │ │ ├── numberbox-abstractions.ts │ │ │ ├── NumberBox.md │ │ │ ├── NumberBox.module.scss │ │ │ ├── NumberBox.spec.ts │ │ │ ├── NumberBox.tsx │ │ │ └── NumberBoxNative.tsx │ │ ├── Option │ │ │ ├── Option.md │ │ │ ├── Option.spec.ts │ │ │ ├── Option.tsx │ │ │ ├── OptionNative.tsx │ │ │ └── OptionTypeProvider.tsx │ │ ├── PageMetaTitle │ │ │ ├── PageMetaTilteNative.tsx │ │ │ ├── PageMetaTitle.md │ │ │ ├── PageMetaTitle.spec.ts │ │ │ └── PageMetaTitle.tsx │ │ ├── Pages │ │ │ ├── Page.md │ │ │ ├── Pages.md │ │ │ ├── Pages.module.scss │ │ │ ├── Pages.tsx │ │ │ └── PagesNative.tsx │ │ ├── Pagination │ │ │ ├── Pagination.md │ │ │ ├── Pagination.module.scss │ │ │ ├── Pagination.spec.ts │ │ │ ├── Pagination.tsx │ │ │ └── PaginationNative.tsx │ │ ├── PositionedContainer │ │ │ ├── PositionedContainer.module.scss │ │ │ ├── PositionedContainer.tsx │ │ │ └── PositionedContainerNative.tsx │ │ ├── ProfileMenu │ │ │ ├── ProfileMenu.module.scss │ │ │ └── ProfileMenu.tsx │ │ ├── ProgressBar │ │ │ ├── ProgressBar.md │ │ │ ├── ProgressBar.module.scss │ │ │ ├── ProgressBar.spec.ts │ │ │ ├── ProgressBar.tsx │ │ │ └── ProgressBarNative.tsx │ │ ├── Queue │ │ │ ├── Queue.md │ │ │ ├── Queue.spec.ts │ │ │ ├── Queue.tsx │ │ │ ├── queueActions.ts │ │ │ └── QueueNative.tsx │ │ ├── RadioGroup │ │ │ ├── RadioGroup.md │ │ │ ├── RadioGroup.module.scss │ │ │ ├── RadioGroup.spec.ts │ │ │ ├── RadioGroup.tsx │ │ │ ├── RadioGroupNative.tsx │ │ │ ├── RadioItem.tsx │ │ │ └── RadioItemNative.tsx │ │ ├── RealTimeAdapter │ │ │ ├── RealTimeAdapter.tsx │ │ │ └── RealTimeAdapterNative.tsx │ │ ├── Redirect │ │ │ ├── Redirect.md │ │ │ ├── Redirect.spec.ts │ │ │ └── Redirect.tsx │ │ ├── ResponsiveBar │ │ │ ├── README.md │ │ │ ├── ResponsiveBar.md │ │ │ ├── ResponsiveBar.module.scss │ │ │ ├── ResponsiveBar.spec.ts │ │ │ ├── ResponsiveBar.tsx │ │ │ └── ResponsiveBarNative.tsx │ │ ├── Select │ │ │ ├── HiddenOption.tsx │ │ │ ├── MultiSelectOption.tsx │ │ │ ├── OptionContext.ts │ │ │ ├── Select.md │ │ │ ├── Select.module.scss │ │ │ ├── Select.spec.ts │ │ │ ├── Select.tsx │ │ │ ├── SelectContext.tsx │ │ │ ├── SelectNative.tsx │ │ │ ├── SelectOption.tsx │ │ │ └── SimpleSelect.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 │ │ │ ├── compound-utils.ts │ │ │ ├── css-utils.ts │ │ │ ├── DataLoaderQueryKeyGenerator.ts │ │ │ ├── date-utils.ts │ │ │ ├── extractParam.ts │ │ │ ├── hooks.tsx │ │ │ ├── LruCache.ts │ │ │ ├── mergeProps.ts │ │ │ ├── misc.ts │ │ │ ├── request-params.ts │ │ │ ├── statementUtils.ts │ │ │ └── treeUtils.ts │ │ └── xmlui-parser.ts │ ├── index-standalone.ts │ ├── index.scss │ ├── index.ts │ ├── language-server │ │ ├── server-common.ts │ │ ├── server-web-worker.ts │ │ ├── server.ts │ │ ├── services │ │ │ ├── common │ │ │ │ ├── docs-generation.ts │ │ │ │ ├── lsp-utils.ts │ │ │ │ ├── metadata-utils.ts │ │ │ │ └── syntax-node-utilities.ts │ │ │ ├── completion.ts │ │ │ ├── diagnostic.ts │ │ │ ├── format.ts │ │ │ └── hover.ts │ │ └── xmlui-metadata-generated.mjs │ ├── logging │ │ ├── LoggerContext.tsx │ │ ├── LoggerInitializer.tsx │ │ ├── LoggerService.ts │ │ └── xmlui.ts │ ├── logo.svg │ ├── parsers │ │ ├── common │ │ │ ├── GenericToken.ts │ │ │ ├── InputStream.ts │ │ │ └── utils.ts │ │ ├── scripting │ │ │ ├── code-behind-collect.ts │ │ │ ├── Lexer.ts │ │ │ ├── modules.ts │ │ │ ├── Parser.ts │ │ │ ├── ParserError.ts │ │ │ ├── ScriptingNodeTypes.ts │ │ │ ├── TokenTrait.ts │ │ │ ├── TokenType.ts │ │ │ └── tree-visitor.ts │ │ ├── style-parser │ │ │ ├── errors.ts │ │ │ ├── source-tree.ts │ │ │ ├── StyleInputStream.ts │ │ │ ├── StyleLexer.ts │ │ │ ├── StyleParser.ts │ │ │ └── tokens.ts │ │ └── xmlui-parser │ │ ├── CharacterCodes.ts │ │ ├── diagnostics.ts │ │ ├── fileExtensions.ts │ │ ├── index.ts │ │ ├── lint.ts │ │ ├── parser.ts │ │ ├── ParserError.ts │ │ ├── scanner.ts │ │ ├── syntax-kind.ts │ │ ├── syntax-node.ts │ │ ├── transform.ts │ │ ├── utils.ts │ │ ├── xmlui-serializer.ts │ │ └── xmlui-tree.ts │ ├── react-app-env.d.ts │ ├── syntax │ │ ├── monaco │ │ │ ├── grammar.monacoLanguage.ts │ │ │ ├── index.ts │ │ │ ├── xmlui-dark.ts │ │ │ ├── xmlui-light.ts │ │ │ └── xmluiscript.monacoLanguage.ts │ │ └── textMate │ │ ├── index.ts │ │ ├── xmlui-dark.json │ │ ├── xmlui-light.json │ │ ├── xmlui.json │ │ └── xmlui.tmLanguage.json │ ├── testing │ │ ├── assertions.ts │ │ ├── component-test-helpers.ts │ │ ├── ComponentDrivers.ts │ │ ├── drivers │ │ │ ├── DateInputDriver.ts │ │ │ ├── ModalDialogDriver.ts │ │ │ ├── NumberBoxDriver.ts │ │ │ ├── TextBoxDriver.ts │ │ │ ├── TimeInputDriver.ts │ │ │ ├── TimerDriver.ts │ │ │ └── TreeDriver.ts │ │ ├── fixtures.ts │ │ ├── infrastructure │ │ │ ├── index.html │ │ │ ├── main.tsx │ │ │ ├── public │ │ │ │ ├── mockServiceWorker.js │ │ │ │ ├── resources │ │ │ │ │ ├── bell.svg │ │ │ │ │ ├── box.svg │ │ │ │ │ ├── doc.svg │ │ │ │ │ ├── eye.svg │ │ │ │ │ ├── flower-640x480.jpg │ │ │ │ │ ├── sun.svg │ │ │ │ │ ├── test-image-100x100.jpg │ │ │ │ │ └── txt.svg │ │ │ │ └── serve.json │ │ │ └── TestBed.tsx │ │ └── themed-app-test-helpers.ts │ └── vite-env.d.ts ├── tests │ ├── components │ │ ├── CodeBlock │ │ │ └── hightlight-code.test.ts │ │ ├── playground-pattern.test.ts │ │ └── Tree │ │ └── Tree-states.test.ts │ ├── components-core │ │ ├── abstractions │ │ │ └── treeAbstractions.test.ts │ │ ├── container │ │ │ └── buildProxy.test.ts │ │ ├── interception │ │ │ ├── orderBy.test.ts │ │ │ ├── ReadOnlyCollection.test.ts │ │ │ └── request-param-converter.test.ts │ │ ├── scripts-runner │ │ │ ├── AttributeValueParser.test.ts │ │ │ ├── eval-tree-arrow-async.test.ts │ │ │ ├── eval-tree-arrow.test.ts │ │ │ ├── eval-tree-func-decl-async.test.ts │ │ │ ├── eval-tree-func-decl.test.ts │ │ │ ├── eval-tree-pre-post.test.ts │ │ │ ├── eval-tree-regression.test.ts │ │ │ ├── eval-tree.test.ts │ │ │ ├── function-proxy.test.ts │ │ │ ├── parser-regression.test.ts │ │ │ ├── process-event.test.ts │ │ │ ├── process-function.test.ts │ │ │ ├── process-implicit-context.test.ts │ │ │ ├── process-statement-asgn.test.ts │ │ │ ├── process-statement-destruct.test.ts │ │ │ ├── process-statement-regs.test.ts │ │ │ ├── process-statement-sync.test.ts │ │ │ ├── process-statement.test.ts │ │ │ ├── process-switch-sync.test.ts │ │ │ ├── process-switch.test.ts │ │ │ ├── process-try-sync.test.ts │ │ │ ├── process-try.test.ts │ │ │ └── test-helpers.ts │ │ ├── test-metadata-handler.ts │ │ ├── theming │ │ │ ├── border-segments.test.ts │ │ │ ├── component-layout.resolver.test.ts │ │ │ ├── layout-property-parser.test.ts │ │ │ ├── layout-resolver.test.ts │ │ │ ├── layout-resolver2.test.ts │ │ │ ├── layout-vp-override.test.ts │ │ │ └── padding-segments.test.ts │ │ └── utils │ │ ├── date-utils.test.ts │ │ ├── format-human-elapsed-time.test.ts │ │ └── LruCache.test.ts │ ├── language-server │ │ ├── completion.test.ts │ │ ├── format.test.ts │ │ ├── hover.test.ts │ │ └── mockData.ts │ └── parsers │ ├── common │ │ └── input-stream.test.ts │ ├── markdown │ │ └── parse-binding-expression.test.ts │ ├── parameter-parser.test.ts │ ├── paremeter-parser.test.ts │ ├── scripting │ │ ├── eval-tree-arrow.test.ts │ │ ├── eval-tree-pre-post.test.ts │ │ ├── eval-tree.test.ts │ │ ├── function-proxy.test.ts │ │ ├── lexer-literals.test.ts │ │ ├── lexer-misc.test.ts │ │ ├── module-parse.test.ts │ │ ├── parser-arrow.test.ts │ │ ├── parser-assignments.test.ts │ │ ├── parser-binary.test.ts │ │ ├── parser-destructuring.test.ts │ │ ├── parser-errors.test.ts │ │ ├── parser-expressions.test.ts │ │ ├── parser-function.test.ts │ │ ├── parser-literals.test.ts │ │ ├── parser-primary.test.ts │ │ ├── parser-regex.test.ts │ │ ├── parser-statements.test.ts │ │ ├── parser-unary.test.ts │ │ ├── process-event.test.ts │ │ ├── process-implicit-context.test.ts │ │ ├── process-statement-asgn.test.ts │ │ ├── process-statement-destruct.test.ts │ │ ├── process-statement-regs.test.ts │ │ ├── process-statement-sync.test.ts │ │ ├── process-statement.test.ts │ │ ├── process-switch-sync.test.ts │ │ ├── process-switch.test.ts │ │ ├── process-try-sync.test.ts │ │ ├── process-try.test.ts │ │ ├── simplify-expression.test.ts │ │ ├── statement-hooks.test.ts │ │ └── test-helpers.ts │ ├── style-parser │ │ ├── generateHvarChain.test.ts │ │ ├── parseHVar.test.ts │ │ ├── parser.test.ts │ │ └── tokens.test.ts │ └── xmlui │ ├── lint.test.ts │ ├── parser.test.ts │ ├── scanner.test.ts │ ├── transform.attr.test.ts │ ├── transform.circular.test.ts │ ├── transform.element.test.ts │ ├── transform.errors.test.ts │ ├── transform.escape.test.ts │ ├── transform.regression.test.ts │ ├── transform.script.test.ts │ ├── transform.test.ts │ └── xmlui.ts ├── tests-e2e │ ├── api-bound-component-regression.spec.ts │ ├── api-call-as-extracted-component.spec.ts │ ├── assign-to-object-or-array-regression.spec.ts │ ├── binding-regression.spec.ts │ ├── children-as-template-context-vars.spec.ts │ ├── compound-component.spec.ts │ ├── context-vars-regression.spec.ts │ ├── data-bindings.spec.ts │ ├── datasource-and-api-usage-in-var.spec.ts │ ├── datasource-direct-binding.spec.ts │ ├── datasource-onLoaded-regression.spec.ts │ ├── modify-array-item-regression.spec.ts │ ├── namespaces.spec.ts │ ├── push-to-array-regression.spec.ts │ ├── screen-breakpoints.spec.ts │ ├── scripting.spec.ts │ ├── state-scope-in-pages.spec.ts │ └── state-var-scopes.spec.ts ├── tsconfig.bin.json ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── vitest.config.ts ``` # Files -------------------------------------------------------------------------------- /xmlui/src/components/Pages/PagesNative.tsx: -------------------------------------------------------------------------------- ```typescript import type { CSSProperties, ReactNode } from "react"; import { useMemo } from "react"; import { Navigate, Route, Routes, useParams } from "@remix-run/react"; import classnames from "classnames"; import type { ComponentDef } from "../../abstractions/ComponentDefs"; import type { LayoutContext, RenderChildFn, ValueExtractor } from "../../abstractions/RendererDefs"; import { EMPTY_ARRAY, EMPTY_OBJECT } from "../../components-core/constants"; import type { PageMd } from "./Pages"; import styles from "./Pages.module.scss"; // Default props for Pages component export const defaultProps = { fallbackPath: "/", }; // --- We need this component to make sure all the child routes are wrapped in a // --- container and this way they can access the routeParams type RouteWrapperProps = { childRoute?: ComponentDef | Array<ComponentDef>; renderChild: RenderChildFn; layoutContext?: LayoutContext; style?: CSSProperties; className?: string; uid?: string; }; export function RouteWrapper({ childRoute = EMPTY_ARRAY, renderChild, layoutContext, style, className, uid, }: RouteWrapperProps) { const params = useParams(); //we need to wrap the child route in a container to make sure the route params are available. // we do this wrapping by providing an empty object to vars. // this way it becomes an 'implicit' container (vars/state inside this container is propagated to the parent) const wrappedWithContainer = useMemo(() => { if (Array.isArray(childRoute)) { return { type: "Fragment", uid, vars: EMPTY_OBJECT, children: childRoute, }; } return { type: "Fragment", uid, vars: EMPTY_OBJECT, children: [childRoute], }; }, [childRoute, uid]); return ( <div key={JSON.stringify(params)} className={classnames(className, styles.wrapper, "xmlui-page-root")} style={style} > {renderChild(wrappedWithContainer, layoutContext)} </div> ); } type PageComponentDef = ComponentDef<typeof PageMd>; type PagesProps = { fallbackPath?: string; node?: ComponentDef; renderChild: RenderChildFn; extractValue: ValueExtractor; children?: ReactNode; className?: ReactNode; }; export function Pages({ node, renderChild, extractValue, fallbackPath = defaultProps.fallbackPath, }: PagesProps) { const routes: Array<PageComponentDef> = []; const restChildren: Array<ComponentDef> = []; node.children?.forEach((child) => { if (child.type === "Page") { routes.push(child as PageComponentDef); } else { restChildren.push(child); } }); return ( <> <Routes> {routes.map((child, i) => { return ( <Route path={extractValue(child.props.url)} key={i} element={renderChild(child)} /> ); })} {fallbackPath && <Route path="*" element={<Navigate to={fallbackPath} replace />} />} </Routes> {renderChild(restChildren)} </> ); } ``` -------------------------------------------------------------------------------- /docs/public/pages/app-structure.md: -------------------------------------------------------------------------------- ```markdown # Structure of an XMLUI app The [XMLUI Invoice demo app](https://github.com/xmlui-org/xmlui-invoice/releases) exhibits the typical structure of an XMLUI app. ```xmlui-tree <root> index.html Main.xmlui config.json components ClientDetails.xmlui Clients.xmlui ... MonthlyRevenue.xmlui WeeklyRevenue.xmlui resources favicon.ico xmlui-logo-inverted.svg xmlui-logo.svg themes invoice.json xmlui 0.9.23.js charts-0.1.21.js start.bat start.sh api.json data.db xmlui-test-server ``` > [!INFO] The `xmlui` folder contains the xmlui engine with a version number, specifically `0.9.23.js`. We recommend this practice in order to know when/whether to upgrade. | file| description | |---|---| | **`index.html`** | The default webpage to display | | **`Main.xmlui`** | The XMLUI app's entry point | | **`config.json`** | The XMLUI app's configuration file | | **`components`** | The folder with your custom components | | **`resources`** | The folder with static app resources | | **`themes`** | The folder with your custom themes | | **`xmlui`** | The folder with the XMLUI core framework and extensions | | **`start.bat`** | The batch file to start the test server on Windows | | **`start.sh`** | The bash script file to start the test server on Mac, Linux, or WSL | | **`api.json`** | *Optional*: API description file for use with xmlui-test-server | | **`data.db`** | *Optional*: SQLite database for use with xmlui-test-server| | **`xmlui-test-server`** | *Optional*: server, you can use any static web server| You can deploy this tree structure (minus the optional `api.json`, `data.db`, and `xmlui-test-server`) to any static webserver that's configured to serve `index.html`. Consider this minimal app. ```xmlui-tree xmlui-minimal index.html Main.xmlui components Home.xmlui resources favicon.ico xmlui-logo-inverted.svg xmlui-logo.svg xmlui 0.9.23.js ``` ## index.html ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <script src="xmlui/0.9.23.js"></script> </head> <body> </body> </html> ``` ## Main.xmlui ```xmlui <App name="XMLUI Minimal"> <NavPanel> <NavLink label="Home" to="/Home" /> </NavPanel> <Pages> <Page url="/Home"> <Home /> </Page> </Pages> </App> ``` ## Home.xmlui ```xmlui <Component name="Home" > A minimal XMLUI app </Component> ``` ## Local deployment If you are working locally, in a folder at the root of this tree, here are some ways you can serve the app. If you have node.js and npm: ``` npx -y http-server $ npx -y http-server Starting up http-server, serving ./ Available on: http://127.0.0.1:8080 ``` If you have python: ``` $ python -m http.server 8080 Serving HTTP on :: port 8080 (http://[::]:8080/) ... ``` In either case, visit http://localhost:8080 to view the app. See also [Hosted deployment](/hosted-deployment). ``` -------------------------------------------------------------------------------- /xmlui/src/components/Charts/DonutChart/DonutChart.tsx: -------------------------------------------------------------------------------- ```typescript import { createComponentRenderer } from "../../../components-core/renderers"; import styles from "../PieChart/PieChartNative.module.scss"; import { defaultProps, PieChart } from "../PieChart/PieChartNative"; import { parseScssVar } from "../../../components-core/theming/themeVars"; import { createMetadata } from "../../metadata-helpers"; const COMP = "DonutChart"; const defaultPropsDonut = { ...defaultProps, innerRadius: 60, }; export const DonutChartMd = createMetadata({ status: "experimental", description: "A derivative of [PieChart](/components/PieChart) with a hollow center. " + "Note that the height of the component or its parent needs to be set explicitly.", props: { data: { description: "The data to be displayed in the chart. Needs to be an array of objects.", }, nameKey: { description: "Specifies the key in the data objects that will be used to label the different data series.", valueType: "string", }, dataKey: { description: "This property specifies the key in the data objects that will be used to render the chart.", valueType: "string", }, showLabel: { description: "Toggles whether to show labels (\`true\`) or not (\`false\`).", valueType: "boolean", defaultValue: defaultPropsDonut.showLabel, }, innerRadius: { description: "Sets the inner radius of the donut chart.", valueType: "number", defaultValue: defaultPropsDonut.innerRadius, }, showLabelList: { description: "Whether to show labels in a list (\`true\`) or not (\`false\`).", valueType: "boolean", defaultValue: defaultPropsDonut.showLabelList, }, showLegend: { description: "Whether to show a legend (\`true\`) or not (\`false\`).", valueType: "boolean", defaultValue: defaultPropsDonut.showLegend, }, }, themeVars: parseScssVar(styles.themeVars), defaultThemeVars: { "textColor-labelList-PieChart": "$textColor-primary", }, }); export const donutChartComponentRenderer = createComponentRenderer( COMP, DonutChartMd, ({ extractValue, node, className, renderChild }) => { return ( <PieChart showLabelList={extractValue.asOptionalBoolean( node.props?.showLabelList, defaultPropsDonut.showLabelList, )} innerRadius={extractValue.asOptionalNumber( node.props?.innerRadius, defaultPropsDonut.innerRadius, )} data={extractValue(node.props?.data)} className={className} showLabel={extractValue.asOptionalBoolean( node.props?.showLabel, defaultPropsDonut.showLabel, )} dataKey={extractValue(node.props?.dataKey)} nameKey={extractValue(node.props?.nameKey)} showLegend={extractValue.asOptionalBoolean( node.props?.showLegend, defaultPropsDonut.showLegend, )} > {renderChild(node.children)} </PieChart> ); }, ); ``` -------------------------------------------------------------------------------- /xmlui/src/components/List/List.module.scss: -------------------------------------------------------------------------------- ```scss @use "../../components-core/theming/themes" as t; // --- This code snippet is required to collect the theme variables used in this module $themeVars: (); @function createThemeVar($componentVariable) { $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global; @return t.getThemeVar($themeVars, $componentVariable); } @layer components { .outerWrapper { overflow: auto; //max-height: 100%; overflow-anchor: none; &.hasOutsideScroll { overflow: initial; } } .innerWrapper { visibility: hidden; display: flex; flex-direction: column; min-height: 100%; &.reverse { justify-content: flex-end; } } .infoWrapper { width: 100%; margin: 0 auto; } .loadingWrapper { display: flex; flex-direction: row; justify-content: center; padding-top: t.$space-2; padding-bottom: t.$space-2; } .noRows { width: 100%; text-align: center; margin-top: t.$space-4; } // --- We export the theme variables to add them to the component renderer :export { themeVars: t.json-stringify($themeVars); } .borderCollapse { &:not(.sectioned) { .row { //first row in the list (but not the last) &:first-child:not(:last-child) { & > * { border-bottom-left-radius: 0; border-bottom-right-radius: 0; border-bottom: 0; } } //last row in the list (but not the first) &:last-child:not(:first-child) { & > * { border-top-left-radius: 0; border-top-right-radius: 0; } } //rows in the middle of the list &:not(:first-child):not(:last-child) { & > * { border-bottom-left-radius: 0; border-bottom-right-radius: 0; border-top-left-radius: 0; border-top-right-radius: 0; border-bottom: 0; } } } } &.sectioned { //.row after the section header (first row in a section) .section + .row:not(:has(+ .sectionFooter)) { & > * { border-bottom-left-radius: 0; border-bottom-right-radius: 0; border-bottom: 0; } } //.row before the sectionFooter (last row in a section) .row:has(+ .sectionFooter):not(.section + .row) { & > * { border-top-left-radius: 0; border-top-right-radius: 0; } } //we select the rows that has .row as direct sibling, but not the first row of a section (rows in the middle of the section) .row:has(+ .row):not(.section + .row) { & > * { border-bottom-left-radius: 0; border-bottom-right-radius: 0; border-top-left-radius: 0; border-top-right-radius: 0; border-bottom: 0; } } } } } // --- We export the theme variables to add them to the component renderer :export { themeVars: t.json-stringify($themeVars); } ``` -------------------------------------------------------------------------------- /xmlui/src/components/ToneSwitch/ToneSwitch.spec.ts: -------------------------------------------------------------------------------- ```typescript import { expect, test } from "../../testing/fixtures"; // ============================================================================= // BASIC FUNCTIONALITY TESTS // ============================================================================= test.describe("Basic Functionality", () => { test("renders in light mode by default", async ({ initTestBed, page }) => { await initTestBed(` <App> <ToneSwitch /> <Text>{activeThemeTone}</Text> </App> `); const toggle = page.getByRole("switch"); await expect(toggle).toBeVisible(); await expect(page.getByText("light")).toBeVisible(); }); test("toggles to dark mode when clicked", async ({ initTestBed, page }) => { await initTestBed(` <App> <ToneSwitch /> <Text>{activeThemeTone}</Text> </App> `); const toggle = page.getByRole("switch"); await expect(toggle).toBeVisible(); await toggle.click({ force: true }); await expect(page.getByText("dark")).toBeVisible(); await expect(toggle).toBeChecked(); }); test("toggles back to light mode when clicked again", async ({ initTestBed, page }) => { await initTestBed(` <App> <ToneSwitch /> <Text>{activeThemeTone}</Text> </App> `); const toggle = page.getByRole("switch"); await expect(toggle).toBeVisible(); await toggle.click({ force: true }); await expect(page.getByText("dark")).toBeVisible(); await expect(toggle).toBeChecked(); await toggle.click({ force: true }); await expect(page.getByText("light")).toBeVisible(); await expect(toggle).not.toBeChecked(); }); }); // ============================================================================= // ACCESSIBILITY TESTS // ============================================================================= test.describe("Accessibility", () => { test("has switch role", async ({ initTestBed, page }) => { await initTestBed(`<ToneSwitch />`); const toggle = page.getByRole("switch"); await expect(toggle).toBeVisible(); }); test("is keyboard accessible with Space key", async ({ initTestBed, page }) => { await initTestBed(`<ToneSwitch />`); const toggle = page.getByRole("switch"); await toggle.focus(); await expect(toggle).toBeFocused(); await page.keyboard.press("Space"); await expect(toggle).toBeChecked(); }); test("maintains focus during interactions", async ({ initTestBed, page }) => { await initTestBed(`<ToneSwitch />`); const toggle = page.getByRole("switch"); await toggle.focus(); await toggle.click({ force: true }); await expect(toggle).toBeFocused(); }); test("has appropriate aria-checked state", async ({ initTestBed, page }) => { await initTestBed(`<ToneSwitch />`); const toggle = page.getByRole("switch"); await expect(toggle).toHaveAttribute("aria-checked", "false"); await toggle.click({ force: true }); await expect(toggle).toHaveAttribute("aria-checked", "true"); }); }); ``` -------------------------------------------------------------------------------- /xmlui/src/components/Charts/PieChart/PieChart.tsx: -------------------------------------------------------------------------------- ```typescript import { defaultProps, PieChart } from "./PieChartNative"; import styles from "./PieChartNative.module.scss"; import { LabelPositionValues } from "../utils/abstractions"; import { parseScssVar } from "../../../components-core/theming/themeVars"; import { createComponentRenderer } from "../../../components-core/renderers"; import type { LabelPosition } from "recharts/types/component/Label"; import { createMetadata, d } from "../../metadata-helpers"; const COMP = "PieChart"; export const PieChartMd = createMetadata({ status: "experimental", description: "`PieChart` visualizes proportional data as circular segments; each slice " + "represents a percentage of the whole. Note that the height of the component or " + "its parent needs to be set explicitly.", docFolder: "Charts/PieChart", props: { data: { description: "The data to be displayed in the chart. Needs to be an array of objects.", }, nameKey: { description: "Specifies the key in the data objects that will be used to label the different data series.", valueType: "string", }, dataKey: { description: "This property specifies the key in the data objects that will be used to render the chart.", valueType: "string", }, showLabel: { description: "Toggles whether to show labels (\`true\`) or not (\`false\`).", valueType: "boolean", defaultValue: defaultProps.showLabel, }, showLabelList: { description: "Whether to show labels in a list (\`true\`) or not (\`false\`).", valueType: "boolean", defaultValue: defaultProps.showLabelList, }, labelListPosition: { description: "The position of the label list.", valueType: "string", defaultValue: defaultProps.labelListPosition, availableValues: LabelPositionValues, }, outerRadius: d( "The outer radius of the pie chart, can be a number or a string (e.g., '100%').", ), showLegend: { description: "Toggles whether to show legend (\`true\`) or not (\`false\`).", valueType: "boolean", defaultValue: defaultProps.showLegend, } }, themeVars: parseScssVar(styles.themeVars), defaultThemeVars: { "textColor-labelList-PieChart": "$textColor-primary", }, }); export const pieChartComponentRenderer = createComponentRenderer( COMP, PieChartMd, ({ extractValue, node, className, renderChild }) => { return ( <PieChart showLabelList={extractValue.asOptionalBoolean(node.props?.showLabelList)} labelListPosition={extractValue.asOptionalString(node.props?.labelListPosition) as LabelPosition} data={extractValue(node.props?.data)} className={className} showLabel={extractValue.asOptionalBoolean(node.props?.showLabel)} showLegend={extractValue.asOptionalBoolean(node.props?.showLegend)} dataKey={extractValue(node.props?.dataKey)} nameKey={extractValue(node.props?.nameKey)} > {renderChild(node.children)} </PieChart> ); }, ); ``` -------------------------------------------------------------------------------- /xmlui/src/components/Tooltip/Tooltip.module.scss: -------------------------------------------------------------------------------- ```scss @use "../../components-core/theming/themes" as t; // --- This code snippet is required to collect the theme variables used in this module $themeVars: (); @function createThemeVar($componentVariable) { $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global; @return t.getThemeVar($themeVars, $componentVariable); } $component: "Tooltip"; $themeVars: t.composePaddingVars($themeVars, $component); $themeVars: t.composeBorderVars($themeVars, $component); // --- Theme variables for Tooltip component $backgroundColor-Tooltip: createThemeVar("backgroundColor-#{$component}"); $textColor-Tooltip: createThemeVar("textColor-#{$component}"); $fontSize-Tooltip: createThemeVar("fontSize-#{$component}"); $lineHeight-Tooltip: createThemeVar("lineHeight-#{$component}"); $boxShadow-Tooltip: createThemeVar("boxShadow-#{$component}"); $fill-arrow-Tooltip: createThemeVar("fill-arrow-#{$component}"); $stroke-arrow-Tooltip: createThemeVar("stroke-arrow-#{$component}"); $strokeWidth-arrow-Tooltip: createThemeVar("strokeWidth-arrow-#{$component}"); $animationDuration-Tooltip: createThemeVar("animationDuration-#{$component}"); $animation-Tooltip: createThemeVar("animation-#{$component}"); /* Tooltip styles */ .hiddenTrigger { position: fixed; left: 0; top: 0; width: 1px; height: 1px; pointer-events: none; z-index: -1; opacity: 0; } .content { @include t.paddingVars($themeVars, $component); @include t.borderVars($themeVars, $component); font-size: $fontSize-Tooltip; line-height: $lineHeight-Tooltip; color: $textColor-Tooltip; background-color: $backgroundColor-Tooltip; box-shadow: $boxShadow-Tooltip; user-select: none; animation-duration: $animationDuration-Tooltip; animation-timing-function: $animation-Tooltip; will-change: transform, opacity; z-index: 9999; } .content[data-state='delayed-open'][data-side='top'] { animation-name: slideDownAndFade; } .content[data-state='delayed-open'][data-side='right'] { animation-name: slideLeftAndFade; } .content[data-state='delayed-open'][data-side='bottom'] { animation-name: slideUpAndFade; } .content[data-state='delayed-open'][data-side='left'] { animation-name: slideRightAndFade; } .arrow { fill: $fill-arrow-Tooltip; stroke: $stroke-arrow-Tooltip; stroke-width: $strokeWidth-arrow-Tooltip; } @keyframes slideUpAndFade { from { opacity: 0; transform: translateY(2px); } to { opacity: 1; transform: translateY(0); } } @keyframes slideRightAndFade { from { opacity: 0; transform: translateX(-2px); } to { opacity: 1; transform: translateX(0); } } @keyframes slideDownAndFade { from { opacity: 0; transform: translateY(-2px); } to { opacity: 1; transform: translateY(0); } } @keyframes slideLeftAndFade { from { opacity: 0; transform: translateX(2px); } to { opacity: 1; transform: translateX(0); } } // --- We export the theme variables to add them to the component renderer :export { themeVars: t.json-stringify($themeVars); } ``` -------------------------------------------------------------------------------- /xmlui/tests/parsers/scripting/eval-tree-arrow.test.ts: -------------------------------------------------------------------------------- ```typescript import { describe, expect, it } from "vitest"; import { evalBindingExpression } from "../../../src/components-core/script-runner/eval-tree-sync"; import { createEvalContext } from "./test-helpers"; describe("Evaluate arrow expressions", () => { it("Arrow #1", () => { // --- Arrange const source = "(x => 2 * x)(4)"; const context = createEvalContext({}); // --- Act const value = evalBindingExpression(source, context); // --- Arrange expect(value).equal(8); }); it("Arrow #2", () => { // --- Arrange const source = "((x, y) => x + y)(1, 2)"; const context = createEvalContext({}); // --- Act const value = evalBindingExpression(source, context); // --- Arrange expect(value).equal(3); }); it("Arrow #3", () => { // --- Arrange const source = "((x, y) => { return x + y })(1, 2)"; const context = createEvalContext({}); // --- Act const value = evalBindingExpression(source, context); // --- Arrange expect(value).equal(3); }); it("Arrow #4", () => { // --- Arrange const source = "(x => (++x.h))(count)"; const context = createEvalContext({ localContext: { count: { h: 3 }, }, }); // --- Act const value = evalBindingExpression(source, context); // --- Arrange expect(value).equal(4); }); it("Arrow #5", () => { // --- Arrange const source = "(x => x += 2)(count)"; const context = createEvalContext({ localContext: { count: 3, }, }); // --- Act const value = evalBindingExpression(source, context); // --- Arrange expect(value).equal(5); }); it("Arrow #6", () => { // --- Arrange const source = "(x => x += 2)(count + 4)"; const context = createEvalContext({ localContext: { count: 3, }, }); // --- Act const value = evalBindingExpression(source, context); // --- Arrange expect(value).equal(9); }); it("Arrow #7", () => { // --- Arrange const source = "[1,2,3,4,5].filter(x => x % 2 === 0)[1]"; const context = createEvalContext({ localContext: { count: 3, }, }); // --- Act const value = evalBindingExpression(source, context); // --- Arrange expect(value).equal(4); }); it("Arrow #8", () => { // --- Arrange const source = "containsArray.array.filter(item => item % 2 === 0)[1]"; const context = createEvalContext({ localContext: { containsArray: { array: [5, 4, 3, 2, 1], }, }, }); // --- Act const value = evalBindingExpression(source, context); // --- Arrange expect(value).equal(2); }); it("Arrow #9", () => { // --- Arrange const source = "array.reduce((acc, item) => acc + item, 0)"; const context = createEvalContext({ localContext: { array: [5, 4, 3, 2, 1], }, }); // --- Act const value = evalBindingExpression(source, context); // --- Arrange expect(value).equal(15); }); }); ``` -------------------------------------------------------------------------------- /xmlui/src/components-core/script-runner/asyncProxy.ts: -------------------------------------------------------------------------------- ```typescript /** * Gets a proxy function for one that does not support async operations * @param fn Function to replace with a proxy * @param origArgs Original function arguments * @param context Function context ("this" of the function invocation) * @return The proxy, if found; otherwise the original function */ export function getAsyncProxy(fn: Function, origArgs: any[], context: any): Function { const proxyFn = asyncProxies.get(fn); if (!proxyFn) return fn; origArgs.unshift(context); return proxyFn; } // Async implementations for JavaScript functions that do not support async arguments const asyncProxies = new Map<Function, Function>(); asyncProxies.set(Array.prototype.filter, asyncFilter); asyncProxies.set(Array.prototype.forEach, asyncForEach); asyncProxies.set(Array.prototype.map, asyncMap); asyncProxies.set(Array.prototype.every, asyncEvery); asyncProxies.set(Array.prototype.findIndex, asyncFindIndex); asyncProxies.set(Array.prototype.find, asyncFind); asyncProxies.set(Array.prototype.flatMap, asyncFlatMap); asyncProxies.set(Array.prototype.some, asyncSome); // The async implementation of Array.prototype.some async function asyncSome(arr: any[], predicate: (...args: any[]) => boolean) { const results = await Promise.all(arr.map(predicate)); return arr.some((_v, index) => results[index]); } // The async implementation of Array.prototype.filter async function asyncFilter(arr: any[], predicate: (...args: any[]) => boolean) { const results = await Promise.all(arr.map(predicate)); return arr.filter((_v, index) => results[index]); } // The async implementation of Array.prototype.forEach async function asyncForEach(arr: any[], predicate: (...args: any[]) => void) { for (let i = 0; i < arr.length; i++) { await predicate(arr[i], i, arr); } } // The async implementation of Array.prototype.map async function asyncMap(arr: any[], predicate: (...args: any[]) => Promise<any[]>) { const result = []; for (let i = 0; i < arr.length; i++) { result.push(await predicate(arr[i], i, arr)); } return result; } // The async implementation of Array.prototype.asyncEvery async function asyncEvery(arr: any[], callback: (...args: any[]) => any) { const results = await Promise.all(arr.map(callback)); return results.every((_v, index) => results[index]); } // The async implementation of Array.prototype.asyncFind async function asyncFind(arr: any[], predicate: (...args: any[]) => boolean) { const results = await Promise.all(arr.map(predicate)); return arr.find((_v, index) => results[index]); } // The async implementation of Array.prototype.asyncFindIndex async function asyncFindIndex(arr: any[], predicate: (...args: any[]) => boolean) { const results = await Promise.all(arr.map(predicate)); return arr.findIndex((_v, index) => results[index]); } // The async implementation of Array.prototype.asyncFlatMap async function asyncFlatMap(arr: any[], predicate: (...args: any[]) => boolean) { const results = await Promise.all(arr.map(predicate)); return arr.flatMap((_v, index) => results[index]); } ``` -------------------------------------------------------------------------------- /docs/public/pages/user-defined-components.md: -------------------------------------------------------------------------------- ```markdown # User-defined components You can define your own components, pass properties to them, and use them interchangeably with core components. When you find yourself writing a component with more than a few dozen lines of XMLUI markup, consider refactoring in order to name and package the key blocks of code. This strategy not only enables reuse but, just as importantly, ensures that the refactored component will be easy to read and maintain. Such refactoring requires you to create and name new `.xmlui` files, identify inline elements that need to be passed as properties from a refactored component, and use those properties in newly-created subcomponents. Historically that kind of overhead has been a disincentive to refactoring in any programming environment. Now you can often outsource that gruntwork to AI assistants. It works particularly well with XMLUI, we use this strategy extensively, and we highly recommend it. Here's a simple component to package a name/value pair. ```xmlui-pg display noHeader ---app display <App> <NameValue name="Mary" value="123" /> </App> ---comp display <Component name="NameValue"> <Card width="20%"> <Text>Name: { $props.name} </Text> <Text>Value: { $props.value} </Text> </Card> </Component> ``` The component's name must start with an uppercase letter followed by letters, digits, the underscore (`_`), or the dollar sign (`$`) character. Components must be placed into separate files in the `components` folder within the app's root folder. The component's name must match its filename. Here's how you can define default values for properties. ```xmlui <Component name="NameValue"> <Card width="20%"> <Text>Name: { $props.name ?? '[no name]' } </Text> <Text>Value: { $props.value ?? '[no value]' } </Text> </Card> </Component> ``` ## Events The `<IncButton>` component increments its value for every click, and notifies its environment by firing an event. The event's handler receives the current counter as an event parameter. ```xmlui-pg noHeader ---app display <App> <Card width="30%"> <variable name="text" value=""/> <IncButton onIncremented="(clickCount) => text += ' ' + clickCount" /> <Text value="{text}" /> </Card> </App> ---comp display <Component name="IncButton"> <variable name="count" value="{0}" /> <Button label="Click to increment: {count}" onClick="count++; emitEvent('incremented', count)" /> </Component> ``` ## Methods The `<UsingInternalModal>` component exports the `open` method of the `ModalDialog` that it defines. ```xmlui-pg noHeader ---app display <App height="300px" > <UsingInternalModal id="component"/> <Button label="Open the internal dialog" onClick="component.openDialog()" /> </App> ---comp display <Component name="UsingInternalModal"> <ModalDialog id="dialog" title="Example Dialog"> <Button label="Close Dialog" onClick="dialog.close()" /> </ModalDialog> <H1>Using an Internal Modal Dialog</H1> <method name="openDialog"> console.log('internal method called') dialog.open(); </method> </Component> ``` ``` -------------------------------------------------------------------------------- /xmlui/src/components/Accordion/Accordion.module.scss: -------------------------------------------------------------------------------- ```scss @use "../../components-core/theming/themes" as t; // --- This code snippet is required to collect the theme variables used in this module $themeVars: (); @function createThemeVar($componentVariable) { $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global; @return t.getThemeVar($themeVars, $componentVariable); } $component: "Accordion"; $themeVars: t.composeBorderVars($themeVars, $component); $themeVars: t.composePaddingVars($themeVars, $component); $borderRadius: createThemeVar("borderRadius-#{$component}"); @layer components { .root { overflow: hidden; width: 100%; //box-shadow: 0 2px 10px var(--black-a4); @include t.borderVars($themeVars, $component); @include t.paddingVars($themeVars, $component); } .item { overflow: hidden; margin-top: 1px; box-sizing: border-box; &:first-child { margin-top: 0; border-top-left-radius: $borderRadius; border-top-right-radius: $borderRadius; } &:last-child { border-bottom-left-radius: $borderRadius; border-bottom-right-radius: $borderRadius; } &:focus-within { position: relative; z-index: 1; } } .header { display: flex; margin: 0; } .trigger { font-family: inherit; flex: 1; display: inline-flex; align-items: createThemeVar("verticalAlignment-header-#{$component}"); justify-content: space-between; line-height: 1; box-sizing: border-box; font-size: createThemeVar("fontSize-header-#{$component}"); font-weight: createThemeVar("fontWeight-header-#{$component}"); font-style: createThemeVar("fontStyle-header-#{$component}"); padding: createThemeVar("paddingVertical-header-#{$component}") createThemeVar("paddingHorizontal-header-#{$component}"); background-color: createThemeVar("backgroundColor-header-#{$component}"); color: createThemeVar("color-header-#{$component}"); &:hover { background-color: createThemeVar("backgroundColor-header-#{$component}-hover"); } &.triggerStart { flex-direction: row-reverse; } } .contentWrapper { overflow: hidden; color: createThemeVar("color-content-#{$component}"); background-color: createThemeVar("backgroundColor-content-#{$component}"); &[data-state='open'] { animation: slideDown 300ms cubic-bezier(0.87, 0, 0.13, 1); } &[data-state='closed'] { animation: slideUp 300ms cubic-bezier(0.87, 0, 0.13, 1); } } .content { padding: t.$space-2; } .chevron { width: createThemeVar("width-icon-#{$component}"); height: createThemeVar("height-icon-#{$component}"); color: createThemeVar("color-icon-#{$component}"); } @keyframes slideDown { from { height: 0; } to { height: var(--radix-accordion-content-height); } } @keyframes slideUp { from { height: var(--radix-accordion-content-height); } to { height: 0; } } } // --- We export the theme variables to add them to the component renderer :export { themeVars: t.json-stringify($themeVars); } ``` -------------------------------------------------------------------------------- /packages/xmlui-animations/src/FadeAnimation.tsx: -------------------------------------------------------------------------------- ```typescript import { createComponentRenderer, createMetadata } from "xmlui"; import { Animation, defaultProps } from "./AnimationNative"; const COMP = "FadeAnimation"; const defaultAnimationValues = { from: 0, to: 1, }; export const FadeAnimationMd = createMetadata({ status: "experimental", description: `The \`${COMP}\` component represents an animation that fades the content with adjustable opacity values.`, docFolder: "src", props: { from: { description: "Sets the initial opacity of the content." + "If the `to` property is not set, the initial opacity set here will be used as the final opacity.", valueType: "number", defaultValue: defaultAnimationValues.from, }, to: { description: "Sets the final opacity of the content." + "If the `from` property is not set, the initial opacity set here will be used as the final opacity.", valueType: "number", defaultValue: defaultAnimationValues.to, }, animateWhenInView: { description: `Indicates whether the animation should start when the component is in view`, valueType: "boolean", }, duration: { description: `The duration of the animation in milliseconds`, valueType: "number", }, reverse: { description: `Indicates whether the animation should run in reverse`, defaultValue: defaultProps.reverse, valueType: "boolean", }, loop: { description: `Indicates whether the animation should loop`, defaultValue: defaultProps.loop, valueType: "boolean", }, delay: { description: `The delay before the animation starts in milliseconds`, defaultValue: defaultProps.delay, valueType: "number", }, }, events: { started: { description: `Event fired when the animation starts` }, stopped: { description: `Event fired when the animation stops` }, }, apis: { start: { description: `Starts the animation` }, stop: { description: `Stops the animation` }, }, }); export const fadeAnimationRenderer = createComponentRenderer( "FadeAnimation", FadeAnimationMd, ({ node, renderChild, extractValue, registerComponentApi, lookupEventHandler }) => { return ( <Animation registerComponentApi={registerComponentApi} animation={{ from: { opacity: extractValue.asOptionalNumber(node.props.from, defaultAnimationValues.from), }, to: { opacity: extractValue.asOptionalNumber(node.props.to, defaultAnimationValues.to), }, }} duration={extractValue.asOptionalNumber(node.props.duration)} onStop={lookupEventHandler("stopped")} onStart={lookupEventHandler("started")} animateWhenInView={extractValue.asOptionalBoolean(node.props.animateWhenInView)} reverse={extractValue.asOptionalBoolean(node.props.reverse)} loop={extractValue.asOptionalBoolean(node.props.loop)} delay={extractValue.asOptionalNumber(node.props.delay)} > {renderChild(node.children)} </Animation> ); }, ); ``` -------------------------------------------------------------------------------- /xmlui/tests/parsers/xmlui/transform.escape.test.ts: -------------------------------------------------------------------------------- ```typescript import { describe, expect, it } from "vitest"; import { ComponentDef } from "../../../src/abstractions/ComponentDefs"; import { transformSource } from "./xmlui"; describe("Xmlui transform - no backslash escape", () => { it("backslash", () => { const cd = transformSource("<Stack a='\\\\' />") as ComponentDef; expect((cd.props as any).a).equal("\\\\"); }); it("newline", () => { const cd = transformSource("<Stack a='\\n' />") as ComponentDef; expect((cd.props as any).a).equal("\\n"); }); }); describe("Xmlui transform - entity escape", () => { it("&", () => { const cd = transformSource("<Stack>&</Stack>") as ComponentDef; expect((cd.children![0].props as any).value).toEqual("&"); }); it(">", () => { const cd = transformSource("<Stack>></Stack>") as ComponentDef; expect((cd.children![0].props as any).value).toEqual(">"); }); it("<", () => { const cd = transformSource("<Stack><</Stack>") as ComponentDef; expect((cd.children![0].props as any).value).toEqual("<"); }); it("'", () => { const cd = transformSource("<Stack>'</Stack>") as ComponentDef; expect((cd.children![0].props as any).value).toEqual("'"); }); it(""", () => { const cd = transformSource("<Stack>"</Stack>") as ComponentDef; expect((cd.children![0].props as any).value).toEqual('"'); }); it(" ", () => { const cd = transformSource("<Stack> </Stack>") as ComponentDef; expect((cd.children![0].props as any).value).toEqual("\xa0\xa0\xa0"); }); it("combined", () => { const cd = transformSource("<Stack>"&abc><</Stack>") as ComponentDef; expect((cd.children![0].props as any).value).toEqual('"&abc><'); }); it("& ignored inside CData", () => { const cd = transformSource("<Stack><![CDATA[&]]></Stack>") as ComponentDef; expect((cd.children![0].props as any).value).toEqual("&"); }); it("attr - &", () => { const cd = transformSource("<H1 value='&' />") as ComponentDef; expect((cd.props as any).value).toEqual("&"); }); it("attr - >", () => { const cd = transformSource("<H1 value='>' />") as ComponentDef; expect((cd.props as any).value).toEqual(">"); }); it("attr - ", () => { const cd = transformSource("<H1 value=' ' />") as ComponentDef; expect((cd.props as any).value).toEqual("\xa0\xa0\xa0"); }); it("attr - <", () => { const cd = transformSource("<H1 value='<' />") as ComponentDef; expect((cd.props as any).value).toEqual("<"); }); it("attr - '", () => { const cd = transformSource("<H1 value=''' />") as ComponentDef; expect((cd.props as any).value).toEqual("'"); }); it("attr - "", () => { const cd = transformSource("<H1 value='"' />") as ComponentDef; expect((cd.props as any).value).toEqual('"'); }); it("attr - combined", () => { const cd = transformSource("<H1 value='"&abc>' />") as ComponentDef; expect((cd.props as any).value).toEqual('"&abc>'); }); }); ``` -------------------------------------------------------------------------------- /docs/public/resources/files/products.json: -------------------------------------------------------------------------------- ```json [{"id":14,"name":"API Integration","description":"Third-party API integration services","price":105,"created_at":"2022-08-10T14:06:49.463Z"}, {"id":22,"name":"Brand Strategy Consulting","description":"Strategic brand positioning and messaging","price":115,"created_at":"2025-06-06 00:02:08"}, {"id":8,"name":"Cloud Migration","description":"Migrate systems to cloud infrastructure","price":130,"created_at":"2022-10-20T02:24:30.991Z"}, {"id":6,"name":"Content Creation","description":"Blog posts and content marketing","price":65,"created_at":"2023-02-27T14:54:28.774Z"}, {"id":13,"name":"Data Analysis","description":"Data analytics and reporting","price":90,"created_at":"2022-05-10T21:05:04.425Z"}, {"id":7,"name":"Database Design","description":"Database architecture and optimization","price":110,"created_at":"2022-02-06T12:47:47.397Z"}, {"id":19,"name":"Domain Registration (Yearly)","description":"Domain name registration and renewal","price":15,"created_at":"2023-03-08T06:10:10.884Z"}, {"id":21,"name":"E-commerce Development","description":"Online store development and integration","price":135,"created_at":"2025-06-06 00:02:08"}, {"id":12,"name":"Email Marketing","description":"Email campaign setup and management","price":60,"created_at":"2022-03-07T00:58:53.758Z"}, {"id":11,"name":"Logo Design","description":"Professional logo design services","price":80,"created_at":"2022-06-29T23:21:01.184Z"}, {"id":4,"name":"Mobile App Development","description":"Native mobile application development","price":145,"created_at":"2022-04-27T01:31:14.995Z"}, {"id":15,"name":"Project Management","description":"Professional project management services","price":95,"created_at":"2022-07-29T06:59:59.756Z"}, {"id":16,"name":"QA Testing","description":"Software quality assurance testing","price":80,"created_at":"2022-05-19T05:35:07.028Z"}, {"id":5,"name":"SEO Optimization","description":"Search engine optimization services","price":85,"created_at":"2022-09-27T13:12:58.057Z"}, {"id":9,"name":"Security Audit","description":"Comprehensive security assessment","price":150,"created_at":"2022-02-26T22:17:19.018Z"}, {"id":3,"name":"Server Maintenance","description":"Monthly server maintenance and updates","price":75,"created_at":"2022-02-27T00:11:24.402Z"}, {"id":10,"name":"Social Media Management","description":"Monthly social media account management","price":70,"created_at":"2022-03-17T19:11:09.894Z"}, {"id":17,"name":"Tech Consultation","description":"Technology consultation services (hourly)","price":125,"created_at":"2022-07-09T22:04:19.424Z"}, {"id":2,"name":"UI/UX Design","description":"User interface and experience design","price":95,"created_at":"2022-02-08T19:22:16.697Z"}, {"id":20,"name":"Video Production","description":"Professional video production services","price":175,"created_at":"2023-05-07T22:52:06.967Z"}, {"id":1,"name":"Web Development","description":"Full stack web application development","price":120,"created_at":"2022-12-24T10:09:20.570Z"}, {"id":18,"name":"Website Hosting (Monthly)","description":"Managed website hosting services","price":45,"created_at":"2022-02-28T15:52:17.105Z"}] ``` -------------------------------------------------------------------------------- /xmlui/src/components/FlowLayout/FlowLayout.tsx: -------------------------------------------------------------------------------- ```typescript import styles from "./FlowLayout.module.scss"; import { createComponentRenderer } from "../../components-core/renderers"; import { isComponentDefChildren } from "../../components-core/utils/misc"; import { NotAComponentDefError } from "../../components-core/EngineError"; import { parseScssVar } from "../../components-core/theming/themeVars"; import { FlowItemBreak, FlowItemWrapper, FlowLayout, defaultProps } from "./FlowLayoutNative"; import { createMetadata } from "../metadata-helpers"; const COMP = "FlowLayout"; export const FlowLayoutMd = createMetadata({ status: "stable", description: "`FlowLayout` positions content in rows with automatic wrapping. When items " + "exceed the available horizontal space, they automatically wrap to a new line.", props: { gap: { description: `This property defines the gap between items in the same row and between rows. The ${COMP} ` + `component creates a new row when an item is about to overflow the current row.`, type: "string", defaultValue: "$gap-normal", }, columnGap: { description: "The \`columnGap\` property specifies the space between items in a single row; it overrides " + "the \`gap\` value.", defaultValue: defaultProps.columnGap, }, rowGap: { description: `The \`rowGap\` property specifies the space between the ${COMP} rows; it overrides ` + `the \`gap\` value.`, defaultValue: defaultProps.rowGap, }, }, themeVars: parseScssVar(styles.themeVars), }); export const flowLayoutComponentRenderer = createComponentRenderer( COMP, FlowLayoutMd, ({ node, renderChild, className, extractValue }) => { if (!isComponentDefChildren(node.children)) { throw new NotAComponentDefError(); } const columnGap = extractValue.asSize(node.props?.columnGap) || extractValue.asSize(node.props?.gap) || extractValue.asSize("$space-4"); const rowGap = extractValue.asSize(node.props?.rowGap) || extractValue.asSize(node.props?.gap) || extractValue.asSize("$space-4"); return ( <FlowLayout className={className} columnGap={columnGap} rowGap={rowGap}> {renderChild(node.children, { wrapChild: ({ node, extractValue }, renderedChild, hints) => { if (hints?.opaque) { return renderedChild; } // Handle SpaceFiller as flow item break if (node.type === "SpaceFiller") { return <FlowItemBreak force={true} />; } const width = extractValue((node.props as any)?.width); const minWidth = extractValue((node.props as any)?.minWidth); const maxWidth = extractValue((node.props as any)?.maxWidth); return ( <FlowItemWrapper width={width} minWidth={minWidth} maxWidth={maxWidth} forceBreak={node.type === "SpaceFiller"} > {renderedChild} </FlowItemWrapper> ); }, })} </FlowLayout> ); }, ); ``` -------------------------------------------------------------------------------- /xmlui/src/components/Markdown/CodeText.module.scss: -------------------------------------------------------------------------------- ```scss @use "../../components-core/theming/themes" as t; $themeVars: (); @function createThemeVar($componentVariable) { $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global; @return t.getThemeVar($themeVars, $componentVariable); } $component: "Text"; @mixin textVariant($variant) { $variantName: if($variant == "", "#{$component}", "#{$component}-#{$variant}"); $themeVars: t.composePaddingVars($themeVars, $variantName); $themeVars: t.composeBorderVars($themeVars, $variantName); $themeVars: t.composeTextVars($themeVars, $variantName, $component); @include t.paddingVars($themeVars, $variantName); @include t.borderVars($themeVars, $variantName); @include t.textVars($themeVars, $variantName); margin-top: createThemeVar("marginTop-#{$variantName}"); margin-bottom: createThemeVar("marginBottom-#{$variantName}"); margin-left: createThemeVar("marginLeft-#{$variantName}"); margin-right: createThemeVar("marginRight-#{$variantName}"); vertical-align: createThemeVar("verticalAlignment-#{$variantName}"); } @layer components { .codeText { overflow: hidden; text-overflow: ellipsis; margin: 0; padding: 0; min-height: fit-content; // --- Styling <pre><code> elements // codefence/<pre> elements are block level by default, even in another text element // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/pre .text.codefence { display: block; margin-top: createThemeVar("marginTop-#{$component}-codefence"); margin-bottom: createThemeVar("marginBottom-#{$component}-codefence"); height: 100%; } // For inline code :not(.text.codefence) > &.code, :not(.text.codefence) > &code { @include textVariant("code"); } // For code blocks using <![CDATA[ + <code> tags &.codefence:has(> .text.code), &.codefence:has(> code) { @include textVariant("codefence"); // TEMP: solution for a demo - need to re-evaluate how to truncate/wrap code blocks overflow-x: auto; text-overflow: initial; } // TEMP: experimental - may be removed &.codefence:has(> code > span) { padding-inline: 0; } &.codefence > code > span:not([class*="numbered"]) { padding-inline: t.$space-3; } &.codefence > code { font-family: createThemeVar("fontFamily-#{$component}-codefence"); //white-space: pre-wrap; font-family: createThemeVar("fontFamily-#{$component}-codefence"); //display: inline-block; width: 100%; } &.codefence > code::before { counter-reset: listing; } &.codefence > code > span { display: inline-block; width: 100%; } &.codefence > code > span[class*="numbered"] { counter-increment: listing; } &.codefence > code > span[class*="numbered"]::before { content: counter(listing); display: inline-block; width: t.$space-12; padding-left: auto; padding-right: t.$space-5; margin-left: auto; text-align: right; color: t.$textColor-secondary; } } } :export { themeVars: t.json-stringify($themeVars); } ``` -------------------------------------------------------------------------------- /packages/xmlui-website-blocks/src/Carousel/Carousel.module.scss: -------------------------------------------------------------------------------- ```scss @use "xmlui/themes.scss" as t; // --- This code snippet is required to collect the theme variables used in this module $themeVars: ( ); @function createThemeVar($componentVariable) { $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global; @return t.getThemeVar($themeVars, $componentVariable); } $component: "Carousel"; @layer components { .carousel { position: relative; overflow: hidden; width: createThemeVar("width-#{$component}"); height: createThemeVar("height-#{$component}"); justify-content: center; align-items: center; } .innerWrapper { display: flex; width: 100%; height: 100%; flex-direction: row; justify-content: center; } .carouselContentWrapper { overflow: hidden; height: 100%; } .carouselContent { display: flex; height: 100%; } .vertical { flex-direction: column; } .carouselItem { overflow: hidden; min-width: 0; flex-shrink: 0; flex-grow: 0; flex-basis: 100%; } .controls { z-index: 99; display: flex; right: 1rem; bottom: 1rem; position: absolute; gap: .6rem; } .controlButton { transition: all .3s; justify-content: center; align-items: center; display: inline-flex; height: createThemeVar("height-control-#{$component}"); width: createThemeVar("width-control-#{$component}"); color: createThemeVar("textColor-control-#{$component}"); background-color: createThemeVar("backgroundColor-control-#{$component}"); ; border-radius: createThemeVar("borderRadius-control-#{$component}"); } .controlButton:hover { background-color: createThemeVar("backgroundColor-control-hover-#{$component}"); color: createThemeVar("textColor-control-hover-#{$component}"); } .controlButton:active { background-color: createThemeVar("backgroundColor-control-active-#{$component}"); color: createThemeVar("textColor-control-active-#{$component}"); } .controlButton:disabled { color: createThemeVar("textColor-control-disabled-#{$component}"); background-color: createThemeVar("backgroundColor-control-disabled-#{$component}"); } .indicators { position: absolute; bottom: 1rem; left: 50%; transform: translateX(-50%); } .indicator { transition: all .3s; width: createThemeVar("width-indicator-#{$component}"); height: createThemeVar("height-indicator-#{$component}"); border-radius: 2px; margin: 0 1px; color: createThemeVar("textColor-indicator-#{$component}"); background-color: createThemeVar("backgroundColor-indicator-#{$component}"); &:hover { background-color: createThemeVar("backgroundColor-indicator-hover-#{$component}"); color: createThemeVar("textColor-indicator-hover-#{$component}"); } &.active { background-color: createThemeVar("backgroundColor-indicator-active-#{$component}"); color: createThemeVar("textColor-indicator-active-#{$component}"); } } } // --- We export the theme variables to add them to the component renderer :export { themeVars: t.json-stringify($themeVars); } ``` -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- ```json { "$schema": "https://turborepo.com/schema.json", "globalEnv": ["CI"], "ui": "tui", "envMode": "loose", "tasks": { "build:bin": { "outputs": ["dist/scripts/**"] }, "build:xmlui": { "outputs": ["dist/lib/**"] }, "build:xmlui-standalone": { "outputs": ["dist/standalone/**"] }, "build:xmlui-metadata": { "inputs": ["$TURBO_DEFAULT$", "!src/language-server/xmlui-metadata-generated.mjs"], "outputs": ["dist/metadata/**"] }, "gen:langserver-metadata": { "dependsOn": ["build:xmlui-metadata"], "outputs": ["src/language-server/xmlui-metadata-generated.mjs"] }, "build:extension": { "dependsOn": ["^build:extension"], "outputs": ["dist/**"] }, "build:meta": { "outputs": ["dist/**"] }, "xmlui-vscode#build:vsix": { "dependsOn": ["xmlui-vscode#build"], "outputs": ["xmlui-vscode-*.vsix"] }, "xmlui-vscode#build": { "dependsOn": ["^gen:langserver-metadata"], "outputs": ["dist/**"] }, "build:xmlui-all": { "dependsOn": [ "build:bin", "build:xmlui-metadata", "build:xmlui", "build:xmlui-standalone", "build:extension" ], "outputs": ["dist/**"] }, "gen:releases": { "env": ["GITHUB_TOKEN", "DOCS_XMLUI_MAX_RELEASES_LENGTH"], "outputs": ["public/resources/files/releases.json"] }, "gen:download-latest-xmlui-release": { "outputs": ["public/resources/files/for-download/xmlui/xmlui-standalone.umd.js"] }, "generate-docs": { "outputs": ["scripts/generate-docs/metadata/**", "$TURBO_ROOT$/docs/content/**"], "inputs": ["$TURBO_DEFAULT$"], "dependsOn": ["build:xmlui-metadata", "build:meta"] }, "generate-docs-summaries": { "outputs": ["$TURBO_ROOT$/docs/content/**"], "inputs": ["$TURBO_DEFAULT$", "dist/metadata/**"], "dependsOn": ["generate-docs"] }, "build:docs": { "dependsOn": [ "^build:extension", "^build:xmlui", "gen:releases", "gen:download-latest-xmlui-release", "generate-docs-summaries" ], "outputs": ["dist/**"] }, "build:blog": { "dependsOn": [ "^build:extension", "^build:xmlui" ], "outputs": ["dist/**"] }, "build:xmlui-test-bed": { "dependsOn": ["build:extension"], "outputs": ["src/testing/infrastructure/dist/**"] }, "build:test-bed": { "dependsOn": ["build:extension"], "outputs": ["dist/**"] }, "test:unit": {}, "test:e2e-smoke": { "dependsOn": ["build:test-bed", "build:xmlui-test-bed"], "outputs": ["playwright-report/**"] }, "test:e2e-non-smoke": { "dependsOn": ["build:test-bed", "build:xmlui-test-bed", "test:e2e-smoke"], "outputs": ["playwright-report/**"] }, "test:xmlui-all": { "dependsOn": ["test:unit", "test:e2e-non-smoke"], "outputs": ["playwright-report/**"] }, "test:xmlui-smoke": { "dependsOn": ["test:unit", "test:e2e-smoke"], "outputs": ["playwright-report/**"] } } } ``` -------------------------------------------------------------------------------- /xmlui/src/components/Splitter/Splitter.module.scss: -------------------------------------------------------------------------------- ```scss @use "../../components-core/theming/themes" as t; // --- This code snippet is required to collect the theme variables used in this module $themeVars: (); @function createThemeVar($componentVariable) { $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global; @return t.getThemeVar($themeVars, $componentVariable); } $component: "Splitter"; $boxShadow-Splitter: createThemeVar("boxShadow-#{$component}"); $backgroundColor-Splitter: createThemeVar("backgroundColor-#{$component}"); $borderRadius-Splitter: createThemeVar("borderRadius-#{$component}"); $borderColor-Splitter: createThemeVar("borderColor-#{$component}"); $borderWidth-Splitter: createThemeVar("borderWidth-#{$component}"); $borderStyle-Splitter: createThemeVar("borderStyle-#{$component}"); $border-Splitter: createThemeVar("border-#{$component}"); $backgroundColor-resizer-Splitter: createThemeVar("backgroundColor-resizer-#{$component}"); $thickness-resizer-Splitter: createThemeVar("thickness-resizer-#{$component}"); $cursor-resizer-horizontal-Splitter: createThemeVar("cursor-resizer-horizontal-#{$component}"); $cursor-resizer-vertical-Splitter: createThemeVar("cursor-resizer-vertical-#{$component}"); @layer components { .splitter { overflow: hidden; position: relative; flex-wrap: nowrap; display: flex; height: 100%; width: 100%; box-shadow: $boxShadow-Splitter; background-color: $backgroundColor-Splitter; border-radius: $borderRadius-Splitter; border: $border-Splitter $borderStyle-Splitter $borderColor-Splitter; user-select: none; &.vertical { flex-direction: column; } &.horizontal { flex-direction: row; } } .panel { overflow: hidden; display: flex; flex-direction: column; flex: 1; } .primaryPanel { flex-shrink: 0; flex-grow: 0; overflow: auto; order: 1; } .secondaryPanel { flex: 1; overflow: auto; order: 3; } .resizer { order: 2; overflow: hidden; background-color: t.useVar($backgroundColor-resizer-Splitter, transparent); &.vertical { cursor: t.useVar($cursor-resizer-vertical-Splitter, ns-resize); height: t.useVar($thickness-resizer-Splitter, 5px); } &.horizontal { cursor: t.useVar($cursor-resizer-horizontal-Splitter, ew-resize); width: t.useVar($thickness-resizer-Splitter, 5px); } } .floatingResizer { position: absolute; z-index: 1000; opacity: 0; overflow: hidden; transition: opacity 0.2s; background-color: t.useVar($backgroundColor-resizer-Splitter, transparent); &.vertical { cursor: t.useVar($cursor-resizer-vertical-Splitter, ns-resize); width: 100%; height: t.useVar($thickness-resizer-Splitter, 5px); transform: translateY(-50%); } &.horizontal { cursor: t.useVar($cursor-resizer-horizontal-Splitter, ew-resize); width: t.useVar($thickness-resizer-Splitter, 5px); height: 100%; transform: translateX(-50%); } } } // --- We export the theme variables to add them to the component renderer :export { themeVars: t.json-stringify($themeVars); } ``` -------------------------------------------------------------------------------- /xmlui/src/components/Carousel/Carousel.module.scss: -------------------------------------------------------------------------------- ```scss @use "../../components-core/theming/themes" as t; // --- This code snippet is required to collect the theme variables used in this module $themeVars: (); @function createThemeVar($componentVariable) { $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global; @return t.getThemeVar($themeVars, $componentVariable); } $component: "Carousel"; @layer components { .carousel { position: relative; overflow: hidden; width: createThemeVar("width-#{$component}"); height: createThemeVar("height-#{$component}"); justify-content: center; align-items: center; } .innerWrapper { display: flex; width: 100%; height: 100%; flex-direction: row; justify-content: center; } .carouselContentWrapper { overflow: hidden; height: 100%; } .carouselContent { display: flex; height: 100%; } .vertical { flex-direction: column; } .carouselItem { overflow: hidden; min-width: 0; flex-shrink: 0; flex-grow: 0; flex-basis: 100%; } .controls { z-index: 99; display: flex; right: 1rem; bottom: 1rem; position: absolute; gap: .6rem; } .controlButton { transition: all .3s; justify-content: center; align-items: center; display: inline-flex; height: createThemeVar("height-control-#{$component}"); width: createThemeVar("width-control-#{$component}"); color: createThemeVar("textColor-control-#{$component}"); background-color: createThemeVar("backgroundColor-control-#{$component}");; border-radius: createThemeVar("borderRadius-control-#{$component}"); } .controlButton:hover { background-color: createThemeVar("backgroundColor-control-hover-#{$component}"); color: createThemeVar("textColor-control-hover-#{$component}"); } .controlButton:active { background-color: createThemeVar("backgroundColor-control-active-#{$component}"); color: createThemeVar("textColor-control-active-#{$component}"); } .controlButton:disabled { color: createThemeVar("textColor-control-disabled-#{$component}"); background-color: createThemeVar("backgroundColor-control-disabled-#{$component}"); } .indicators { position: absolute; bottom: 1rem; left: 50%; transform: translateX(-50%); } .indicator { transition: all .3s; width: createThemeVar("width-indicator-#{$component}"); height: createThemeVar("height-indicator-#{$component}"); border-radius: 2px; margin: 0 1px; color: createThemeVar("textColor-indicator-#{$component}"); background-color: createThemeVar("backgroundColor-indicator-#{$component}"); &:hover { background-color: createThemeVar("backgroundColor-indicator-hover-#{$component}"); color: createThemeVar("textColor-indicator-hover-#{$component}"); } &.active { background-color: createThemeVar("backgroundColor-indicator-active-#{$component}"); color: createThemeVar("textColor-indicator-active-#{$component}"); } } } // --- We export the theme variables to add them to the component renderer :export { themeVars: t.json-stringify($themeVars); } ``` -------------------------------------------------------------------------------- /xmlui/tests/parsers/scripting/eval-tree-pre-post.test.ts: -------------------------------------------------------------------------------- ```typescript import { describe, expect, it } from "vitest"; import { evalBindingExpression } from "../../../src/components-core/script-runner/eval-tree-sync"; import { createEvalContext } from "./test-helpers"; describe("Evaluate prefix/postfix expressions", () => { const prePostCases1 = [ { src: "++x", ctx: { x: 0 }, exp: 1, varExp: 1 }, { src: "--x", ctx: { x: 0 }, exp: -1, varExp: -1 }, { src: "x++", ctx: { x: 0 }, exp: 0, varExp: 1 }, { src: "x--", ctx: { x: 0 }, exp: 0, varExp: -1 } ]; prePostCases1.forEach(c => { it(`Prefix/postfix '${c.src}'`, () => { // --- Arrange const context = createEvalContext({ localContext: c.ctx }); // --- Act const value = evalBindingExpression(c.src, context); // --- Arrange expect(value).equal(c.exp); expect(context.localContext["x"]).equal(c.varExp); }); }); const prePostCases2 = [ { src: "++j[2]", exp: 4, varExp: 4 }, { src: "--j[2]", exp: 2, varExp: 2 }, { src: "j[1+1]++", exp: 3, varExp: 4 }, { src: "j[4-2]--", exp: 3, varExp: 2 } ]; prePostCases2.forEach(c => { it(`Prefix/postfix '${c.src}'`, () => { // --- Arrange const jArr = [1, 2, 3]; const context = createEvalContext({ localContext: { j: jArr } }); // --- Act const value = evalBindingExpression(c.src, context); // --- Arrange expect(value).equal(c.exp); expect(context.localContext.j[2]).equal(c.varExp); }); }); const prePostCases3 = [ { src: "++j[2].h", exp: 4, varExp: 4 }, { src: "--j[2].h", exp: 2, varExp: 2 }, { src: "j[1+1].h++", exp: 3, varExp: 4 }, { src: "j[4-2].h--", exp: 3, varExp: 2 } ]; prePostCases3.forEach(c => { it(`Prefix/postfix '${c.src}'`, () => { // --- Arrange const jArr = [{ h: 1 }, { h: 2 }, { h: 3 }]; const context = createEvalContext({ localContext: { j: jArr } }); // --- Act const value = evalBindingExpression(c.src, context); // --- Arrange expect(value).equal(c.exp); expect(context.localContext.j[2].h).equal(c.varExp); }); }); const prePostCases4 = [ { src: "++j.h[2]", exp: 4, varExp: 4 }, { src: "--j.h[2]", exp: 2, varExp: 2 }, { src: "j.h[1+1]++", exp: 3, varExp: 4 }, { src: "j.h[4-2]--", exp: 3, varExp: 2 } ]; prePostCases4.forEach(c => { it(`Prefix/postfix '${c.src}'`, () => { // --- Arrange const jArr = [1, 2, 3]; const context = createEvalContext({ localContext: { j: { h: jArr } } }); // --- Act const value = evalBindingExpression(c.src, context); // --- Arrange expect(value).equal(c.exp); expect(context.localContext.j.h[2]).equal(c.varExp); }); }); it("Postfix with arrow", () => { // --- Arrange const source = "(x => ++x)(count)"; const context = createEvalContext({ localContext: { count: 6 } }); // --- Act const value = evalBindingExpression(source, context); // --- Arrange expect(value).equal(7); }); }); ``` -------------------------------------------------------------------------------- /packages/xmlui-animations/src/SlideInAnimation.tsx: -------------------------------------------------------------------------------- ```typescript import { createComponentRenderer, createMetadata } from "xmlui"; import { Animation, defaultProps } from "./AnimationNative"; const COMP = "SlideInAnimation"; const defaultAnimationValues = { direction: "left", }; export const SlideInAnimationMd = createMetadata({ status: "experimental", description: `The \`${COMP}\` component represents an animation that slides in the content from the left.`, docFolder: "src", props: { direction: { description: "The direction of the animation.", valueType: "string", options: ["left", "right", "top", "bottom"], defaultValue: defaultAnimationValues.direction, }, duration: { description: "The duration of the animation in milliseconds.", valueType: "number", }, animateWhenInView: { description: "Indicates whether the animation should start when the component is in view.", valueType: "boolean", }, reverse: { description: `Indicates whether the animation should run in reverse`, defaultValue: defaultProps.reverse, valueType: "boolean", }, loop: { description: `Indicates whether the animation should loop`, defaultValue: defaultProps.loop, valueType: "boolean", }, delay: { description: `The delay before the animation starts in milliseconds`, defaultValue: defaultProps.delay, valueType: "number", }, }, events: { started: { description: `Event fired when the animation starts` }, stopped: { description: `Event fired when the animation stops` }, }, apis: { start: { description: `Starts the animation` }, stop: { description: `Stops the animation` }, }, }); export const slideInAnimationRenderer = createComponentRenderer( "SlideInAnimation", SlideInAnimationMd, ({ node, renderChild, extractValue, registerComponentApi, lookupEventHandler }) => { const direction = extractValue.asOptionalString(node.props.direction, defaultAnimationValues.direction); const animation = { from: { transform: direction === "right" ? "translateX(100%)" : direction === "left" ? "translateX(-100%)" : direction === "top" ? "translateY(-100%)" : "translateY(100%)", }, to: { transform: direction === "right" || direction === "left" ? "translateX(0)" : "translateY(0)", }, }; return ( <Animation registerComponentApi={registerComponentApi} animation={animation} duration={extractValue.asOptionalNumber(node.props.duration)} onStop={lookupEventHandler("stopped")} onStart={lookupEventHandler("started")} animateWhenInView={extractValue.asOptionalBoolean(node.props.animateWhenInView)} reverse={extractValue.asOptionalBoolean(node.props.reverse)} loop={extractValue.asOptionalBoolean(node.props.loop)} delay={extractValue.asOptionalNumber(node.props.delay)} > {renderChild(node.children)} </Animation> ); }, ); ``` -------------------------------------------------------------------------------- /xmlui/tests/components-core/scripts-runner/eval-tree-pre-post.test.ts: -------------------------------------------------------------------------------- ```typescript import { describe, expect, it } from "vitest"; import { evalBindingExpression } from "../../../src/components-core/script-runner/eval-tree-sync"; import { createEvalContext } from "./test-helpers"; describe("Evaluate prefix/postfix expressions (exp)", () => { const prePostCases1 = [ { src: "++x", ctx: { x: 0 }, exp: 1, varExp: 1 }, { src: "--x", ctx: { x: 0 }, exp: -1, varExp: -1 }, { src: "x++", ctx: { x: 0 }, exp: 0, varExp: 1 }, { src: "x--", ctx: { x: 0 }, exp: 0, varExp: -1 } ]; prePostCases1.forEach(c => { it(`Prefix/postfix '${c.src}'`, () => { // --- Arrange const context = createEvalContext({ localContext: c.ctx }); // --- Act const value = evalBindingExpression(c.src, context); // --- Arrange expect(value).equal(c.exp); expect(context.localContext["x"]).equal(c.varExp); }); }); const prePostCases2 = [ { src: "++j[2]", exp: 4, varExp: 4 }, { src: "--j[2]", exp: 2, varExp: 2 }, { src: "j[1+1]++", exp: 3, varExp: 4 }, { src: "j[4-2]--", exp: 3, varExp: 2 } ]; prePostCases2.forEach(c => { it(`Prefix/postfix '${c.src}'`, () => { // --- Arrange const jArr = [1, 2, 3]; const context = createEvalContext({ localContext: { j: jArr } }); // --- Act const value = evalBindingExpression(c.src, context); // --- Arrange expect(value).equal(c.exp); expect(context.localContext.j[2]).equal(c.varExp); }); }); const prePostCases3 = [ { src: "++j[2].h", exp: 4, varExp: 4 }, { src: "--j[2].h", exp: 2, varExp: 2 }, { src: "j[1+1].h++", exp: 3, varExp: 4 }, { src: "j[4-2].h--", exp: 3, varExp: 2 } ]; prePostCases3.forEach(c => { it(`Prefix/postfix '${c.src}'`, () => { // --- Arrange const jArr = [{ h: 1 }, { h: 2 }, { h: 3 }]; const context = createEvalContext({ localContext: { j: jArr } }); // --- Act const value = evalBindingExpression(c.src, context); // --- Arrange expect(value).equal(c.exp); expect(context.localContext.j[2].h).equal(c.varExp); }); }); const prePostCases4 = [ { src: "++j.h[2]", exp: 4, varExp: 4 }, { src: "--j.h[2]", exp: 2, varExp: 2 }, { src: "j.h[1+1]++", exp: 3, varExp: 4 }, { src: "j.h[4-2]--", exp: 3, varExp: 2 } ]; prePostCases4.forEach(c => { it(`Prefix/postfix '${c.src}'`, () => { // --- Arrange const jArr = [1, 2, 3]; const context = createEvalContext({ localContext: { j: { h: jArr } } }); // --- Act const value = evalBindingExpression(c.src, context); // --- Arrange expect(value).equal(c.exp); expect(context.localContext.j.h[2]).equal(c.varExp); }); }); it("Postfix with arrow", () => { // --- Arrange const source = "(x => ++x)(count)"; const context = createEvalContext({ localContext: { count: 6 } }); // --- Act const value = evalBindingExpression(source, context); // --- Arrange expect(value).equal(7); }); }); ``` -------------------------------------------------------------------------------- /xmlui/src/components-core/script-runner/statement-queue.ts: -------------------------------------------------------------------------------- ```typescript import type { Statement } from "./ScriptingSourceTree"; export type StatementRunTimeInfo = { guard?: boolean; keys?: string[]; keyIndex?: number; iterator?: any; removeBlockScope?: boolean; }; export type StatementWithInfo = { // --- The description of the statement to execute statement: Statement; // --- Execution information for the statement execInfo?: StatementRunTimeInfo; }; // An item of the queue we use for processing a statement export type StatementQueueItem = StatementWithInfo & { // --- The label of the item. We need this information for "break" and "continue" statements label: number; }; // The outcome of processing a statement of the queue export type ProcessOutcome = { // --- New statements to unshift into the queue (optional) toUnshift?: StatementQueueItem[]; // --- Label from which statements should be preserved in the queue clearToLabel?: number; }; // --- Implements the statement queue export class StatementQueue { private items: StatementQueueItem[] = []; // --- The current queue length get length(): number { return this.items.length; } // --- Get the subsequent item in the queue without dequeuing it peek(): StatementQueueItem | undefined { return this.items[0]; } // --- Remove and return the subsequent item from the queue dequeue(): StatementQueueItem | undefined { return this.items.shift(); } // --- Push the specified items into the queue and return the corresponding queue items push(statements: StatementQueueItem[]): void { this.items.push(...statements); } // --- Unshift the specified items into the queue in reverse order (starting from the last action) and // --- return the corresponding queue items unshift(statements: StatementQueueItem[]): void { this.items.unshift(...statements); } // --- Remove all items starting from the beginning of the queue, while not reaching the item with the // --- specified label. If the label is found, that remains in the queue; otherwise the queue is emptied clearToLabel(label: number): void { while (this.items.length > 0 && this.items[0].label !== label) { this.items.shift(); } } } // --- Queue information for test and diagnostics purposes export type QueueInfo = { processedStatements: number; maxQueueLength: number; unshiftedItems: number; clearToLabels: number; maxBlocks: number; maxLoops: number; }; // --- The ID of the last used label let nextLabelValue = 1; // --- Map a single item to a queue statement list export function mapToItem(statement: Statement): StatementQueueItem[] { return [ { label: nextLabelValue++, statement: { ...statement }, } as StatementQueueItem, ]; } // --- Map a single statement into a queue item function mapStatementToQueueItem(stmt: StatementWithInfo): StatementQueueItem { return { label: nextLabelValue++, statement: stmt.statement, execInfo: { ...stmt.execInfo }, }; } // --- Map a list of statements into queue items export function mapStatementsToQueueItems(statements: StatementWithInfo[]): StatementQueueItem[] { return statements.map(mapStatementToQueueItem); } ``` -------------------------------------------------------------------------------- /xmlui/src/components-core/interception/abstractions.ts: -------------------------------------------------------------------------------- ```typescript import type { DelayMode } from "msw"; export type AuthDefinition = { defaultLoggedInUser?: any }; export type TableDescriptor = { name: string, fields?: Record<string, any>, pk: Array<string>; indexes?: Array<string>; } export type SchemaDescriptor = { tables: Array<TableDescriptor>, relationships?: any; dtos?: any; } // Represents an interceptor between API consumers (UI in the browser) and their backend export type ApiInterceptorDefinition = { // Type of backend interceptor type?: string; // Interceptor configuration config?: Record<string, any>; artificialDelay?: number | DelayMode; // Information for code generators schemaDescriptor?: SchemaDescriptor // URL to particular interceptor is bound to apiUrl?: string; // Initial data to set up (initialize) the interceptor initialData?: Record<string, any[]> | (() => Promise<Record<string, any[]>>); helpers?: Record<string, any>; initialize?: string; // Available operations operations?: Record<string, InterceptorOperationDef>; auth?: AuthDefinition; useWorker?: boolean; }; // Represents a single operation of the interceptor export type InterceptorOperationDef = { // Operation method (can be extended in the future) method: "get" | "post" | "put" | "delete" | "patch" | "head" | "options"; // Operation URL(s) url: string | Array<string>; // The source code of the operation's handler. This code is parsed and executed handler: string; // Shape of the request this operation expects requestShape?: any; // Shape of the response this operation returns responseShape?: any; // Type mappings of path parameters pathParamTypes?: Record<string, string> // Type mappings of query parameters queryParamTypes?: Record<string, string>; successStatusCode?: number; }; // Describes the backend environment (change it for mocked tests) export type BackendEnvironment = { getDate: () => Date; }; // Represents the persistence service (database) of the backend // TODO: Review this interface export interface IDatabase { [key: string]: unknown; getItems(collectionName: string): Promise<Array<any>>; getItem(collectionName: string, predicate: (item: any) => Promise<boolean>): Promise<any>; getItemById(collectionName: string, id: any): Promise<any>; deleteItems(collectionName: string, predicate: (item: any) => Promise<boolean>): Promise<void>; insertItem(collectionName: string, item: any): Promise<any>; updateItem(collectionName: string, item: any): Promise<any>; updateItem(collectionName: string, item: any): Promise<any>; } // Represents the request parameters the backed translates to operations export type RequestParams = { // URL path parameters pathParams: Record<string, any>; // URL query parameters queryParams: Record<string, any>; // The body of a request requestBody: any; cookies: any; requestHeaders: Record<string, string>; }; // Represents the definition of a particular backed export type BackendDefinition = { initialData?: Record<string, any[]>; initialize?: string; helpers?: Record<string, any>; operations: Record<string, string>; }; ``` -------------------------------------------------------------------------------- /docs/public/pages/howto/sync-selections-between-table-and-list-views.md: -------------------------------------------------------------------------------- ```markdown # Sync selections between a Table view and a List view The Table view syncs selections automatically via `syncWithAppState`, while the Tiles (List) view uses checkboxes that manually coordinate with AppState. ```xmlui-pg ---app <App> <Files /> </App> ---api { "apiUrl": "/api", "initialize": "$state.files = [ { id: 1, name: 'document.pdf', size: '2.3 MB', type: 'PDF' }, { id: 2, name: 'image.jpg', size: '1.1 MB', type: 'Image' }, { id: 3, name: 'spreadsheet.xlsx', size: '856 KB', type: 'Excel' }, { id: 4, name: 'presentation.pptx', size: '4.2 MB', type: 'PowerPoint' }, { id: 5, name: 'text.txt', size: '12 KB', type: 'Text' } ]", "operations": { "get-files": { "url": "/files", "method": "get", "handler": "return $state.files" } } } ---comp display /selections/ /sharedSelections/ /selectedIds/ <Component name="Files"> <AppState id="selections" bucket="sharedSelections" initialValue="{{ selectedIds: [] }}" /> <DataSource id="files" url="/api/files" /> <Text>Selected: {selections.value.selectedIds.length} items</Text> <Tabs> <TabItem label="Table View"> <TableView data="{files}" /> </TabItem> <TabItem label="Tiles View"> <TilesView data="{files}" /> </TabItem> <TabItem label="Debug"> <DebugView /> </TabItem> </Tabs> </Component> ---comp display /selections/ /sharedSelections/ /selectedIds/ <Component name="TableView"> <AppState id="selections" bucket="sharedSelections" /> <Card> <Table data="{$props.data}" rowsSelectable="true" syncWithAppState="{selections}" loading="{!$props.data}" > <Column bindTo="name" /> <Column bindTo="size" /> <Column bindTo="type" /> </Table> </Card> </Component> ---comp display /selections/ /sharedSelections/ /selectedIds/ <Component name="TilesView"> <AppState id="selections" bucket="sharedSelections" /> <List data="{$props.data}"> <Card> <HStack verticalAlignment="center"> <Checkbox initialValue="{selections.value.selectedIds?.includes($item.id) || false}" onDidChange="(checked) => { const ids = selections.value.selectedIds || []; if (checked) { selections.update({ selectedIds: [...ids, $item.id] }); } else { selections.update({ selectedIds: ids.filter(id => id !== $item.id) }); } }" /> <VStack> <Text>{$item.name}</Text> <Text variant="caption">{$item.size} - {$item.type}</Text> </VStack> </HStack> </Card> </List> </Component> ---desc The spread operator `...ids` takes all existing IDs from the array, and creates a new array with those IDs plus the new one. The filter method creates a new array with only items that meet a condition. ---comp display /selections/ /sharedSelections/ /selectedIds/ <Component name="DebugView"> <AppState id="selections" bucket="sharedSelections" /> <Card> <VStack> <Text>selections.value</Text> <Text variant="code">{JSON.stringify(selections.value, null, 2)}</Text> </VStack> </Card> </Component> ``` ``` -------------------------------------------------------------------------------- /xmlui/src/components/PageMetaTitle/PageMetaTitle.spec.ts: -------------------------------------------------------------------------------- ```typescript import { expect, test } from "../../testing/fixtures"; // ============================================================================= // BASIC FUNCTIONALITY TESTS // ============================================================================= test.describe("Basic Functionality", () => { test("component renders with default props", async ({ initTestBed, page }) => { await initTestBed(`<PageMetaTitle />`); // PageMetaTitle doesn't render visible DOM elements, it only affects page title // Default title should be set in browser with test bed suffix await expect(page).toHaveTitle("XMLUI Application | test bed app"); }); test("component renders with value property", async ({ initTestBed, page }) => { await initTestBed(`<PageMetaTitle value="Custom Page Title" />`); // Custom title should be set in browser with test bed suffix await expect(page).toHaveTitle("Custom Page Title | test bed app"); }); test("component handles dynamic value changes", async ({ initTestBed, page }) => { const { testStateDriver } = await initTestBed(` <VStack> <PageMetaTitle value="{testState || 'Initial Title'}" /> <Button onClick="testState = 'Updated Title'">Update Title</Button> </VStack> `); // Initial title should be set await expect(page).toHaveTitle("Initial Title | test bed app"); // Click button to update title await page.getByRole("button").click(); await expect.poll(testStateDriver.testState).toEqual("Updated Title"); // Title should be updated in browser await expect(page).toHaveTitle("Updated Title | test bed app"); }); test("component handles child content as title", async ({ initTestBed, page }) => { await initTestBed(` <PageMetaTitle> Child Content Title </PageMetaTitle> `); // Child content should be used as title await expect(page).toHaveTitle("Child Content Title | test bed app"); }); test("value property takes precedence over child content", async ({ initTestBed, page }) => { await initTestBed(` <PageMetaTitle value="Value Property Title"> Child Content That Should Be Ignored </PageMetaTitle> `); // Value property should take precedence await expect(page).toHaveTitle("Value Property Title | test bed app"); }); test("multiple PageMetaTitle components - last one wins", async ({ initTestBed, page }) => { await initTestBed(` <VStack> <PageMetaTitle value="First Title" /> <PageMetaTitle value="Second Title" /> <PageMetaTitle value="Final Title" /> </VStack> `); // Last component should set the final title await expect(page).toHaveTitle("Final Title | test bed app"); }); test("component updates existing page title", async ({ initTestBed, page }) => { // Set initial title await initTestBed(`<PageMetaTitle value="Initial Title" />`); await expect(page).toHaveTitle("Initial Title | test bed app"); // Update with new component await initTestBed(`<PageMetaTitle value="Updated Title" />`); await expect(page).toHaveTitle("Updated Title | test bed app"); }); }); ``` -------------------------------------------------------------------------------- /xmlui/src/components-core/action/FileUploadAction.tsx: -------------------------------------------------------------------------------- ```typescript import type { ActionExecutionContext } from "../../abstractions/ActionDefs"; import type { ComponentDef } from "../../abstractions/ComponentDefs"; import { extractParam } from "../utils/extractParam"; import { invalidateQueries } from "../utils/actionUtils"; import type { ApiActionOptions, UploadOperationDef } from "../RestApiProxy"; import RestApiProxy from "../RestApiProxy"; import { createAction } from "./actions"; export interface UploadActionComponent extends ComponentDef { props: { invalidates?: string | string[]; } & UploadOperationDef; events?: { error?: string; success?: string; }; } export type UploadActionParams = { invalidates?: string | string[]; params: any; chunkSizeInBytes?: number; onError?: string; onProgress?: (...args: any) => void; } & UploadOperationDef; async function uploadFile( { appContext, state, lookupAction, uid }: ActionExecutionContext, { params, invalidates, onError, queryParams, asForm, file, headers, url, method, formParams, rawBody, body, chunkSizeInBytes, onProgress, }: UploadActionParams, { resolveBindingExpressions }: ApiActionOptions = {}, ) { const stateContext = { ...params, ...state }; const api = new RestApiProxy(appContext); const operation: UploadOperationDef = { file, body, asForm, formParams, rawBody, method, url, queryParams, headers, }; let result = null; try { const _chunkSizeInBytes = extractParam(stateContext, chunkSizeInBytes, appContext); const _onProgress = extractParam(stateContext, onProgress, appContext); if (_chunkSizeInBytes !== undefined) { const _file = extractParam(stateContext, file, appContext); const numberOfChunks = Math.ceil(_file.size / _chunkSizeInBytes); for (let i = 0; i < numberOfChunks; i++) { const start = i * _chunkSizeInBytes; const chunkEnd = Math.min(start + _chunkSizeInBytes, _file.size); const chunk = _file.slice(start, chunkEnd); result = await api.upload({ operation, chunk: { blob: chunk, chunkStart: start, chunkEnd: chunkEnd, }, params: stateContext, onUploadProgress: (progressEvent) => { const overallTotal = _file.size; const overallLoaded = start + progressEvent.loaded; const overallProgressEvent = { total: overallTotal, loaded: overallLoaded, progress: overallLoaded / overallTotal, }; _onProgress?.(overallProgressEvent); }, resolveBindingExpressions, }); } } else { result = await api.upload({ operation, params: stateContext, onUploadProgress: _onProgress, resolveBindingExpressions, }); } } catch (e) { const onErrorFn = lookupAction(onError, uid); const result = await onErrorFn?.(e, stateContext["$param"]); if (result !== false) { throw e; } } void invalidateQueries(invalidates, appContext, state); return result; } export const uploadAction = createAction("upload", uploadFile); ``` -------------------------------------------------------------------------------- /xmlui/src/components/FileUploadDropZone/FileUploadDropZone.tsx: -------------------------------------------------------------------------------- ```typescript import styles from "./FileUploadDropZone.module.scss"; import { createComponentRenderer } from "../../components-core/renderers"; import { parseScssVar } from "../../components-core/theming/themeVars"; import { FileUploadDropZone, defaultProps } from "./FileUploadDropZoneNative"; import { createMetadata, d } from "../metadata-helpers"; const COMP = "FileUploadDropZone"; export const FileUploadDropZoneMd = createMetadata({ status: "stable", description: "`FileUploadDropZone` enables users to upload files by dragging and dropping " + "files from their local file system onto a designated area within the UI.", props: { text: { description: "With this property, you can change the default text to display when files " + "are dragged over the drop zone.", defaultValue: defaultProps.text, type: "string", }, allowPaste: { description: "This property indicates if the drop zone accepts files pasted from the " + "clipboard (\`true\`) or " + "only dragged files (\`false\`).", type: "boolean", defaultValue: defaultProps.allowPaste, }, enabled: d( `If set to \`false\`, the drop zone will be disabled and users will not be able to upload files.`, null, "boolean", true, ), acceptedFileTypes: d(`Accepted file MIME types, separated by commas. For example: 'image/*,application/pdf'.`), maxFiles: d(`The maximum number of files that can be selected.`), }, events: { upload: d( `This component accepts files for upload but does not perform the actual operation. It fires ` + `the \`upload\` event and passes the list files to upload in the method's argument. You can ` + `use the passed file information to implement the upload (according to the protocol your ` + `backend supports).`, ), }, themeVars: parseScssVar(styles.themeVars), defaultThemeVars: { "backgroundColor-FileUploadDropZone": "$backgroundColor", "backgroundColor-dropping-FileUploadDropZone": "$backgroundColor--selected", "opacity-dropping-FileUploadDropZone": "0.5", "textColor-FileUploadDropZone": "$textColor", light: { // --- No light-specific theme vars }, dark: { // --- No dark-specific theme vars }, }, }); export const fileUploadDropZoneComponentRenderer = createComponentRenderer( COMP, FileUploadDropZoneMd, ({ node, extractValue, updateState, renderChild, lookupEventHandler, registerComponentApi, className }) => { return ( <FileUploadDropZone onUpload={lookupEventHandler("upload")!} uid={extractValue(node.uid)} registerComponentApi={registerComponentApi} className={className} allowPaste={extractValue(node.props.allowPaste)} text={extractValue(node.props.text)} disabled={!extractValue.asOptionalBoolean(node.props.enabled, true)} updateState={updateState} acceptedFileTypes={extractValue.asOptionalString(node.props.acceptedFileTypes)} maxFiles={extractValue.asOptionalNumber(node.props.maxFiles)} > {renderChild(node.children, { type: "Stack" })} </FileUploadDropZone> ); }, ); ```