This is page 17 of 181. Use http://codebase.md/xmlui-org/xmlui?lines=true&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 │ │ │ ├── Debug.xmlui │ │ │ └── PageNotFound.xmlui │ │ ├── config.ts │ │ ├── Main.xmlui │ │ ├── Main.xmlui.xs │ │ └── 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/Accordion/Accordion.module.scss: -------------------------------------------------------------------------------- ```scss 1 | @use "../../components-core/theming/themes" as t; 2 | 3 | // --- This code snippet is required to collect the theme variables used in this module 4 | $themeVars: (); 5 | @function createThemeVar($componentVariable) { 6 | $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global; 7 | @return t.getThemeVar($themeVars, $componentVariable); 8 | } 9 | 10 | $component: "Accordion"; 11 | $themeVars: t.composeBorderVars($themeVars, $component); 12 | $themeVars: t.composePaddingVars($themeVars, $component); 13 | $borderRadius: createThemeVar("borderRadius-#{$component}"); 14 | 15 | 16 | @layer components { 17 | .root { 18 | overflow: hidden; 19 | width: 100%; 20 | //box-shadow: 0 2px 10px var(--black-a4); 21 | @include t.borderVars($themeVars, $component); 22 | @include t.paddingVars($themeVars, $component); 23 | } 24 | 25 | .item { 26 | overflow: hidden; 27 | margin-top: 1px; 28 | box-sizing: border-box; 29 | 30 | &:first-child { 31 | margin-top: 0; 32 | border-top-left-radius: $borderRadius; 33 | border-top-right-radius: $borderRadius; 34 | } 35 | 36 | &:last-child { 37 | border-bottom-left-radius: $borderRadius; 38 | border-bottom-right-radius: $borderRadius; 39 | } 40 | 41 | &:focus-within { 42 | position: relative; 43 | z-index: 1; 44 | } 45 | } 46 | 47 | .header { 48 | display: flex; 49 | margin: 0; 50 | } 51 | 52 | .trigger { 53 | font-family: inherit; 54 | flex: 1; 55 | display: inline-flex; 56 | align-items: createThemeVar("verticalAlignment-header-#{$component}"); 57 | justify-content: space-between; 58 | line-height: 1; 59 | box-sizing: border-box; 60 | font-size: createThemeVar("fontSize-header-#{$component}"); 61 | font-weight: createThemeVar("fontWeight-header-#{$component}"); 62 | font-style: createThemeVar("fontStyle-header-#{$component}"); 63 | padding: createThemeVar("paddingVertical-header-#{$component}") createThemeVar("paddingHorizontal-header-#{$component}"); 64 | background-color: createThemeVar("backgroundColor-header-#{$component}"); 65 | color: createThemeVar("color-header-#{$component}"); 66 | 67 | &:hover { 68 | background-color: createThemeVar("backgroundColor-header-#{$component}-hover"); 69 | } 70 | 71 | &.triggerStart { 72 | flex-direction: row-reverse; 73 | } 74 | } 75 | 76 | .contentWrapper { 77 | overflow: hidden; 78 | color: createThemeVar("color-content-#{$component}"); 79 | background-color: createThemeVar("backgroundColor-content-#{$component}"); 80 | 81 | &[data-state='open'] { 82 | animation: slideDown 300ms cubic-bezier(0.87, 0, 0.13, 1); 83 | } 84 | 85 | &[data-state='closed'] { 86 | animation: slideUp 300ms cubic-bezier(0.87, 0, 0.13, 1); 87 | } 88 | } 89 | 90 | .content { 91 | padding: t.$space-2; 92 | } 93 | 94 | .chevron { 95 | width: createThemeVar("width-icon-#{$component}"); 96 | height: createThemeVar("height-icon-#{$component}"); 97 | color: createThemeVar("color-icon-#{$component}"); 98 | } 99 | 100 | @keyframes slideDown { 101 | from { 102 | height: 0; 103 | } 104 | to { 105 | height: var(--radix-accordion-content-height); 106 | } 107 | } 108 | 109 | @keyframes slideUp { 110 | from { 111 | height: var(--radix-accordion-content-height); 112 | } 113 | to { 114 | height: 0; 115 | } 116 | } 117 | } 118 | 119 | 120 | 121 | // --- We export the theme variables to add them to the component renderer 122 | :export { 123 | themeVars: t.json-stringify($themeVars); 124 | } 125 | ``` -------------------------------------------------------------------------------- /packages/xmlui-animations/src/FadeAnimation.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import { createComponentRenderer, createMetadata } from "xmlui"; 2 | import { Animation, defaultProps } from "./AnimationNative"; 3 | 4 | const COMP = "FadeAnimation"; 5 | 6 | const defaultAnimationValues = { 7 | from: 0, 8 | to: 1, 9 | }; 10 | 11 | export const FadeAnimationMd = createMetadata({ 12 | status: "experimental", 13 | description: `The \`${COMP}\` component represents an animation that fades the content with adjustable opacity values.`, 14 | docFolder: "src", 15 | props: { 16 | from: { 17 | description: 18 | "Sets the initial opacity of the content." + 19 | "If the `to` property is not set, the initial opacity set here will be used as the final opacity.", 20 | valueType: "number", 21 | defaultValue: defaultAnimationValues.from, 22 | }, 23 | to: { 24 | description: 25 | "Sets the final opacity of the content." + 26 | "If the `from` property is not set, the initial opacity set here will be used as the final opacity.", 27 | valueType: "number", 28 | defaultValue: defaultAnimationValues.to, 29 | }, 30 | animateWhenInView: { 31 | description: `Indicates whether the animation should start when the component is in view`, 32 | valueType: "boolean", 33 | }, 34 | duration: { 35 | description: `The duration of the animation in milliseconds`, 36 | valueType: "number", 37 | }, 38 | reverse: { 39 | description: `Indicates whether the animation should run in reverse`, 40 | defaultValue: defaultProps.reverse, 41 | valueType: "boolean", 42 | }, 43 | loop: { 44 | description: `Indicates whether the animation should loop`, 45 | defaultValue: defaultProps.loop, 46 | valueType: "boolean", 47 | }, 48 | delay: { 49 | description: `The delay before the animation starts in milliseconds`, 50 | defaultValue: defaultProps.delay, 51 | valueType: "number", 52 | }, 53 | }, 54 | events: { 55 | started: { description: `Event fired when the animation starts` }, 56 | stopped: { description: `Event fired when the animation stops` }, 57 | }, 58 | apis: { 59 | start: { description: `Starts the animation` }, 60 | stop: { description: `Stops the animation` }, 61 | }, 62 | }); 63 | 64 | export const fadeAnimationRenderer = createComponentRenderer( 65 | "FadeAnimation", 66 | FadeAnimationMd, 67 | ({ node, renderChild, extractValue, registerComponentApi, lookupEventHandler }) => { 68 | return ( 69 | <Animation 70 | registerComponentApi={registerComponentApi} 71 | animation={{ 72 | from: { 73 | opacity: extractValue.asOptionalNumber(node.props.from, defaultAnimationValues.from), 74 | }, 75 | to: { 76 | opacity: extractValue.asOptionalNumber(node.props.to, defaultAnimationValues.to), 77 | }, 78 | }} 79 | duration={extractValue.asOptionalNumber(node.props.duration)} 80 | onStop={lookupEventHandler("stopped")} 81 | onStart={lookupEventHandler("started")} 82 | animateWhenInView={extractValue.asOptionalBoolean(node.props.animateWhenInView)} 83 | reverse={extractValue.asOptionalBoolean(node.props.reverse)} 84 | loop={extractValue.asOptionalBoolean(node.props.loop)} 85 | delay={extractValue.asOptionalNumber(node.props.delay)} 86 | > 87 | {renderChild(node.children)} 88 | </Animation> 89 | ); 90 | }, 91 | ); 92 | ``` -------------------------------------------------------------------------------- /xmlui/tests/parsers/xmlui/transform.escape.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { describe, expect, it } from "vitest"; 2 | import { ComponentDef } from "../../../src/abstractions/ComponentDefs"; 3 | import { transformSource } from "./xmlui"; 4 | 5 | describe("Xmlui transform - no backslash escape", () => { 6 | it("backslash", () => { 7 | const cd = transformSource("<Stack a='\\\\' />") as ComponentDef; 8 | expect((cd.props as any).a).equal("\\\\"); 9 | }); 10 | 11 | it("newline", () => { 12 | const cd = transformSource("<Stack a='\\n' />") as ComponentDef; 13 | expect((cd.props as any).a).equal("\\n"); 14 | }); 15 | }); 16 | 17 | describe("Xmlui transform - entity escape", () => { 18 | it("&", () => { 19 | const cd = transformSource("<Stack>&</Stack>") as ComponentDef; 20 | expect((cd.children![0].props as any).value).toEqual("&"); 21 | }); 22 | 23 | it(">", () => { 24 | const cd = transformSource("<Stack>></Stack>") as ComponentDef; 25 | expect((cd.children![0].props as any).value).toEqual(">"); 26 | }); 27 | 28 | it("<", () => { 29 | const cd = transformSource("<Stack><</Stack>") as ComponentDef; 30 | expect((cd.children![0].props as any).value).toEqual("<"); 31 | }); 32 | it("'", () => { 33 | const cd = transformSource("<Stack>'</Stack>") as ComponentDef; 34 | expect((cd.children![0].props as any).value).toEqual("'"); 35 | }); 36 | it(""", () => { 37 | const cd = transformSource("<Stack>"</Stack>") as ComponentDef; 38 | expect((cd.children![0].props as any).value).toEqual('"'); 39 | }); 40 | it(" ", () => { 41 | const cd = transformSource("<Stack> </Stack>") as ComponentDef; 42 | expect((cd.children![0].props as any).value).toEqual("\xa0\xa0\xa0"); 43 | }); 44 | 45 | it("combined", () => { 46 | const cd = transformSource("<Stack>"&abc><</Stack>") as ComponentDef; 47 | expect((cd.children![0].props as any).value).toEqual('"&abc><'); 48 | }); 49 | 50 | it("& ignored inside CData", () => { 51 | const cd = transformSource("<Stack><![CDATA[&]]></Stack>") as ComponentDef; 52 | expect((cd.children![0].props as any).value).toEqual("&"); 53 | }); 54 | 55 | it("attr - &", () => { 56 | const cd = transformSource("<H1 value='&' />") as ComponentDef; 57 | expect((cd.props as any).value).toEqual("&"); 58 | }); 59 | 60 | it("attr - >", () => { 61 | const cd = transformSource("<H1 value='>' />") as ComponentDef; 62 | expect((cd.props as any).value).toEqual(">"); 63 | }); 64 | 65 | it("attr - ", () => { 66 | const cd = transformSource("<H1 value=' ' />") as ComponentDef; 67 | expect((cd.props as any).value).toEqual("\xa0\xa0\xa0"); 68 | }); 69 | 70 | it("attr - <", () => { 71 | const cd = transformSource("<H1 value='<' />") as ComponentDef; 72 | expect((cd.props as any).value).toEqual("<"); 73 | }); 74 | it("attr - '", () => { 75 | const cd = transformSource("<H1 value=''' />") as ComponentDef; 76 | expect((cd.props as any).value).toEqual("'"); 77 | }); 78 | it("attr - "", () => { 79 | const cd = transformSource("<H1 value='"' />") as ComponentDef; 80 | expect((cd.props as any).value).toEqual('"'); 81 | }); 82 | 83 | it("attr - combined", () => { 84 | const cd = transformSource("<H1 value='"&abc>' />") as ComponentDef; 85 | expect((cd.props as any).value).toEqual('"&abc>'); 86 | }); 87 | }); 88 | ``` -------------------------------------------------------------------------------- /docs/public/resources/files/products.json: -------------------------------------------------------------------------------- ```json 1 | [{"id":14,"name":"API Integration","description":"Third-party API integration services","price":105,"created_at":"2022-08-10T14:06:49.463Z"}, 2 | {"id":22,"name":"Brand Strategy Consulting","description":"Strategic brand positioning and messaging","price":115,"created_at":"2025-06-06 00:02:08"}, 3 | {"id":8,"name":"Cloud Migration","description":"Migrate systems to cloud infrastructure","price":130,"created_at":"2022-10-20T02:24:30.991Z"}, 4 | {"id":6,"name":"Content Creation","description":"Blog posts and content marketing","price":65,"created_at":"2023-02-27T14:54:28.774Z"}, 5 | {"id":13,"name":"Data Analysis","description":"Data analytics and reporting","price":90,"created_at":"2022-05-10T21:05:04.425Z"}, 6 | {"id":7,"name":"Database Design","description":"Database architecture and optimization","price":110,"created_at":"2022-02-06T12:47:47.397Z"}, 7 | {"id":19,"name":"Domain Registration (Yearly)","description":"Domain name registration and renewal","price":15,"created_at":"2023-03-08T06:10:10.884Z"}, 8 | {"id":21,"name":"E-commerce Development","description":"Online store development and integration","price":135,"created_at":"2025-06-06 00:02:08"}, 9 | {"id":12,"name":"Email Marketing","description":"Email campaign setup and management","price":60,"created_at":"2022-03-07T00:58:53.758Z"}, 10 | {"id":11,"name":"Logo Design","description":"Professional logo design services","price":80,"created_at":"2022-06-29T23:21:01.184Z"}, 11 | {"id":4,"name":"Mobile App Development","description":"Native mobile application development","price":145,"created_at":"2022-04-27T01:31:14.995Z"}, 12 | {"id":15,"name":"Project Management","description":"Professional project management services","price":95,"created_at":"2022-07-29T06:59:59.756Z"}, 13 | {"id":16,"name":"QA Testing","description":"Software quality assurance testing","price":80,"created_at":"2022-05-19T05:35:07.028Z"}, 14 | {"id":5,"name":"SEO Optimization","description":"Search engine optimization services","price":85,"created_at":"2022-09-27T13:12:58.057Z"}, 15 | {"id":9,"name":"Security Audit","description":"Comprehensive security assessment","price":150,"created_at":"2022-02-26T22:17:19.018Z"}, 16 | {"id":3,"name":"Server Maintenance","description":"Monthly server maintenance and updates","price":75,"created_at":"2022-02-27T00:11:24.402Z"}, 17 | {"id":10,"name":"Social Media Management","description":"Monthly social media account management","price":70,"created_at":"2022-03-17T19:11:09.894Z"}, 18 | {"id":17,"name":"Tech Consultation","description":"Technology consultation services (hourly)","price":125,"created_at":"2022-07-09T22:04:19.424Z"}, 19 | {"id":2,"name":"UI/UX Design","description":"User interface and experience design","price":95,"created_at":"2022-02-08T19:22:16.697Z"}, 20 | {"id":20,"name":"Video Production","description":"Professional video production services","price":175,"created_at":"2023-05-07T22:52:06.967Z"}, 21 | {"id":1,"name":"Web Development","description":"Full stack web application development","price":120,"created_at":"2022-12-24T10:09:20.570Z"}, 22 | {"id":18,"name":"Website Hosting (Monthly)","description":"Managed website hosting services","price":45,"created_at":"2022-02-28T15:52:17.105Z"}] 23 | ``` -------------------------------------------------------------------------------- /xmlui/src/components/FlowLayout/FlowLayout.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import styles from "./FlowLayout.module.scss"; 2 | 3 | import { createComponentRenderer } from "../../components-core/renderers"; 4 | import { isComponentDefChildren } from "../../components-core/utils/misc"; 5 | import { NotAComponentDefError } from "../../components-core/EngineError"; 6 | import { parseScssVar } from "../../components-core/theming/themeVars"; 7 | import { FlowItemBreak, FlowItemWrapper, FlowLayout, defaultProps } from "./FlowLayoutNative"; 8 | import { createMetadata } from "../metadata-helpers"; 9 | 10 | const COMP = "FlowLayout"; 11 | 12 | export const FlowLayoutMd = createMetadata({ 13 | status: "stable", 14 | description: 15 | "`FlowLayout` positions content in rows with automatic wrapping. When items " + 16 | "exceed the available horizontal space, they automatically wrap to a new line.", 17 | props: { 18 | gap: { 19 | description: 20 | `This property defines the gap between items in the same row and between rows. The ${COMP} ` + 21 | `component creates a new row when an item is about to overflow the current row.`, 22 | type: "string", 23 | defaultValue: "$gap-normal", 24 | }, 25 | columnGap: { 26 | description: 27 | "The \`columnGap\` property specifies the space between items in a single row; it overrides " + 28 | "the \`gap\` value.", 29 | defaultValue: defaultProps.columnGap, 30 | }, 31 | rowGap: { 32 | description: 33 | `The \`rowGap\` property specifies the space between the ${COMP} rows; it overrides ` + 34 | `the \`gap\` value.`, 35 | defaultValue: defaultProps.rowGap, 36 | }, 37 | }, 38 | themeVars: parseScssVar(styles.themeVars), 39 | }); 40 | 41 | export const flowLayoutComponentRenderer = createComponentRenderer( 42 | COMP, 43 | FlowLayoutMd, 44 | ({ node, renderChild, className, extractValue }) => { 45 | if (!isComponentDefChildren(node.children)) { 46 | throw new NotAComponentDefError(); 47 | } 48 | 49 | const columnGap = 50 | extractValue.asSize(node.props?.columnGap) || 51 | extractValue.asSize(node.props?.gap) || 52 | extractValue.asSize("$space-4"); 53 | const rowGap = 54 | extractValue.asSize(node.props?.rowGap) || extractValue.asSize(node.props?.gap) || extractValue.asSize("$space-4"); 55 | 56 | return ( 57 | <FlowLayout className={className} columnGap={columnGap} rowGap={rowGap}> 58 | {renderChild(node.children, { 59 | wrapChild: ({ node, extractValue }, renderedChild, hints) => { 60 | if (hints?.opaque) { 61 | return renderedChild; 62 | } 63 | // Handle SpaceFiller as flow item break 64 | if (node.type === "SpaceFiller") { 65 | return <FlowItemBreak force={true} />; 66 | } 67 | const width = extractValue((node.props as any)?.width); 68 | const minWidth = extractValue((node.props as any)?.minWidth); 69 | const maxWidth = extractValue((node.props as any)?.maxWidth); 70 | return ( 71 | <FlowItemWrapper 72 | width={width} 73 | minWidth={minWidth} 74 | maxWidth={maxWidth} 75 | forceBreak={node.type === "SpaceFiller"} 76 | > 77 | {renderedChild} 78 | </FlowItemWrapper> 79 | ); 80 | }, 81 | })} 82 | </FlowLayout> 83 | ); 84 | }, 85 | ); 86 | ``` -------------------------------------------------------------------------------- /xmlui/src/components/Markdown/CodeText.module.scss: -------------------------------------------------------------------------------- ```scss 1 | @use "../../components-core/theming/themes" as t; 2 | 3 | $themeVars: (); 4 | @function createThemeVar($componentVariable) { 5 | $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global; 6 | @return t.getThemeVar($themeVars, $componentVariable); 7 | } 8 | 9 | $component: "Text"; 10 | 11 | @mixin textVariant($variant) { 12 | $variantName: if($variant == "", "#{$component}", "#{$component}-#{$variant}"); 13 | $themeVars: t.composePaddingVars($themeVars, $variantName); 14 | $themeVars: t.composeBorderVars($themeVars, $variantName); 15 | $themeVars: t.composeTextVars($themeVars, $variantName, $component); 16 | @include t.paddingVars($themeVars, $variantName); 17 | @include t.borderVars($themeVars, $variantName); 18 | @include t.textVars($themeVars, $variantName); 19 | margin-top: createThemeVar("marginTop-#{$variantName}"); 20 | margin-bottom: createThemeVar("marginBottom-#{$variantName}"); 21 | margin-left: createThemeVar("marginLeft-#{$variantName}"); 22 | margin-right: createThemeVar("marginRight-#{$variantName}"); 23 | vertical-align: createThemeVar("verticalAlignment-#{$variantName}"); 24 | } 25 | 26 | @layer components { 27 | .codeText { 28 | overflow: hidden; 29 | text-overflow: ellipsis; 30 | margin: 0; 31 | padding: 0; 32 | min-height: fit-content; 33 | 34 | // --- Styling <pre><code> elements 35 | 36 | // codefence/<pre> elements are block level by default, even in another text element 37 | // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/pre 38 | .text.codefence { 39 | display: block; 40 | margin-top: createThemeVar("marginTop-#{$component}-codefence"); 41 | margin-bottom: createThemeVar("marginBottom-#{$component}-codefence"); 42 | height: 100%; 43 | } 44 | 45 | // For inline code 46 | :not(.text.codefence) > &.code, 47 | :not(.text.codefence) > &code { 48 | @include textVariant("code"); 49 | } 50 | 51 | // For code blocks using <![CDATA[ + <code> tags 52 | &.codefence:has(> .text.code), 53 | &.codefence:has(> code) { 54 | @include textVariant("codefence"); 55 | // TEMP: solution for a demo - need to re-evaluate how to truncate/wrap code blocks 56 | overflow-x: auto; 57 | text-overflow: initial; 58 | } 59 | 60 | // TEMP: experimental - may be removed 61 | &.codefence:has(> code > span) { 62 | padding-inline: 0; 63 | } 64 | &.codefence > code > span:not([class*="numbered"]) { 65 | padding-inline: t.$space-3; 66 | } 67 | 68 | &.codefence > code { 69 | font-family: createThemeVar("fontFamily-#{$component}-codefence"); 70 | //white-space: pre-wrap; 71 | font-family: createThemeVar("fontFamily-#{$component}-codefence"); 72 | //display: inline-block; 73 | width: 100%; 74 | } 75 | 76 | &.codefence > code::before { 77 | counter-reset: listing; 78 | } 79 | 80 | &.codefence > code > span { 81 | display: inline-block; 82 | width: 100%; 83 | } 84 | &.codefence > code > span[class*="numbered"] { 85 | counter-increment: listing; 86 | } 87 | 88 | &.codefence > code > span[class*="numbered"]::before { 89 | content: counter(listing); 90 | display: inline-block; 91 | width: t.$space-12; 92 | padding-left: auto; 93 | padding-right: t.$space-5; 94 | margin-left: auto; 95 | text-align: right; 96 | color: t.$textColor-secondary; 97 | } 98 | } 99 | } 100 | 101 | :export { 102 | themeVars: t.json-stringify($themeVars); 103 | } 104 | ``` -------------------------------------------------------------------------------- /packages/xmlui-website-blocks/src/Carousel/Carousel.module.scss: -------------------------------------------------------------------------------- ```scss 1 | @use "xmlui/themes.scss" as t; 2 | 3 | // --- This code snippet is required to collect the theme variables used in this module 4 | $themeVars: ( 5 | ); 6 | 7 | @function createThemeVar($componentVariable) { 8 | $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global; 9 | @return t.getThemeVar($themeVars, $componentVariable); 10 | } 11 | 12 | $component: "Carousel"; 13 | 14 | @layer components { 15 | .carousel { 16 | position: relative; 17 | overflow: hidden; 18 | width: createThemeVar("width-#{$component}"); 19 | height: createThemeVar("height-#{$component}"); 20 | justify-content: center; 21 | align-items: center; 22 | } 23 | 24 | .innerWrapper { 25 | display: flex; 26 | width: 100%; 27 | height: 100%; 28 | flex-direction: row; 29 | justify-content: center; 30 | } 31 | 32 | .carouselContentWrapper { 33 | overflow: hidden; 34 | height: 100%; 35 | } 36 | 37 | .carouselContent { 38 | display: flex; 39 | height: 100%; 40 | } 41 | 42 | .vertical { 43 | flex-direction: column; 44 | } 45 | 46 | .carouselItem { 47 | overflow: hidden; 48 | min-width: 0; 49 | flex-shrink: 0; 50 | flex-grow: 0; 51 | flex-basis: 100%; 52 | } 53 | 54 | .controls { 55 | z-index: 99; 56 | display: flex; 57 | right: 1rem; 58 | bottom: 1rem; 59 | position: absolute; 60 | gap: .6rem; 61 | } 62 | 63 | .controlButton { 64 | transition: all .3s; 65 | justify-content: center; 66 | align-items: center; 67 | display: inline-flex; 68 | height: createThemeVar("height-control-#{$component}"); 69 | width: createThemeVar("width-control-#{$component}"); 70 | color: createThemeVar("textColor-control-#{$component}"); 71 | background-color: createThemeVar("backgroundColor-control-#{$component}"); 72 | ; 73 | border-radius: createThemeVar("borderRadius-control-#{$component}"); 74 | } 75 | 76 | .controlButton:hover { 77 | background-color: createThemeVar("backgroundColor-control-hover-#{$component}"); 78 | color: createThemeVar("textColor-control-hover-#{$component}"); 79 | } 80 | 81 | .controlButton:active { 82 | background-color: createThemeVar("backgroundColor-control-active-#{$component}"); 83 | color: createThemeVar("textColor-control-active-#{$component}"); 84 | } 85 | 86 | .controlButton:disabled { 87 | color: createThemeVar("textColor-control-disabled-#{$component}"); 88 | background-color: createThemeVar("backgroundColor-control-disabled-#{$component}"); 89 | } 90 | 91 | .indicators { 92 | position: absolute; 93 | bottom: 1rem; 94 | left: 50%; 95 | transform: translateX(-50%); 96 | } 97 | 98 | .indicator { 99 | transition: all .3s; 100 | width: createThemeVar("width-indicator-#{$component}"); 101 | height: createThemeVar("height-indicator-#{$component}"); 102 | border-radius: 2px; 103 | margin: 0 1px; 104 | color: createThemeVar("textColor-indicator-#{$component}"); 105 | background-color: createThemeVar("backgroundColor-indicator-#{$component}"); 106 | 107 | &:hover { 108 | background-color: createThemeVar("backgroundColor-indicator-hover-#{$component}"); 109 | color: createThemeVar("textColor-indicator-hover-#{$component}"); 110 | } 111 | 112 | &.active { 113 | background-color: createThemeVar("backgroundColor-indicator-active-#{$component}"); 114 | color: createThemeVar("textColor-indicator-active-#{$component}"); 115 | } 116 | } 117 | } 118 | 119 | 120 | // --- We export the theme variables to add them to the component renderer 121 | :export { 122 | themeVars: t.json-stringify($themeVars); 123 | } ``` -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "$schema": "https://turborepo.com/schema.json", 3 | "globalEnv": ["CI"], 4 | "ui": "tui", 5 | "envMode": "loose", 6 | "tasks": { 7 | "build:bin": { 8 | "outputs": ["dist/scripts/**"] 9 | }, 10 | "build:xmlui": { 11 | "outputs": ["dist/lib/**"] 12 | }, 13 | "build:xmlui-standalone": { 14 | "outputs": ["dist/standalone/**"] 15 | }, 16 | "build:xmlui-metadata": { 17 | "inputs": ["$TURBO_DEFAULT$", "!src/language-server/xmlui-metadata-generated.mjs"], 18 | "outputs": ["dist/metadata/**"] 19 | }, 20 | "gen:langserver-metadata": { 21 | "dependsOn": ["build:xmlui-metadata"], 22 | "outputs": ["src/language-server/xmlui-metadata-generated.mjs"] 23 | }, 24 | "build:extension": { 25 | "dependsOn": ["^build:extension"], 26 | "outputs": ["dist/**"] 27 | }, 28 | "build:meta": { 29 | "outputs": ["dist/**"] 30 | }, 31 | "xmlui-vscode#build:vsix": { 32 | "dependsOn": ["xmlui-vscode#build"], 33 | "outputs": ["xmlui-vscode-*.vsix"] 34 | }, 35 | "xmlui-vscode#build": { 36 | "dependsOn": ["^gen:langserver-metadata"], 37 | "outputs": ["dist/**"] 38 | }, 39 | "build:xmlui-all": { 40 | "dependsOn": [ 41 | "build:bin", 42 | "build:xmlui-metadata", 43 | "build:xmlui", 44 | "build:xmlui-standalone", 45 | "build:extension" 46 | ], 47 | "outputs": ["dist/**"] 48 | }, 49 | "gen:releases": { 50 | "env": ["GITHUB_TOKEN", "DOCS_XMLUI_MAX_RELEASES_LENGTH"], 51 | "outputs": ["public/resources/files/releases.json"] 52 | }, 53 | "gen:download-latest-xmlui-release": { 54 | "outputs": ["public/resources/files/for-download/xmlui/xmlui-standalone.umd.js"] 55 | }, 56 | "generate-docs": { 57 | "outputs": ["scripts/generate-docs/metadata/**", "$TURBO_ROOT$/docs/content/**"], 58 | "inputs": ["$TURBO_DEFAULT$"], 59 | "dependsOn": ["build:xmlui-metadata", "build:meta"] 60 | }, 61 | "generate-docs-summaries": { 62 | "outputs": ["$TURBO_ROOT$/docs/content/**"], 63 | "inputs": ["$TURBO_DEFAULT$", "dist/metadata/**"], 64 | "dependsOn": ["generate-docs"] 65 | }, 66 | "build:docs": { 67 | "dependsOn": [ 68 | "^build:extension", 69 | "^build:xmlui", 70 | "gen:releases", 71 | "gen:download-latest-xmlui-release", 72 | "generate-docs-summaries" 73 | ], 74 | "outputs": ["dist/**"] 75 | }, 76 | "build:blog": { 77 | "dependsOn": [ 78 | "^build:extension", 79 | "^build:xmlui" 80 | ], 81 | "outputs": ["dist/**"] 82 | }, 83 | "build:xmlui-test-bed": { 84 | "dependsOn": ["build:extension"], 85 | "outputs": ["src/testing/infrastructure/dist/**"] 86 | }, 87 | "build:test-bed": { 88 | "dependsOn": ["build:extension"], 89 | "outputs": ["dist/**"] 90 | }, 91 | "test:unit": {}, 92 | "test:e2e-smoke": { 93 | "dependsOn": ["build:test-bed", "build:xmlui-test-bed"], 94 | "outputs": ["playwright-report/**"] 95 | }, 96 | "test:e2e-non-smoke": { 97 | "dependsOn": ["build:test-bed", "build:xmlui-test-bed", "test:e2e-smoke"], 98 | "outputs": ["playwright-report/**"] 99 | }, 100 | "test:xmlui-all": { 101 | "dependsOn": ["test:unit", "test:e2e-non-smoke"], 102 | "outputs": ["playwright-report/**"] 103 | }, 104 | "test:xmlui-smoke": { 105 | "dependsOn": ["test:unit", "test:e2e-smoke"], 106 | "outputs": ["playwright-report/**"] 107 | } 108 | } 109 | } 110 | ``` -------------------------------------------------------------------------------- /xmlui/src/components/Splitter/Splitter.module.scss: -------------------------------------------------------------------------------- ```scss 1 | @use "../../components-core/theming/themes" as t; 2 | 3 | // --- This code snippet is required to collect the theme variables used in this module 4 | $themeVars: (); 5 | @function createThemeVar($componentVariable) { 6 | $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global; 7 | @return t.getThemeVar($themeVars, $componentVariable); 8 | } 9 | 10 | $component: "Splitter"; 11 | $boxShadow-Splitter: createThemeVar("boxShadow-#{$component}"); 12 | $backgroundColor-Splitter: createThemeVar("backgroundColor-#{$component}"); 13 | $borderRadius-Splitter: createThemeVar("borderRadius-#{$component}"); 14 | $borderColor-Splitter: createThemeVar("borderColor-#{$component}"); 15 | $borderWidth-Splitter: createThemeVar("borderWidth-#{$component}"); 16 | $borderStyle-Splitter: createThemeVar("borderStyle-#{$component}"); 17 | $border-Splitter: createThemeVar("border-#{$component}"); 18 | $backgroundColor-resizer-Splitter: createThemeVar("backgroundColor-resizer-#{$component}"); 19 | $thickness-resizer-Splitter: createThemeVar("thickness-resizer-#{$component}"); 20 | $cursor-resizer-horizontal-Splitter: createThemeVar("cursor-resizer-horizontal-#{$component}"); 21 | $cursor-resizer-vertical-Splitter: createThemeVar("cursor-resizer-vertical-#{$component}"); 22 | 23 | @layer components { 24 | .splitter { 25 | overflow: hidden; 26 | position: relative; 27 | flex-wrap: nowrap; 28 | display: flex; 29 | height: 100%; 30 | width: 100%; 31 | box-shadow: $boxShadow-Splitter; 32 | background-color: $backgroundColor-Splitter; 33 | border-radius: $borderRadius-Splitter; 34 | border: $border-Splitter $borderStyle-Splitter $borderColor-Splitter; 35 | user-select: none; 36 | 37 | &.vertical { 38 | flex-direction: column; 39 | } 40 | 41 | &.horizontal { 42 | flex-direction: row; 43 | } 44 | } 45 | 46 | .panel { 47 | overflow: hidden; 48 | display: flex; 49 | flex-direction: column; 50 | flex: 1; 51 | } 52 | 53 | .primaryPanel { 54 | flex-shrink: 0; 55 | flex-grow: 0; 56 | overflow: auto; 57 | order: 1; 58 | } 59 | 60 | .secondaryPanel { 61 | flex: 1; 62 | overflow: auto; 63 | order: 3; 64 | } 65 | 66 | .resizer { 67 | order: 2; 68 | overflow: hidden; 69 | background-color: t.useVar($backgroundColor-resizer-Splitter, transparent); 70 | 71 | &.vertical { 72 | cursor: t.useVar($cursor-resizer-vertical-Splitter, ns-resize); 73 | height: t.useVar($thickness-resizer-Splitter, 5px); 74 | } 75 | 76 | &.horizontal { 77 | cursor: t.useVar($cursor-resizer-horizontal-Splitter, ew-resize); 78 | width: t.useVar($thickness-resizer-Splitter, 5px); 79 | } 80 | } 81 | 82 | .floatingResizer { 83 | position: absolute; 84 | z-index: 1000; 85 | opacity: 0; 86 | overflow: hidden; 87 | transition: opacity 0.2s; 88 | background-color: t.useVar($backgroundColor-resizer-Splitter, transparent); 89 | 90 | &.vertical { 91 | cursor: t.useVar($cursor-resizer-vertical-Splitter, ns-resize); 92 | width: 100%; 93 | height: t.useVar($thickness-resizer-Splitter, 5px); 94 | transform: translateY(-50%); 95 | } 96 | 97 | &.horizontal { 98 | cursor: t.useVar($cursor-resizer-horizontal-Splitter, ew-resize); 99 | width: t.useVar($thickness-resizer-Splitter, 5px); 100 | height: 100%; 101 | transform: translateX(-50%); 102 | } 103 | } 104 | } 105 | 106 | // --- We export the theme variables to add them to the component renderer 107 | :export { 108 | themeVars: t.json-stringify($themeVars); 109 | } 110 | ``` -------------------------------------------------------------------------------- /xmlui/src/components/Carousel/Carousel.module.scss: -------------------------------------------------------------------------------- ```scss 1 | @use "../../components-core/theming/themes" as t; 2 | 3 | // --- This code snippet is required to collect the theme variables used in this module 4 | $themeVars: (); 5 | @function createThemeVar($componentVariable) { 6 | $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global; 7 | @return t.getThemeVar($themeVars, $componentVariable); 8 | } 9 | 10 | $component: "Carousel"; 11 | 12 | @layer components { 13 | .carousel { 14 | position: relative; 15 | overflow: hidden; 16 | width: createThemeVar("width-#{$component}"); 17 | height: createThemeVar("height-#{$component}"); 18 | justify-content: center; 19 | align-items: center; 20 | } 21 | 22 | .innerWrapper { 23 | display: flex; 24 | width: 100%; 25 | height: 100%; 26 | flex-direction: row; 27 | justify-content: center; 28 | } 29 | 30 | .carouselContentWrapper { 31 | overflow: hidden; 32 | height: 100%; 33 | } 34 | 35 | .carouselContent { 36 | display: flex; 37 | height: 100%; 38 | } 39 | 40 | .vertical { 41 | flex-direction: column; 42 | } 43 | 44 | .carouselItem { 45 | overflow: hidden; 46 | min-width: 0; 47 | flex-shrink: 0; 48 | flex-grow: 0; 49 | flex-basis: 100%; 50 | } 51 | 52 | .controls { 53 | z-index: 99; 54 | display: flex; 55 | right: 1rem; 56 | bottom: 1rem; 57 | position: absolute; 58 | gap: .6rem; 59 | } 60 | 61 | .controlButton { 62 | transition: all .3s; 63 | justify-content: center; 64 | align-items: center; 65 | display: inline-flex; 66 | height: createThemeVar("height-control-#{$component}"); 67 | width: createThemeVar("width-control-#{$component}"); 68 | color: createThemeVar("textColor-control-#{$component}"); 69 | background-color: createThemeVar("backgroundColor-control-#{$component}");; 70 | border-radius: createThemeVar("borderRadius-control-#{$component}"); 71 | } 72 | 73 | .controlButton:hover { 74 | background-color: createThemeVar("backgroundColor-control-hover-#{$component}"); 75 | color: createThemeVar("textColor-control-hover-#{$component}"); 76 | } 77 | 78 | .controlButton:active { 79 | background-color: createThemeVar("backgroundColor-control-active-#{$component}"); 80 | color: createThemeVar("textColor-control-active-#{$component}"); 81 | } 82 | 83 | .controlButton:disabled { 84 | color: createThemeVar("textColor-control-disabled-#{$component}"); 85 | background-color: createThemeVar("backgroundColor-control-disabled-#{$component}"); 86 | } 87 | 88 | .indicators { 89 | position: absolute; 90 | bottom: 1rem; 91 | left: 50%; 92 | transform: translateX(-50%); 93 | } 94 | 95 | .indicator { 96 | transition: all .3s; 97 | width: createThemeVar("width-indicator-#{$component}"); 98 | height: createThemeVar("height-indicator-#{$component}"); 99 | border-radius: 2px; 100 | margin: 0 1px; 101 | color: createThemeVar("textColor-indicator-#{$component}"); 102 | background-color: createThemeVar("backgroundColor-indicator-#{$component}"); 103 | 104 | &:hover { 105 | background-color: createThemeVar("backgroundColor-indicator-hover-#{$component}"); 106 | color: createThemeVar("textColor-indicator-hover-#{$component}"); 107 | } 108 | 109 | &.active { 110 | background-color: createThemeVar("backgroundColor-indicator-active-#{$component}"); 111 | color: createThemeVar("textColor-indicator-active-#{$component}"); 112 | } 113 | } 114 | } 115 | 116 | 117 | // --- We export the theme variables to add them to the component renderer 118 | :export { 119 | themeVars: t.json-stringify($themeVars); 120 | } 121 | 122 | 123 | 124 | ``` -------------------------------------------------------------------------------- /xmlui/tests/parsers/scripting/eval-tree-pre-post.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { describe, expect, it } from "vitest"; 2 | 3 | import { evalBindingExpression } from "../../../src/components-core/script-runner/eval-tree-sync"; 4 | import { createEvalContext } from "./test-helpers"; 5 | 6 | describe("Evaluate prefix/postfix expressions", () => { 7 | const prePostCases1 = [ 8 | { src: "++x", ctx: { x: 0 }, exp: 1, varExp: 1 }, 9 | { src: "--x", ctx: { x: 0 }, exp: -1, varExp: -1 }, 10 | { src: "x++", ctx: { x: 0 }, exp: 0, varExp: 1 }, 11 | { src: "x--", ctx: { x: 0 }, exp: 0, varExp: -1 } 12 | ]; 13 | prePostCases1.forEach(c => { 14 | it(`Prefix/postfix '${c.src}'`, () => { 15 | // --- Arrange 16 | const context = createEvalContext({ 17 | localContext: c.ctx 18 | }); 19 | 20 | // --- Act 21 | const value = evalBindingExpression(c.src, context); 22 | 23 | // --- Arrange 24 | expect(value).equal(c.exp); 25 | expect(context.localContext["x"]).equal(c.varExp); 26 | }); 27 | }); 28 | const prePostCases2 = [ 29 | { src: "++j[2]", exp: 4, varExp: 4 }, 30 | { src: "--j[2]", exp: 2, varExp: 2 }, 31 | { src: "j[1+1]++", exp: 3, varExp: 4 }, 32 | { src: "j[4-2]--", exp: 3, varExp: 2 } 33 | ]; 34 | prePostCases2.forEach(c => { 35 | it(`Prefix/postfix '${c.src}'`, () => { 36 | // --- Arrange 37 | const jArr = [1, 2, 3]; 38 | const context = createEvalContext({ 39 | localContext: { 40 | j: jArr 41 | } 42 | }); 43 | 44 | // --- Act 45 | const value = evalBindingExpression(c.src, context); 46 | 47 | // --- Arrange 48 | expect(value).equal(c.exp); 49 | expect(context.localContext.j[2]).equal(c.varExp); 50 | }); 51 | }); 52 | 53 | const prePostCases3 = [ 54 | { src: "++j[2].h", exp: 4, varExp: 4 }, 55 | { src: "--j[2].h", exp: 2, varExp: 2 }, 56 | { src: "j[1+1].h++", exp: 3, varExp: 4 }, 57 | { src: "j[4-2].h--", exp: 3, varExp: 2 } 58 | ]; 59 | prePostCases3.forEach(c => { 60 | it(`Prefix/postfix '${c.src}'`, () => { 61 | // --- Arrange 62 | const jArr = [{ h: 1 }, { h: 2 }, { h: 3 }]; 63 | const context = createEvalContext({ 64 | localContext: { 65 | j: jArr 66 | } 67 | }); 68 | 69 | // --- Act 70 | const value = evalBindingExpression(c.src, context); 71 | 72 | // --- Arrange 73 | expect(value).equal(c.exp); 74 | expect(context.localContext.j[2].h).equal(c.varExp); 75 | }); 76 | }); 77 | 78 | const prePostCases4 = [ 79 | { src: "++j.h[2]", exp: 4, varExp: 4 }, 80 | { src: "--j.h[2]", exp: 2, varExp: 2 }, 81 | { src: "j.h[1+1]++", exp: 3, varExp: 4 }, 82 | { src: "j.h[4-2]--", exp: 3, varExp: 2 } 83 | ]; 84 | prePostCases4.forEach(c => { 85 | it(`Prefix/postfix '${c.src}'`, () => { 86 | // --- Arrange 87 | const jArr = [1, 2, 3]; 88 | const context = createEvalContext({ 89 | localContext: { 90 | j: { h: jArr } 91 | } 92 | }); 93 | 94 | // --- Act 95 | const value = evalBindingExpression(c.src, context); 96 | 97 | // --- Arrange 98 | expect(value).equal(c.exp); 99 | expect(context.localContext.j.h[2]).equal(c.varExp); 100 | }); 101 | }); 102 | 103 | it("Postfix with arrow", () => { 104 | // --- Arrange 105 | const source = "(x => ++x)(count)"; 106 | const context = createEvalContext({ 107 | localContext: { 108 | count: 6 109 | } 110 | }); 111 | 112 | // --- Act 113 | const value = evalBindingExpression(source, context); 114 | 115 | // --- Arrange 116 | expect(value).equal(7); 117 | }); 118 | }); 119 | ``` -------------------------------------------------------------------------------- /packages/xmlui-animations/src/SlideInAnimation.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import { createComponentRenderer, createMetadata } from "xmlui"; 2 | import { Animation, defaultProps } from "./AnimationNative"; 3 | 4 | const COMP = "SlideInAnimation"; 5 | 6 | const defaultAnimationValues = { 7 | direction: "left", 8 | }; 9 | 10 | export const SlideInAnimationMd = createMetadata({ 11 | status: "experimental", 12 | description: `The \`${COMP}\` component represents an animation that slides in the content from the left.`, 13 | docFolder: "src", 14 | props: { 15 | direction: { 16 | description: "The direction of the animation.", 17 | valueType: "string", 18 | options: ["left", "right", "top", "bottom"], 19 | defaultValue: defaultAnimationValues.direction, 20 | }, 21 | duration: { 22 | description: "The duration of the animation in milliseconds.", 23 | valueType: "number", 24 | }, 25 | animateWhenInView: { 26 | description: "Indicates whether the animation should start when the component is in view.", 27 | valueType: "boolean", 28 | }, 29 | reverse: { 30 | description: `Indicates whether the animation should run in reverse`, 31 | defaultValue: defaultProps.reverse, 32 | valueType: "boolean", 33 | }, 34 | loop: { 35 | description: `Indicates whether the animation should loop`, 36 | defaultValue: defaultProps.loop, 37 | valueType: "boolean", 38 | }, 39 | delay: { 40 | description: `The delay before the animation starts in milliseconds`, 41 | defaultValue: defaultProps.delay, 42 | valueType: "number", 43 | }, 44 | }, 45 | events: { 46 | started: { description: `Event fired when the animation starts` }, 47 | stopped: { description: `Event fired when the animation stops` }, 48 | }, 49 | apis: { 50 | start: { description: `Starts the animation` }, 51 | stop: { description: `Stops the animation` }, 52 | }, 53 | }); 54 | 55 | export const slideInAnimationRenderer = createComponentRenderer( 56 | "SlideInAnimation", 57 | SlideInAnimationMd, 58 | ({ node, renderChild, extractValue, registerComponentApi, lookupEventHandler }) => { 59 | const direction = extractValue.asOptionalString(node.props.direction, defaultAnimationValues.direction); 60 | const animation = { 61 | from: { 62 | transform: 63 | direction === "right" 64 | ? "translateX(100%)" 65 | : direction === "left" 66 | ? "translateX(-100%)" 67 | : direction === "top" 68 | ? "translateY(-100%)" 69 | : "translateY(100%)", 70 | }, 71 | to: { 72 | transform: 73 | direction === "right" || direction === "left" ? "translateX(0)" : "translateY(0)", 74 | }, 75 | }; 76 | return ( 77 | <Animation 78 | registerComponentApi={registerComponentApi} 79 | animation={animation} 80 | duration={extractValue.asOptionalNumber(node.props.duration)} 81 | onStop={lookupEventHandler("stopped")} 82 | onStart={lookupEventHandler("started")} 83 | animateWhenInView={extractValue.asOptionalBoolean(node.props.animateWhenInView)} 84 | reverse={extractValue.asOptionalBoolean(node.props.reverse)} 85 | loop={extractValue.asOptionalBoolean(node.props.loop)} 86 | delay={extractValue.asOptionalNumber(node.props.delay)} 87 | > 88 | {renderChild(node.children)} 89 | </Animation> 90 | ); 91 | }, 92 | ); 93 | ``` -------------------------------------------------------------------------------- /xmlui/tests/components-core/scripts-runner/eval-tree-pre-post.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { describe, expect, it } from "vitest"; 2 | 3 | import { evalBindingExpression } from "../../../src/components-core/script-runner/eval-tree-sync"; 4 | import { createEvalContext } from "./test-helpers"; 5 | 6 | describe("Evaluate prefix/postfix expressions (exp)", () => { 7 | const prePostCases1 = [ 8 | { src: "++x", ctx: { x: 0 }, exp: 1, varExp: 1 }, 9 | { src: "--x", ctx: { x: 0 }, exp: -1, varExp: -1 }, 10 | { src: "x++", ctx: { x: 0 }, exp: 0, varExp: 1 }, 11 | { src: "x--", ctx: { x: 0 }, exp: 0, varExp: -1 } 12 | ]; 13 | prePostCases1.forEach(c => { 14 | it(`Prefix/postfix '${c.src}'`, () => { 15 | // --- Arrange 16 | const context = createEvalContext({ 17 | localContext: c.ctx 18 | }); 19 | 20 | // --- Act 21 | const value = evalBindingExpression(c.src, context); 22 | 23 | // --- Arrange 24 | expect(value).equal(c.exp); 25 | expect(context.localContext["x"]).equal(c.varExp); 26 | }); 27 | }); 28 | const prePostCases2 = [ 29 | { src: "++j[2]", exp: 4, varExp: 4 }, 30 | { src: "--j[2]", exp: 2, varExp: 2 }, 31 | { src: "j[1+1]++", exp: 3, varExp: 4 }, 32 | { src: "j[4-2]--", exp: 3, varExp: 2 } 33 | ]; 34 | prePostCases2.forEach(c => { 35 | it(`Prefix/postfix '${c.src}'`, () => { 36 | // --- Arrange 37 | const jArr = [1, 2, 3]; 38 | const context = createEvalContext({ 39 | localContext: { 40 | j: jArr 41 | } 42 | }); 43 | 44 | // --- Act 45 | const value = evalBindingExpression(c.src, context); 46 | 47 | // --- Arrange 48 | expect(value).equal(c.exp); 49 | expect(context.localContext.j[2]).equal(c.varExp); 50 | }); 51 | }); 52 | 53 | const prePostCases3 = [ 54 | { src: "++j[2].h", exp: 4, varExp: 4 }, 55 | { src: "--j[2].h", exp: 2, varExp: 2 }, 56 | { src: "j[1+1].h++", exp: 3, varExp: 4 }, 57 | { src: "j[4-2].h--", exp: 3, varExp: 2 } 58 | ]; 59 | prePostCases3.forEach(c => { 60 | it(`Prefix/postfix '${c.src}'`, () => { 61 | // --- Arrange 62 | const jArr = [{ h: 1 }, { h: 2 }, { h: 3 }]; 63 | const context = createEvalContext({ 64 | localContext: { 65 | j: jArr 66 | } 67 | }); 68 | 69 | // --- Act 70 | const value = evalBindingExpression(c.src, context); 71 | 72 | // --- Arrange 73 | expect(value).equal(c.exp); 74 | expect(context.localContext.j[2].h).equal(c.varExp); 75 | }); 76 | }); 77 | 78 | const prePostCases4 = [ 79 | { src: "++j.h[2]", exp: 4, varExp: 4 }, 80 | { src: "--j.h[2]", exp: 2, varExp: 2 }, 81 | { src: "j.h[1+1]++", exp: 3, varExp: 4 }, 82 | { src: "j.h[4-2]--", exp: 3, varExp: 2 } 83 | ]; 84 | prePostCases4.forEach(c => { 85 | it(`Prefix/postfix '${c.src}'`, () => { 86 | // --- Arrange 87 | const jArr = [1, 2, 3]; 88 | const context = createEvalContext({ 89 | localContext: { 90 | j: { h: jArr } 91 | } 92 | }); 93 | 94 | // --- Act 95 | const value = evalBindingExpression(c.src, context); 96 | 97 | // --- Arrange 98 | expect(value).equal(c.exp); 99 | expect(context.localContext.j.h[2]).equal(c.varExp); 100 | }); 101 | }); 102 | 103 | it("Postfix with arrow", () => { 104 | // --- Arrange 105 | const source = "(x => ++x)(count)"; 106 | const context = createEvalContext({ 107 | localContext: { 108 | count: 6 109 | } 110 | }); 111 | 112 | // --- Act 113 | const value = evalBindingExpression(source, context); 114 | 115 | // --- Arrange 116 | expect(value).equal(7); 117 | }); 118 | }); 119 | ``` -------------------------------------------------------------------------------- /xmlui/src/components-core/script-runner/statement-queue.ts: -------------------------------------------------------------------------------- ```typescript 1 | import type { Statement } from "./ScriptingSourceTree"; 2 | 3 | export type StatementRunTimeInfo = { 4 | guard?: boolean; 5 | keys?: string[]; 6 | keyIndex?: number; 7 | iterator?: any; 8 | removeBlockScope?: boolean; 9 | }; 10 | 11 | export type StatementWithInfo = { 12 | // --- The description of the statement to execute 13 | statement: Statement; 14 | 15 | // --- Execution information for the statement 16 | execInfo?: StatementRunTimeInfo; 17 | }; 18 | 19 | // An item of the queue we use for processing a statement 20 | export type StatementQueueItem = StatementWithInfo & { 21 | // --- The label of the item. We need this information for "break" and "continue" statements 22 | label: number; 23 | }; 24 | 25 | // The outcome of processing a statement of the queue 26 | export type ProcessOutcome = { 27 | // --- New statements to unshift into the queue (optional) 28 | toUnshift?: StatementQueueItem[]; 29 | 30 | // --- Label from which statements should be preserved in the queue 31 | clearToLabel?: number; 32 | }; 33 | 34 | // --- Implements the statement queue 35 | export class StatementQueue { 36 | private items: StatementQueueItem[] = []; 37 | 38 | // --- The current queue length 39 | get length(): number { 40 | return this.items.length; 41 | } 42 | 43 | // --- Get the subsequent item in the queue without dequeuing it 44 | peek(): StatementQueueItem | undefined { 45 | return this.items[0]; 46 | } 47 | 48 | // --- Remove and return the subsequent item from the queue 49 | dequeue(): StatementQueueItem | undefined { 50 | return this.items.shift(); 51 | } 52 | 53 | // --- Push the specified items into the queue and return the corresponding queue items 54 | push(statements: StatementQueueItem[]): void { 55 | this.items.push(...statements); 56 | } 57 | 58 | // --- Unshift the specified items into the queue in reverse order (starting from the last action) and 59 | // --- return the corresponding queue items 60 | unshift(statements: StatementQueueItem[]): void { 61 | this.items.unshift(...statements); 62 | } 63 | 64 | // --- Remove all items starting from the beginning of the queue, while not reaching the item with the 65 | // --- specified label. If the label is found, that remains in the queue; otherwise the queue is emptied 66 | clearToLabel(label: number): void { 67 | while (this.items.length > 0 && this.items[0].label !== label) { 68 | this.items.shift(); 69 | } 70 | } 71 | } 72 | 73 | // --- Queue information for test and diagnostics purposes 74 | export type QueueInfo = { 75 | processedStatements: number; 76 | maxQueueLength: number; 77 | unshiftedItems: number; 78 | clearToLabels: number; 79 | maxBlocks: number; 80 | maxLoops: number; 81 | }; 82 | 83 | // --- The ID of the last used label 84 | let nextLabelValue = 1; 85 | 86 | // --- Map a single item to a queue statement list 87 | export function mapToItem(statement: Statement): StatementQueueItem[] { 88 | return [ 89 | { 90 | label: nextLabelValue++, 91 | statement: { ...statement }, 92 | } as StatementQueueItem, 93 | ]; 94 | } 95 | 96 | // --- Map a single statement into a queue item 97 | function mapStatementToQueueItem(stmt: StatementWithInfo): StatementQueueItem { 98 | return { 99 | label: nextLabelValue++, 100 | statement: stmt.statement, 101 | execInfo: { ...stmt.execInfo }, 102 | }; 103 | } 104 | 105 | // --- Map a list of statements into queue items 106 | export function mapStatementsToQueueItems(statements: StatementWithInfo[]): StatementQueueItem[] { 107 | return statements.map(mapStatementToQueueItem); 108 | } 109 | ``` -------------------------------------------------------------------------------- /xmlui/src/components-core/interception/abstractions.ts: -------------------------------------------------------------------------------- ```typescript 1 | import type { DelayMode } from "msw"; 2 | 3 | export type AuthDefinition = { 4 | defaultLoggedInUser?: any 5 | }; 6 | 7 | export type TableDescriptor = { 8 | name: string, 9 | fields?: Record<string, any>, 10 | pk: Array<string>; 11 | indexes?: Array<string>; 12 | } 13 | 14 | export type SchemaDescriptor = { 15 | tables: Array<TableDescriptor>, 16 | relationships?: any; 17 | dtos?: any; 18 | } 19 | 20 | // Represents an interceptor between API consumers (UI in the browser) and their backend 21 | export type ApiInterceptorDefinition = { 22 | // Type of backend interceptor 23 | type?: string; 24 | 25 | // Interceptor configuration 26 | config?: Record<string, any>; 27 | 28 | artificialDelay?: number | DelayMode; 29 | 30 | // Information for code generators 31 | schemaDescriptor?: SchemaDescriptor 32 | 33 | // URL to particular interceptor is bound to 34 | apiUrl?: string; 35 | 36 | // Initial data to set up (initialize) the interceptor 37 | initialData?: Record<string, any[]> | (() => Promise<Record<string, any[]>>); 38 | 39 | helpers?: Record<string, any>; 40 | initialize?: string; 41 | 42 | // Available operations 43 | operations?: Record<string, InterceptorOperationDef>; 44 | 45 | auth?: AuthDefinition; 46 | 47 | useWorker?: boolean; 48 | }; 49 | 50 | // Represents a single operation of the interceptor 51 | export type InterceptorOperationDef = { 52 | // Operation method (can be extended in the future) 53 | method: "get" | "post" | "put" | "delete" | "patch" | "head" | "options"; 54 | 55 | // Operation URL(s) 56 | url: string | Array<string>; 57 | 58 | // The source code of the operation's handler. This code is parsed and executed 59 | handler: string; 60 | 61 | // Shape of the request this operation expects 62 | requestShape?: any; 63 | 64 | // Shape of the response this operation returns 65 | responseShape?: any; 66 | 67 | // Type mappings of path parameters 68 | pathParamTypes?: Record<string, string> 69 | 70 | // Type mappings of query parameters 71 | queryParamTypes?: Record<string, string>; 72 | 73 | successStatusCode?: number; 74 | }; 75 | 76 | // Describes the backend environment (change it for mocked tests) 77 | export type BackendEnvironment = { 78 | getDate: () => Date; 79 | }; 80 | 81 | // Represents the persistence service (database) of the backend 82 | // TODO: Review this interface 83 | export interface IDatabase { 84 | [key: string]: unknown; 85 | getItems(collectionName: string): Promise<Array<any>>; 86 | getItem(collectionName: string, predicate: (item: any) => Promise<boolean>): Promise<any>; 87 | getItemById(collectionName: string, id: any): Promise<any>; 88 | deleteItems(collectionName: string, predicate: (item: any) => Promise<boolean>): Promise<void>; 89 | insertItem(collectionName: string, item: any): Promise<any>; 90 | updateItem(collectionName: string, item: any): Promise<any>; 91 | updateItem(collectionName: string, item: any): Promise<any>; 92 | } 93 | 94 | // Represents the request parameters the backed translates to operations 95 | export type RequestParams = { 96 | // URL path parameters 97 | pathParams: Record<string, any>; 98 | 99 | // URL query parameters 100 | queryParams: Record<string, any>; 101 | 102 | // The body of a request 103 | requestBody: any; 104 | 105 | cookies: any; 106 | 107 | requestHeaders: Record<string, string>; 108 | }; 109 | 110 | // Represents the definition of a particular backed 111 | export type BackendDefinition = { 112 | initialData?: Record<string, any[]>; 113 | initialize?: string; 114 | helpers?: Record<string, any>; 115 | operations: Record<string, string>; 116 | }; 117 | ``` -------------------------------------------------------------------------------- /docs/public/pages/howto/sync-selections-between-table-and-list-views.md: -------------------------------------------------------------------------------- ```markdown 1 | # Sync selections between a Table view and a List view 2 | 3 | The Table view syncs selections automatically via `syncWithAppState`, while the Tiles (List) view uses checkboxes that manually coordinate with AppState. 4 | 5 | ```xmlui-pg 6 | ---app 7 | <App> 8 | <Files /> 9 | </App> 10 | ---api 11 | { 12 | "apiUrl": "/api", 13 | "initialize": "$state.files = [ 14 | { id: 1, name: 'document.pdf', size: '2.3 MB', type: 'PDF' }, 15 | { id: 2, name: 'image.jpg', size: '1.1 MB', type: 'Image' }, 16 | { id: 3, name: 'spreadsheet.xlsx', size: '856 KB', type: 'Excel' }, 17 | { id: 4, name: 'presentation.pptx', size: '4.2 MB', type: 'PowerPoint' }, 18 | { id: 5, name: 'text.txt', size: '12 KB', type: 'Text' } 19 | ]", 20 | "operations": { 21 | "get-files": { 22 | "url": "/files", 23 | "method": "get", 24 | "handler": "return $state.files" 25 | } 26 | } 27 | } 28 | ---comp display /selections/ /sharedSelections/ /selectedIds/ 29 | <Component name="Files"> 30 | <AppState id="selections" bucket="sharedSelections" initialValue="{{ selectedIds: [] }}" /> 31 | <DataSource id="files" url="/api/files" /> 32 | <Text>Selected: {selections.value.selectedIds.length} items</Text> 33 | <Tabs> 34 | <TabItem label="Table View"> 35 | <TableView data="{files}" /> 36 | </TabItem> 37 | <TabItem label="Tiles View"> 38 | <TilesView data="{files}" /> 39 | </TabItem> 40 | <TabItem label="Debug"> 41 | <DebugView /> 42 | </TabItem> 43 | </Tabs> 44 | </Component> 45 | ---comp display /selections/ /sharedSelections/ /selectedIds/ 46 | <Component name="TableView"> 47 | <AppState id="selections" bucket="sharedSelections" /> 48 | <Card> 49 | <Table 50 | data="{$props.data}" 51 | rowsSelectable="true" 52 | syncWithAppState="{selections}" 53 | loading="{!$props.data}" 54 | > 55 | <Column bindTo="name" /> 56 | <Column bindTo="size" /> 57 | <Column bindTo="type" /> 58 | </Table> 59 | </Card> 60 | </Component> 61 | ---comp display /selections/ /sharedSelections/ /selectedIds/ 62 | <Component name="TilesView"> 63 | <AppState id="selections" bucket="sharedSelections" /> 64 | <List data="{$props.data}"> 65 | <Card> 66 | <HStack verticalAlignment="center"> 67 | <Checkbox 68 | initialValue="{selections.value.selectedIds?.includes($item.id) || false}" 69 | onDidChange="(checked) => { 70 | const ids = selections.value.selectedIds || []; 71 | if (checked) { 72 | selections.update({ selectedIds: [...ids, $item.id] }); 73 | } else { 74 | selections.update({ selectedIds: ids.filter(id => id !== $item.id) }); 75 | } 76 | }" 77 | /> 78 | <VStack> 79 | <Text>{$item.name}</Text> 80 | <Text variant="caption">{$item.size} - {$item.type}</Text> 81 | </VStack> 82 | </HStack> 83 | </Card> 84 | </List> 85 | </Component> 86 | ---desc 87 | The spread operator `...ids` takes all existing IDs from the array, and creates a new array with those IDs plus the new one. 88 | 89 | The filter method creates a new array with only items that meet a condition. 90 | ---comp display /selections/ /sharedSelections/ /selectedIds/ 91 | <Component name="DebugView"> 92 | <AppState id="selections" bucket="sharedSelections" /> 93 | <Card> 94 | <VStack> 95 | <Text>selections.value</Text> 96 | <Text variant="code">{JSON.stringify(selections.value, null, 2)}</Text> 97 | </VStack> 98 | </Card> 99 | </Component> 100 | ``` 101 | 102 | 103 | 104 | 105 | ``` -------------------------------------------------------------------------------- /xmlui/src/components/PageMetaTitle/PageMetaTitle.spec.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { expect, test } from "../../testing/fixtures"; 2 | 3 | // ============================================================================= 4 | // BASIC FUNCTIONALITY TESTS 5 | // ============================================================================= 6 | 7 | test.describe("Basic Functionality", () => { 8 | test("component renders with default props", async ({ initTestBed, page }) => { 9 | await initTestBed(`<PageMetaTitle />`); 10 | 11 | // PageMetaTitle doesn't render visible DOM elements, it only affects page title 12 | // Default title should be set in browser with test bed suffix 13 | await expect(page).toHaveTitle("XMLUI Application | test bed app"); 14 | }); 15 | 16 | test("component renders with value property", async ({ initTestBed, page }) => { 17 | await initTestBed(`<PageMetaTitle value="Custom Page Title" />`); 18 | 19 | // Custom title should be set in browser with test bed suffix 20 | await expect(page).toHaveTitle("Custom Page Title | test bed app"); 21 | }); 22 | 23 | test("component handles dynamic value changes", async ({ initTestBed, page }) => { 24 | const { testStateDriver } = await initTestBed(` 25 | <VStack> 26 | <PageMetaTitle value="{testState || 'Initial Title'}" /> 27 | <Button onClick="testState = 'Updated Title'">Update Title</Button> 28 | </VStack> 29 | `); 30 | 31 | // Initial title should be set 32 | await expect(page).toHaveTitle("Initial Title | test bed app"); 33 | 34 | // Click button to update title 35 | await page.getByRole("button").click(); 36 | await expect.poll(testStateDriver.testState).toEqual("Updated Title"); 37 | 38 | // Title should be updated in browser 39 | await expect(page).toHaveTitle("Updated Title | test bed app"); 40 | }); 41 | 42 | test("component handles child content as title", async ({ initTestBed, page }) => { 43 | await initTestBed(` 44 | <PageMetaTitle> 45 | Child Content Title 46 | </PageMetaTitle> 47 | `); 48 | 49 | // Child content should be used as title 50 | await expect(page).toHaveTitle("Child Content Title | test bed app"); 51 | }); 52 | 53 | test("value property takes precedence over child content", async ({ initTestBed, page }) => { 54 | await initTestBed(` 55 | <PageMetaTitle value="Value Property Title"> 56 | Child Content That Should Be Ignored 57 | </PageMetaTitle> 58 | `); 59 | 60 | // Value property should take precedence 61 | await expect(page).toHaveTitle("Value Property Title | test bed app"); 62 | }); 63 | 64 | test("multiple PageMetaTitle components - last one wins", async ({ initTestBed, page }) => { 65 | await initTestBed(` 66 | <VStack> 67 | <PageMetaTitle value="First Title" /> 68 | <PageMetaTitle value="Second Title" /> 69 | <PageMetaTitle value="Final Title" /> 70 | </VStack> 71 | `); 72 | 73 | // Last component should set the final title 74 | await expect(page).toHaveTitle("Final Title | test bed app"); 75 | }); 76 | 77 | test("component updates existing page title", async ({ initTestBed, page }) => { 78 | // Set initial title 79 | await initTestBed(`<PageMetaTitle value="Initial Title" />`); 80 | await expect(page).toHaveTitle("Initial Title | test bed app"); 81 | 82 | // Update with new component 83 | await initTestBed(`<PageMetaTitle value="Updated Title" />`); 84 | await expect(page).toHaveTitle("Updated Title | test bed app"); 85 | }); 86 | }); 87 | ``` -------------------------------------------------------------------------------- /xmlui/src/components-core/action/FileUploadAction.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import type { ActionExecutionContext } from "../../abstractions/ActionDefs"; 2 | import type { ComponentDef } from "../../abstractions/ComponentDefs"; 3 | import { extractParam } from "../utils/extractParam"; 4 | import { invalidateQueries } from "../utils/actionUtils"; 5 | import type { ApiActionOptions, UploadOperationDef } from "../RestApiProxy"; 6 | import RestApiProxy from "../RestApiProxy"; 7 | import { createAction } from "./actions"; 8 | 9 | export interface UploadActionComponent extends ComponentDef { 10 | props: { 11 | invalidates?: string | string[]; 12 | } & UploadOperationDef; 13 | events?: { 14 | error?: string; 15 | success?: string; 16 | }; 17 | } 18 | 19 | export type UploadActionParams = { 20 | invalidates?: string | string[]; 21 | params: any; 22 | chunkSizeInBytes?: number; 23 | onError?: string; 24 | onProgress?: (...args: any) => void; 25 | } & UploadOperationDef; 26 | 27 | async function uploadFile( 28 | { appContext, state, lookupAction, uid }: ActionExecutionContext, 29 | { 30 | params, 31 | invalidates, 32 | onError, 33 | queryParams, 34 | asForm, 35 | file, 36 | headers, 37 | url, 38 | method, 39 | formParams, 40 | rawBody, 41 | body, 42 | chunkSizeInBytes, 43 | onProgress, 44 | }: UploadActionParams, 45 | { resolveBindingExpressions }: ApiActionOptions = {}, 46 | ) { 47 | const stateContext = { ...params, ...state }; 48 | const api = new RestApiProxy(appContext); 49 | 50 | const operation: UploadOperationDef = { 51 | file, 52 | body, 53 | asForm, 54 | formParams, 55 | rawBody, 56 | method, 57 | url, 58 | queryParams, 59 | headers, 60 | }; 61 | 62 | let result = null; 63 | try { 64 | const _chunkSizeInBytes = extractParam(stateContext, chunkSizeInBytes, appContext); 65 | const _onProgress = extractParam(stateContext, onProgress, appContext); 66 | if (_chunkSizeInBytes !== undefined) { 67 | const _file = extractParam(stateContext, file, appContext); 68 | const numberOfChunks = Math.ceil(_file.size / _chunkSizeInBytes); 69 | 70 | for (let i = 0; i < numberOfChunks; i++) { 71 | const start = i * _chunkSizeInBytes; 72 | const chunkEnd = Math.min(start + _chunkSizeInBytes, _file.size); 73 | const chunk = _file.slice(start, chunkEnd); 74 | result = await api.upload({ 75 | operation, 76 | chunk: { 77 | blob: chunk, 78 | chunkStart: start, 79 | chunkEnd: chunkEnd, 80 | }, 81 | params: stateContext, 82 | onUploadProgress: (progressEvent) => { 83 | const overallTotal = _file.size; 84 | const overallLoaded = start + progressEvent.loaded; 85 | const overallProgressEvent = { 86 | total: overallTotal, 87 | loaded: overallLoaded, 88 | progress: overallLoaded / overallTotal, 89 | }; 90 | _onProgress?.(overallProgressEvent); 91 | }, 92 | resolveBindingExpressions, 93 | }); 94 | } 95 | } else { 96 | result = await api.upload({ 97 | operation, 98 | params: stateContext, 99 | onUploadProgress: _onProgress, 100 | resolveBindingExpressions, 101 | }); 102 | } 103 | } catch (e) { 104 | const onErrorFn = lookupAction(onError, uid); 105 | const result = await onErrorFn?.(e, stateContext["$param"]); 106 | if (result !== false) { 107 | throw e; 108 | } 109 | } 110 | void invalidateQueries(invalidates, appContext, state); 111 | return result; 112 | } 113 | 114 | export const uploadAction = createAction("upload", uploadFile); 115 | ``` -------------------------------------------------------------------------------- /xmlui/src/components/FileUploadDropZone/FileUploadDropZone.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import styles from "./FileUploadDropZone.module.scss"; 2 | 3 | import { createComponentRenderer } from "../../components-core/renderers"; 4 | import { parseScssVar } from "../../components-core/theming/themeVars"; 5 | import { FileUploadDropZone, defaultProps } from "./FileUploadDropZoneNative"; 6 | import { createMetadata, d } from "../metadata-helpers"; 7 | 8 | const COMP = "FileUploadDropZone"; 9 | 10 | export const FileUploadDropZoneMd = createMetadata({ 11 | status: "stable", 12 | description: 13 | "`FileUploadDropZone` enables users to upload files by dragging and dropping " + 14 | "files from their local file system onto a designated area within the UI.", 15 | props: { 16 | text: { 17 | description: 18 | "With this property, you can change the default text to display when files " + 19 | "are dragged over the drop zone.", 20 | defaultValue: defaultProps.text, 21 | type: "string", 22 | }, 23 | allowPaste: { 24 | description: 25 | "This property indicates if the drop zone accepts files pasted from the " + 26 | "clipboard (\`true\`) or " + 27 | "only dragged files (\`false\`).", 28 | type: "boolean", 29 | defaultValue: defaultProps.allowPaste, 30 | }, 31 | enabled: d( 32 | `If set to \`false\`, the drop zone will be disabled and users will not be able to upload files.`, 33 | null, 34 | "boolean", 35 | true, 36 | ), 37 | acceptedFileTypes: d(`Accepted file MIME types, separated by commas. For example: 'image/*,application/pdf'.`), 38 | maxFiles: d(`The maximum number of files that can be selected.`), 39 | }, 40 | events: { 41 | upload: d( 42 | `This component accepts files for upload but does not perform the actual operation. It fires ` + 43 | `the \`upload\` event and passes the list files to upload in the method's argument. You can ` + 44 | `use the passed file information to implement the upload (according to the protocol your ` + 45 | `backend supports).`, 46 | ), 47 | }, 48 | themeVars: parseScssVar(styles.themeVars), 49 | defaultThemeVars: { 50 | "backgroundColor-FileUploadDropZone": "$backgroundColor", 51 | "backgroundColor-dropping-FileUploadDropZone": "$backgroundColor--selected", 52 | "opacity-dropping-FileUploadDropZone": "0.5", 53 | "textColor-FileUploadDropZone": "$textColor", 54 | light: { 55 | // --- No light-specific theme vars 56 | }, 57 | dark: { 58 | // --- No dark-specific theme vars 59 | }, 60 | }, 61 | }); 62 | 63 | export const fileUploadDropZoneComponentRenderer = createComponentRenderer( 64 | COMP, 65 | FileUploadDropZoneMd, 66 | ({ node, extractValue, updateState, renderChild, lookupEventHandler, registerComponentApi, className }) => { 67 | return ( 68 | <FileUploadDropZone 69 | onUpload={lookupEventHandler("upload")!} 70 | uid={extractValue(node.uid)} 71 | registerComponentApi={registerComponentApi} 72 | className={className} 73 | allowPaste={extractValue(node.props.allowPaste)} 74 | text={extractValue(node.props.text)} 75 | disabled={!extractValue.asOptionalBoolean(node.props.enabled, true)} 76 | updateState={updateState} 77 | acceptedFileTypes={extractValue.asOptionalString(node.props.acceptedFileTypes)} 78 | maxFiles={extractValue.asOptionalNumber(node.props.maxFiles)} 79 | > 80 | {renderChild(node.children, { type: "Stack" })} 81 | </FileUploadDropZone> 82 | ); 83 | }, 84 | ); 85 | ``` -------------------------------------------------------------------------------- /xmlui/src/components-core/utils/audio-utils.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Audio utility functions 3 | */ 4 | 5 | /** 6 | * Generates a beep sound using the Web Audio API 7 | * @param frequency - The frequency of the beep in Hz (default: 800) 8 | * @param durationInMs - The duration of the beep in milliseconds (default: 200) 9 | */ 10 | export function beep(frequency: number = 800, durationInMs: number = 200): void { 11 | // Check if Web Audio API is supported 12 | if (typeof window === 'undefined' || !window.AudioContext && !(window as any).webkitAudioContext) { 13 | console.warn('Web Audio API is not supported in this environment'); 14 | return; 15 | } 16 | 17 | try { 18 | // Create audio context (handle both standard and webkit prefixed versions) 19 | const AudioContextClass = window.AudioContext || (window as any).webkitAudioContext; 20 | const audioContext = new AudioContextClass(); 21 | 22 | // Create oscillator for the tone 23 | const oscillator = audioContext.createOscillator(); 24 | const gainNode = audioContext.createGain(); 25 | 26 | // Connect nodes: oscillator -> gain -> destination 27 | oscillator.connect(gainNode); 28 | gainNode.connect(audioContext.destination); 29 | 30 | // Configure oscillator 31 | oscillator.frequency.setValueAtTime(frequency, audioContext.currentTime); 32 | oscillator.type = 'sine'; // Use sine wave for a clean tone 33 | 34 | // Configure gain (volume) with fade in/out to avoid clicking 35 | const currentTime = audioContext.currentTime; 36 | const endTime = currentTime + durationInMs / 1000; 37 | const fadeTime = Math.min(0.01, durationInMs / 1000 / 4); // Fade time is 1/4 of duration or 10ms, whichever is smaller 38 | 39 | gainNode.gain.setValueAtTime(0, currentTime); 40 | gainNode.gain.linearRampToValueAtTime(0.3, currentTime + fadeTime); // Fade in 41 | gainNode.gain.setValueAtTime(0.3, endTime - fadeTime); 42 | gainNode.gain.linearRampToValueAtTime(0, endTime); // Fade out 43 | 44 | // Start and stop the oscillator 45 | oscillator.start(currentTime); 46 | oscillator.stop(endTime); 47 | 48 | // Clean up after the beep is done 49 | oscillator.addEventListener('ended', () => { 50 | oscillator.disconnect(); 51 | gainNode.disconnect(); 52 | audioContext.close(); 53 | }); 54 | 55 | } catch (error) { 56 | console.warn('Failed to generate beep:', error); 57 | } 58 | } 59 | 60 | /** 61 | * Generates a sequence of beeps 62 | * @param count - Number of beeps 63 | * @param frequency - The frequency of each beep in Hz (default: 800) 64 | * @param durationInMs - The duration of each beep in milliseconds (default: 200) 65 | * @param intervalInMs - The interval between beeps in milliseconds (default: 300) 66 | */ 67 | export function beepSequence( 68 | count: number, 69 | frequency: number = 800, 70 | durationInMs: number = 200, 71 | intervalInMs: number = 300 72 | ): void { 73 | for (let i = 0; i < count; i++) { 74 | setTimeout(() => { 75 | beep(frequency, durationInMs); 76 | }, i * intervalInMs); 77 | } 78 | } 79 | 80 | /** 81 | * Predefined beep patterns for common use cases 82 | */ 83 | export const BeepPatterns = { 84 | /** Short, high-pitched beep for success */ 85 | success: () => beep(1000, 150), 86 | 87 | /** Lower frequency beep for errors */ 88 | error: () => beep(400, 300), 89 | 90 | /** Quick beep for notifications */ 91 | notification: () => beep(800, 100), 92 | 93 | /** Double beep for warnings */ 94 | warning: () => beepSequence(2, 600, 150, 200), 95 | 96 | /** Triple beep for alerts */ 97 | alert: () => beepSequence(3, 900, 100, 150), 98 | } as const; 99 | ``` -------------------------------------------------------------------------------- /docs/content/components/ChangeListener.md: -------------------------------------------------------------------------------- ```markdown 1 | # ChangeListener [#changelistener] 2 | 3 | `ChangeListener` is an invisible component that watches for changes in values and triggers actions in response. It's essential for creating reactive behavior when you need to respond to data changes, state updates, or component property modifications outside of normal event handlers. 4 | 5 | **Key features:** 6 | - **Value monitoring**: Watches any expression, variable, or component property for changes 7 | - **Throttling support**: Prevents excessive triggering with `throttleWaitInMs` for rapid changes 8 | - **Previous/new values**: Access both old and new values in the change handler 9 | - **Reactive patterns**: Coordinates between components or triggers side effects 10 | 11 | ## Properties [#properties] 12 | 13 | ### `listenTo` [#listento] 14 | 15 | Value to the changes of which this component listens. If this property is not set, the `ChangeListener` is inactive. 16 | 17 | The following sample demonstrates using this property. Every time the user clicks the button, a counter is incremented. The `ChangeListener` component watches the counter's value. Whenever it changes, the component fires the `didChange` event, which stores whether the new counter value is even into the `isEven` variable. 18 | 19 | ```xmlui-pg copy display name="Example: listenTo" 20 | <App var.counter="{0}" var.isEven="{false}"> 21 | <Button label="Increment counter" onClick="{counter++}" /> 22 | <ChangeListener 23 | listenTo="{counter}" 24 | onDidChange="isEven = counter % 2 == 0" /> 25 | <Text>Counter is {counter} which {isEven? "is": "isn't"} even.</Text> 26 | </App> 27 | ``` 28 | 29 | ### `throttleWaitInMs` (default: 0) [#throttlewaitinms-default-0] 30 | 31 | This variable sets a throttling time (in milliseconds) to apply when executing the `didChange` event handler. All changes within that throttling time will only fire the `didChange` event once. 32 | 33 | The following example works like the previous one (in the `listen` prop's description). However, the user can reset or set the throttling time to 3 seconds. You can observe that while the throttling time is 3 seconds, the counter increments on every click, but `isEven` only refreshes once within 3 seconds. 34 | 35 | ```xmlui-pg copy display name="Example: throttleWaitInMs" 36 | <App var.counter="{0}" var.isEven="{false}" var.throttle="{0}"> 37 | <HStack> 38 | <Button label="Increment counter" onClick="{counter++}" /> 39 | <Button label="Set 3 sec throttling" onClick="throttle = 3000" /> 40 | <Button label="Reset throttling" onClick="throttle = 0" /> 41 | </HStack> 42 | 43 | <ChangeListener 44 | listenTo="{counter}" 45 | throttleWaitInMs="{throttle}" 46 | onDidChange="isEven = counter % 2 == 0" /> 47 | <Text>Counter is {counter} which {isEven? "is": "isn't"} even.</Text> 48 | </App> 49 | ``` 50 | 51 | ## Events [#events] 52 | 53 | ### `didChange` [#didchange] 54 | 55 | This event is triggered when value of ChangeListener has changed. 56 | 57 | This event is fired when the component observes a value change (within the specified throttling interval). Define the event handler that responds to that change (as the previous samples demonstrate). 58 | 59 | The event argument is an object with `prevValue` and `newValue` properties that (as their name suggests) contain the previous and the new values. 60 | 61 | ## Exposed Methods [#exposed-methods] 62 | 63 | This component does not expose any methods. 64 | 65 | ## Styling [#styling] 66 | 67 | This component does not have any styles. 68 | ```