This is page 19 of 140. Use http://codebase.md/xmlui-org/xmlui/mockApiDef.js?lines=false&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/Slider/Slider.module.scss: -------------------------------------------------------------------------------- ```scss @use "../../components-core/theming/themes" as t; // --- This code snippet is required to collect the theme variables used in this module $themeVars: (); @function createThemeVar($componentVariable) { $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global; @return t.getThemeVar($themeVars, $componentVariable); } $componentName: "Slider"; // Basic variant mixin for consistent theming @mixin variant($variantName) { border-radius: createThemeVar("Input:borderRadius-#{$componentName}-#{$variantName}"); border-color: createThemeVar("Input:borderColor-#{$componentName}-#{$variantName}"); border-width: createThemeVar("Input:borderWidth-#{$componentName}-#{$variantName}"); border-style: createThemeVar("Input:borderStyle-#{$componentName}-#{$variantName}"); box-shadow: createThemeVar("Input:boxShadow-#{$componentName}-#{$variantName}"); &:hover { border-color: createThemeVar("Input:borderColor-#{$componentName}-#{$variantName}--hover"); box-shadow: createThemeVar("Input:boxShadow-#{$componentName}-#{$variantName}--hover"); } &:focus-within { border-color: createThemeVar("Input:borderColor-#{$componentName}-#{$variantName}--focus"); box-shadow: createThemeVar("Input:boxShadow-#{$componentName}-#{$variantName}--focus"); } } @layer components { // Container for the slider .sliderContainer { width: 100%; position: relative; padding: t.$space-2 0; } // Root element .sliderRoot { position: relative; display: flex; width: 100%; touch-action: none; user-select: none; align-items: center; height: 20px; } // Track element .sliderTrack { position: relative; height: 8px; width: 100%; flex-grow: 1; overflow: hidden; border-radius: 9999px; background-color: createThemeVar("Input:backgroundColor-track-#{$componentName}"); @include variant("default"); &.error { @include variant("error"); } &.warning { @include variant("warning"); } &.valid { @include variant("success"); } &.disabled, &[data-disabled] { background-color: createThemeVar("Input:backgroundColor-track-#{$componentName}--disabled"); } &:not(.disabled):not([data-disabled]):hover { cursor: pointer; } } // Range element .sliderRange { position: absolute; height: 100%; background-color: createThemeVar("Input:backgroundColor-range-#{$componentName}"); border-radius: 9999px; &.disabled, &[data-disabled] { background-color: createThemeVar("Input:backgroundColor-range-#{$componentName}--disabled"); } } // Thumb element .sliderThumb { cursor: pointer; display: block; width: 20px; height: 20px; border-radius: 50%; border: createThemeVar("Input:borderWidth-thumb-#{$componentName}") createThemeVar("Input:borderStyle-thumb-#{$componentName}") createThemeVar("Input:borderColor-thumb-#{$componentName}"); background-color: createThemeVar("Input:backgroundColor-thumb-#{$componentName}"); box-shadow: createThemeVar("Input:boxShadow-thumb-#{$componentName}"); position: relative; transition: background-color 0.2s, box-shadow 0.2s; outline: none; &.disabled, &[data-disabled] { pointer-events: none; opacity: 0.5; } &:focus-visible, &[data-focus] { background-color: createThemeVar("Input:backgroundColor-thumb-#{$componentName}--focus"); box-shadow: createThemeVar("Input:boxShadow-thumb-#{$componentName}--focus"); } &:hover, &[data-hover] { background-color: createThemeVar("Input:backgroundColor-thumb-#{$componentName}--hover"); box-shadow: createThemeVar("Input:boxShadow-thumb-#{$componentName}--hover"); } &:active, &[data-active] { background-color: createThemeVar("Input:backgroundColor-thumb-#{$componentName}--active"); box-shadow: createThemeVar("Input:boxShadow-thumb-#{$componentName}--active"); } } /* States */ .disabled { opacity: 0.5; cursor: not-allowed; } .readOnly { opacity: 0.7; cursor: default; } .error { border-color: createThemeVar("Input:borderColor-#{$componentName}-error"); } .warning { border-color: createThemeVar("Input:borderColor-#{$componentName}-warning"); } .valid { border-color: createThemeVar("Input:borderColor-#{$componentName}-success"); } } // --- We export the theme variables to add them to the component renderer :export { themeVars: t.json-stringify($themeVars); } ``` -------------------------------------------------------------------------------- /xmlui/src/components/Splitter/HSplitter.spec.ts: -------------------------------------------------------------------------------- ```typescript import { getBounds } from "../../testing/component-test-helpers"; import { expect, test } from "../../testing/fixtures"; // ============================================================================= // BASIC FUNCTIONALITY TESTS // ============================================================================= test.describe("Basic Functionality", () => { test("renders with basic setup", async ({ initTestBed, page }) => { await initTestBed(` <HSplitter height="200px" width="400px" testId="hsplitter"> <Stack backgroundColor="lightblue" height="100%" testId="primary"/> <Stack backgroundColor="darksalmon" height="100%" testId="secondary"/> </HSplitter> `); await expect(page.getByTestId("hsplitter")).toBeVisible(); await expect(page.getByTestId("primary")).toBeVisible(); await expect(page.getByTestId("secondary")).toBeVisible(); }); test("defaults to horizontal orientation", async ({ initTestBed, page }) => { await initTestBed(` <HSplitter height="200px" width="400px" testId="hsplitter"> <Stack backgroundColor="lightblue" height="100%" testId="primary"/> <Stack backgroundColor="darksalmon" height="100%" testId="secondary"/> </HSplitter> `); const primary = page.getByTestId("primary"); const secondary = page.getByTestId("secondary"); const primaryBounds = await getBounds(primary); const secondaryBounds = await getBounds(secondary); // In horizontal orientation, primary should be to the left of secondary expect(primaryBounds.right).toBeLessThanOrEqual(secondaryBounds.left + 10); // Allow for small overlap due to resizer }); test("ignores orientation property when explicitly set", async ({ initTestBed, page }) => { await initTestBed(` <HSplitter height="200px" width="400px" orientation="vertical" testId="hsplitter"> <Stack backgroundColor="lightblue" height="100%" testId="primary"/> <Stack backgroundColor="darksalmon" height="100%" testId="secondary"/> </HSplitter> `); const primary = page.getByTestId("primary"); const secondary = page.getByTestId("secondary"); const primaryBounds = await getBounds(primary); const secondaryBounds = await getBounds(secondary); // Even with orientation="vertical", HSplitter should still be horizontal // Primary should be to the left of secondary, NOT above it expect(primaryBounds.right).toBeLessThanOrEqual(secondaryBounds.left + 10); }); test("supports all Splitter properties except orientation", async ({ initTestBed, page, createSplitterDriver }) => { await initTestBed(` <HSplitter height="200px" width="400px" initialPrimarySize="30%" minPrimarySize="20%" maxPrimarySize="80%" floating="false" testId="hsplitter" > <Stack backgroundColor="lightblue" height="100%" testId="primary"/> <Stack backgroundColor="darksalmon" height="100%" testId="secondary"/> </HSplitter> `); const hsplitter = page.getByTestId("hsplitter"); const primary = page.getByTestId("primary"); const driver = await createSplitterDriver(hsplitter); // Verify initial size is applied const hsplitterBounds = await getBounds(hsplitter); const primaryBounds = await getBounds(primary); const expectedWidth = hsplitterBounds.width * 0.3; const tolerance = 15; expect(Math.abs(primaryBounds.width - expectedWidth)).toBeLessThan(tolerance); // Verify resizer is visible (floating=false) const resizer = await driver.getResizer(); await expect(resizer).toBeVisible(); }); }); // ============================================================================= // ACCESSIBILITY TESTS // ============================================================================= test.describe("Accessibility", () => { test("resizer has horizontal cursor style", async ({ initTestBed, page, createSplitterDriver }) => { await initTestBed(` <HSplitter height="200px" width="400px" testId="hsplitter"> <Stack backgroundColor="lightblue" height="100%"/> <Stack backgroundColor="darksalmon" height="100%"/> </HSplitter> `); const hsplitter = page.getByTestId("hsplitter"); const driver = await createSplitterDriver(hsplitter); const resizer = await driver.getResizer(); // HSplitter should always use horizontal cursor (ew-resize) await expect(resizer).toHaveCSS("cursor", "ew-resize"); }); }); ``` -------------------------------------------------------------------------------- /xmlui/dev-docs/next/xmlui-wcag-accessibility-report.md: -------------------------------------------------------------------------------- ```markdown # XMLUI WCAG 2.1 AA Accessibility Audit Report **Audit Date:** July 26, 2025 **Tool Used:** Pa11y (WCAG2AA standard) **URL Tested:** http://localhost:5173 (XMLUI Documentation Site) **Total Issues Found:** 24 Errors ## Executive Summary The accessibility audit of the XMLUI documentation site revealed several WCAG 2.1 AA compliance issues that need to be addressed. The primary concerns are: 1. **Duplicate ID Attributes** - Critical HTML validation issues 2. **Missing Button Labels** - Accessibility API naming problems 3. **Color Contrast Issues** - Multiple instances of insufficient contrast ratios 4. **Anchor Link Contrast** - Link visibility concerns ## Detailed Findings ### 1. Critical Issues (Must Fix) #### Duplicate ID Attributes (3 instances) **WCAG Guideline:** 4.1.1 - Parsing **Severity:** Critical - `id=":rbd:"` - Search input element - `id=":rp:"` - Checkbox/switch element - `id="introduction"` - Anchor span element **Impact:** These duplicate IDs break HTML validity and can cause assistive technology confusion. **Recommendation:** Ensure all ID attributes are unique across the page. #### Missing Button Names (2 instances) **WCAG Guideline:** 4.1.2 - Name, Role, Value **Severity:** High Two button elements lack accessible names for screen readers: - Header button elements with only SVG content - No aria-label, title, or text content provided **Recommendation:** Add `aria-label` attributes to describe button functionality. ### 2. Color Contrast Issues (18 instances) #### Code Syntax Highlighting Contrast Problems **WCAG Guideline:** 1.4.3 - Contrast (Minimum) **Required Ratio:** 4.5:1 for normal text, 3:1 for large text Multiple code elements have insufficient contrast: - **Color #66748E:** 4.06:1 ratio (needs 4.5:1) - Recommendation: Change background to #f7faff - **Color #0074A9:** 4.44:1 ratio (needs 4.5:1) - Recommendation: Change background to #ebf0fb - **Color #F07100:** 2.56:1 ratio (severely insufficient) - Recommendation: Change text color to #070300 - **Color #2F86D2:** 3.31:1 ratio (insufficient) - Recommendation: Change text color to #000306 #### Anchor Link Contrast - **Anchor hash links:** 2.8:1 ratio (needs 3:1 minimum) - Recommendation: Change text color to #8196b4 ## Priority Recommendations ### Immediate Actions (High Priority) 1. **Fix Duplicate IDs** - Generate unique IDs for all elements 2. **Add Button Labels** - Implement aria-label for all unlabeled buttons 3. **Fix Severe Contrast Issues** - Address colors with ratios below 3:1 ### Short-term Actions (Medium Priority) 1. **Improve Code Syntax Colors** - Adjust syntax highlighting color scheme 2. **Review Theme Colors** - Audit all theme colors for WCAG compliance 3. **Test with Screen Readers** - Validate fixes with assistive technology ### Long-term Actions (Lower Priority) 1. **Implement Automated Testing** - Add accessibility testing to CI/CD pipeline 2. **Create Accessibility Guidelines** - Document color contrast requirements 3. **Regular Audits** - Schedule periodic accessibility reviews ## Technical Implementation Notes ### Code Syntax Highlighting Fix ```css /* Recommended color adjustments for syntax highlighting */ .syntax-punctuation { color: #66748E; background: #f7faff; } .syntax-string { color: #0074A9; background: #ebf0fb; } .syntax-keyword { color: #070300; } /* was #F07100 */ .syntax-property { color: #000306; } /* was #2F86D2 */ ``` ### Button Accessibility Fix ```html <!-- Before --> <button class="_headerButton_1b3x4_231" data-state="closed"> <svg>...</svg> </button> <!-- After --> <button class="_headerButton_1b3x4_231" data-state="closed" aria-label="Toggle menu"> <svg>...</svg> </button> ``` ## Compliance Status - **WCAG 2.1 A:** ❌ Non-compliant (duplicate IDs, missing labels) - **WCAG 2.1 AA:** ❌ Non-compliant (contrast issues) - **WCAG 2.1 AAA:** ❌ Non-compliant (multiple issues) ## Next Steps 1. Address critical duplicate ID issues immediately 2. Add proper button labeling for screen reader support 3. Implement color contrast fixes in the theme system 4. Test fixes with automated tools and manual testing 5. Document accessibility standards for the project ## Tools and Testing Environment - **Testing Tool:** Pa11y v6.x with WCAG2AA ruleset - **Browser:** Chrome (headless via Pa11y) - **Testing Date:** July 26, 2025 - **Base URL:** http://localhost:5173 --- *This report provides a baseline accessibility assessment. Regular testing and monitoring are recommended to maintain compliance as the application evolves.* ``` -------------------------------------------------------------------------------- /xmlui/src/components/Slot/Slot.md: -------------------------------------------------------------------------------- ```markdown %-DESC-START ## Using Slot You can add `Slot` to a user-defined component as a placeholder. When you refer to the particular component in the markup, the children are transposed to the `Slot`. ```xmlui-pg name="Using Slot" ---app copy display {3-5} <App name="XMLUI Hello World"> <ActionBar> <Button label="Create" onClick="window.alert('Create clicked')" /> <Button label="Edit" onClick="window.alert('Edit clicked')" /> <Button label="Delete" onClick="window.alert('Delete clicked')" /> </ActionBar> </App> ---desc The app flows down three buttons to the `ActionBar` to render. ---comp copy display {5} <Component name="ActionBar"> <Card> <H3>Use these actions</H3> <HStack> <Slot /> </HStack> </Card> </Component> ---desc `ActionBar` renders the passed children by replacing `Slot` with them. ``` ## Default Slot content You can provide default content for the `Slot`. If the user-defined component does not have any children, XMLUI will render the default content. ```xmlui-pg ---app copy display name="Define default Slot content" <App> <ActionBar /> </App> ---comp copy display {6} <Component name="ActionBar"> <Card> <H3>Use these actions</H3> <HStack> <Slot> <Button label="Default" onClick="window.alert('Default clicked')" /> </Slot> </HStack> </Card> </Component> ``` ## Named Slots You can add multiple slots to a user-defined component; you can have a default slot and several *named* slots. Slot names should end with `template`, and you can use the `<property>` markup syntax to declare their values. ```xmlui-pg ---app copy display name="Using named Slots" {4, 7, 9-11} <App> <ActionBar> <property name="headerTemplate"> <H2>Click one of these actions</H2> </property> <property name="footerTemplate"> <Text>Footer content goes here</Text> </property> <Button label="Create" onClick="window.alert('Create clicked')" /> <Button label="Edit" onClick="window.alert('Edit clicked')" /> <Button label="Delete" onClick="window.alert('Delete clicked')" /> </ActionBar> </App> ---desc This app passes a header template and a footer template slot to the `ActionBar` component and also declares buttons to render. ---comp copy display {3-5, 7-9, 11} <Component name="ActionBar"> <Card> <Slot name="headerTemplate"> <H3>Use these actions</H3> </Slot> <HStack> <Slot> <Button label="Default" onClick="window.alert('Default clicked')" /> </Slot> </HStack> <Slot name="footerTemplate" /> </Card> </Component> ---desc XMLUI finds the appropriate slots by their name and transposes their content received from the app. Just like the default slot, named slots can have default content. ``` > [!WARN] XMLUI will display an error message when the `Slot` name does not end with "Template". ## Template properties The user-defined component can provide properties for the actual template. ```xmlui-pg ---app copy display name="Using template properties" /header/ /name="headerTemplate"/ /$processedHeader/ <App> <ActionBar header="Action Bar Example"> <property name="headerTemplate"> <Text variant="title">{$processedHeader}</Text> </property> <Button label="Create" onClick="window.alert('Create clicked')" /> <Button label="Edit" onClick="window.alert('Edit clicked')" /> <Button label="Delete" onClick="window.alert('Delete clicked')" /> </ActionBar> </App> ---desc The app passes a `header` property value to the `ActionBar` component. `Actionbar` utilizes this property, transforms it, and passes it back to the template in the `$processedHeader` context variable so that the app can use it. `$processHeader` is available only within the `headerTemplate` slot. ---comp copy display /transformedHeader/ /processedHeader="{transformedHeader}"/ <Component name="ActionBar"> <Card var.transformedHeader="*** {$props.header.toUpperCase()} ***"> <Slot name="headerTemplate" processedHeader="{transformedHeader}" > <H3>{transformedHeader}</H3> </Slot> <HStack> <Slot> <Button label="Default" onClick="window.alert('Default clicked')" /> </Slot> </HStack> </Card> </Component> ---desc `Actionbar` transforms the `header` property and stores it internally in the `transformedHeader` variable. It utilizes the value in the default header definition and also passes it back to the actual template definition with the `processedHeader` name. XMLUI creates the `$processedHeader` context variable from this name. ``` %-DESC-END ``` -------------------------------------------------------------------------------- /xmlui/src/components/FlowLayout/FlowLayout.md: -------------------------------------------------------------------------------- ```markdown %-DESC-START For details on how to work with \`FlowLayout\` (like sizing children), see [this guide](/layout#flowlayout). ## Using `SpaceFiller` with `FlowLayout` The `SpaceFiller` component can be used as a line break. See the [reference docs](/components/SpaceFiller) for details. %-DESC-END %-PROP-START gap The `gap` property defines the gap between items in the same row and between rows. The `FlowLayout` component creates a new row when an item is about to overflow the current row. ```xmlui-pg copy display name="Example: gap" ---app copy display <App> <FlowLayout gap="$space-12"> <Stack width="25%" height="32px" backgroundColor="red" /> <Stack width="25%" height="32px" backgroundColor="blue" /> <Stack width="25%" height="32px" backgroundColor="green" /> <Stack width="25%" height="32px" backgroundColor="yellow" /> <Stack width="25%" height="32px" backgroundColor="maroon" /> <Stack width="25%" height="32px" backgroundColor="teal" /> <Stack width="25%" height="32px" backgroundColor="seagreen" /> <Stack width="25%" height="32px" backgroundColor="olive" /> </FlowLayout> </App> ---desc In this markup, only four items fit in a single row. The `gap` property sets the same gaps within and between rows. ``` This markup demonstrates different `gap` values: ```xmlui-pg copy display name="Example: different size units" ---app copy display <App> <FlowLayout> <Stack width="25%" height="32px" backgroundColor="red" /> <Stack width="25%" height="32px" backgroundColor="blue" /> <Stack width="25%" height="32px" backgroundColor="green" /> <Stack width="25%" height="32px" backgroundColor="yellow" /> </FlowLayout> <FlowLayout gap="10px"> <Stack width="25%" height="32px" backgroundColor="red" /> <Stack width="25%" height="32px" backgroundColor="blue" /> <Stack width="25%" height="32px" backgroundColor="green" /> <Stack width="25%" height="32px" backgroundColor="yellow" /> </FlowLayout> <FlowLayout gap="1rem"> <Stack width="25%" height="32px" backgroundColor="red" /> <Stack width="25%" height="32px" backgroundColor="blue" /> <Stack width="25%" height="32px" backgroundColor="green" /> <Stack width="25%" height="32px" backgroundColor="yellow" /> </FlowLayout> <FlowLayout gap="4ch"> <Stack width="25%" height="32px" backgroundColor="red" /> <Stack width="25%" height="32px" backgroundColor="blue" /> <Stack width="25%" height="32px" backgroundColor="green" /> <Stack width="25%" height="32px" backgroundColor="yellow" /> </FlowLayout> </App> ---desc All items within a `FlowLayout` instance fit in a single row, so `gap` affects only the space between items. The space between rows comes from the outermost `Stack`. ``` %-PROP-END %-PROP-START columnGap The `columnGap` property specifies the space between items in a single row; it overrides the `gap` value. ```xmlui-pg copy display name="Example: columnGap" ---app copy display <App> <FlowLayout columnGap="$space-8"> <Stack width="25%" height="32px" backgroundColor="red" /> <Stack width="25%" height="32px" backgroundColor="blue" /> <Stack width="25%" height="32px" backgroundColor="green" /> <Stack width="25%" height="32px" backgroundColor="yellow" /> <Stack width="25%" height="32px" backgroundColor="maroon" /> <Stack width="25%" height="32px" backgroundColor="teal" /> <Stack width="25%" height="32px" backgroundColor="seagreen" /> <Stack width="25%" height="32px" backgroundColor="olive" /> </FlowLayout> </App> ---desc You can observe no gap between the rows of the `FlowLayout`, as `columnGap` keeps the space between rows intact: ``` %-PROP-END %-PROP-START rowGap The `rowGap` property specifies the space between the `FlowLayout` rows; it overrides the `gap` value. ```xmlui-pg copy display name="Example: rowGap" ---app copy display <App> <FlowLayout rowGap="2px"> <Stack width="25%" height="32px" backgroundColor="red" /> <Stack width="25%" height="32px" backgroundColor="blue" /> <Stack width="25%" height="32px" backgroundColor="green" /> <Stack width="25%" height="32px" backgroundColor="yellow" /> <Stack width="25%" height="32px" backgroundColor="maroon" /> <Stack width="25%" height="32px" backgroundColor="teal" /> <Stack width="25%" height="32px" backgroundColor="seagreen" /> <Stack width="25%" height="32px" backgroundColor="olive" /> </FlowLayout> </App> ---desc You can observe no gap between the items in a single row of the `FlowLayout`, as `rowGap` keeps the gap within a row intact: ``` %-PROP-END ``` -------------------------------------------------------------------------------- /xmlui/tests-e2e/context-vars-regression.spec.ts: -------------------------------------------------------------------------------- ```typescript import { expect, test } from "../src/testing/fixtures"; test("context vars dont get resolved multiple times", async ({ page, initTestBed }) => { await initTestBed( ` <Fragment> <DataSource url="/data1" id="stringsLoader"/> <Items items="{stringsLoader.value}"> <Text testId="text-{$itemIndex}">{$item}</Text> </Items> </Fragment> `, { apiInterceptor: { operations: { "load-api-data1": { url: "/data1", method: "get", handler: `()=>{ return ['text0', 'text1', 'text{ 1 + 2 }']; }`, }, }, }, }, ); await expect(page.getByTestId("text-0")).toHaveText("text0"); await expect(page.getByTestId("text-1")).toHaveText("text1"); await expect(page.getByTestId("text-2")).toHaveText("text{ 1 + 2 }"); }); test("can use context vars in simple var declarations", async ({ page, initTestBed }) => { await initTestBed( ` <Fragment> <DataSource url="/data1" id="stringsLoader"/> <Items items="{stringsLoader.value}"> <script> var something = $item + '_modified'; </script> <Text testId="text-{$itemIndex}">{something}</Text> </Items> </Fragment> `, { apiInterceptor: { operations: { "load-api-data1": { url: "/data1", method: "get", handler: `()=>{ return ['text0', 'text1', 'text{ 1 + 2 }']; }`, }, }, }, }, ); await expect(page.getByTestId("text-0")).toHaveText("text0_modified"); await expect(page.getByTestId("text-1")).toHaveText("text1_modified"); await expect(page.getByTestId("text-2")).toHaveText("text{ 1 + 2 }_modified"); }); test("can use context vars in combination with vars and calculated props", async ({ page, initTestBed, }) => { await initTestBed( ` <Fragment> <script> var something = 'modified'; </script> <DataSource url="/data1" id="stringsLoader"/> <Items items="{stringsLoader.value}"> <Text testId="text-{$itemIndex}">{$item}_{something}</Text> </Items> </Fragment> `, { apiInterceptor: { operations: { "load-api-data1": { url: "/data1", method: "get", handler: `()=>{ return ['text0', 'text1', 'text{ 1 + 2 }']; }`, }, }, }, }, ); await expect(page.getByTestId("text-0")).toHaveText("text0_modified"); await expect(page.getByTestId("text-1")).toHaveText("text1_modified"); await expect(page.getByTestId("text-2")).toHaveText("text{ 1 + 2 }_modified"); }); test("context vars in formItems", async ({ page, initTestBed }) => { await initTestBed( ` <Fragment> <Form data="{{ customText: 'hello {1 + 2}' }}"> <FormItem bindTo="customText"> <TextBox initialValue="{$value}" testId="textBox"/> </FormItem> </Form> </Fragment> `, { apiInterceptor: { operations: { "load-api-data1": { url: "/data1", method: "get", handler: `()=>{ return ['text0', 'text1', 'text{ 1 + 2 }']; }`, }, }, }, }, ); await expect(page.getByTestId("textBox").getByRole("textbox")).toHaveValue("hello {1 + 2}"); }); test("$data context var in form event handlers", async ({ page, initTestBed }) => { await initTestBed(` <Fragment var.dataFromSubmit="" var.dataFromCancel="" var.dataFromReset=""> <Form testId="form"> <FormItem bindTo="customText" testId="textBox"/> <event name="submit" value="dataFromSubmit = $data.customText"/> <event name="cancel" value="dataFromCancel = $data.customText"/> <event name="reset" value="dataFromReset = $data.customText"/> </Form> <Text testId="dataFromSubmit">{dataFromSubmit}</Text> <Text testId="dataFromCancel">{dataFromCancel}</Text> <Text testId="dataFromReset">{dataFromReset}</Text> </Fragment> `); await page.getByTestId("textBox").getByRole("textbox").fill("hello {1 + 2}"); await page.getByTestId("form").locator("button[type=button]").click(); await expect(page.getByTestId("dataFromCancel")).toHaveText("hello {1 + 2}"); await page.getByTestId("form").locator("button[type=submit]").click(); await expect(page.getByTestId("dataFromSubmit")).toHaveText("hello {1 + 2}"); //reset is called after submit await expect(page.getByTestId("dataFromReset")).toHaveText("hello {1 + 2}"); }); ``` -------------------------------------------------------------------------------- /xmlui/src/components/Theme/Theme.md: -------------------------------------------------------------------------------- ```markdown %-DESC-START **Key features:** - **No CSS required**: Change component appearance using theme variables instead of custom stylesheets - **Brand consistency**: Maintain design system compliance while allowing contextual variations - **Scoped styling**: Apply theme changes only to nested components without affecting the global design - **Variable overrides**: Modify colors, spacing, typography, and other design variables declaratively - **Nested contexts**: Stack multiple `Theme` components for granular control with automatic specificity rules See [this guide](/themes-intro) and [these references](/styles-and-themes/layout-props) for details. ## Using `Theme` In contrast to other components, `Theme` accepts theme variables as properties. You can define specific styles for components nested in `Theme` using these theme variables. The following example specifies a dark tone for the current theme and sets several theme variables to style the `ProgressBar` component: ```xmlui-pg copy {3-8} display name="Example: using Theme" <App> <Theme tone="dark" backgroundColor-ProgressBar="cyan" color-indicator-ProgressBar="purple" thickness-ProgressBar="12px" borderRadius-indicator-ProgressBar="12px" borderRadius-Progressbar="4px" > <VStack backgroundColor="$backgroundColor-primary"> <ProgressBar value="0"/> <ProgressBar value="0.2"/> <ProgressBar value="0.6"/> <ProgressBar value="1.0"/> </VStack> </Theme> </App> ``` %-DESC-END %-PROP-START themeId ```xmlui-pg copy {2, 9, 16} display name="Example: themeId" <App> <Theme themeId="xmlui"> <VStack backgroundColor="$backgroundColor-primary"> <H3>Use 'xmlui' theme:</H3> <ProgressBar value="0"/> <ProgressBar value="0.6"/> </VStack> </Theme> <Theme themeId="xmlui-green"> <VStack backgroundColor="$backgroundColor-primary"> <H3>Use 'xmlui-green' theme:</H3> <ProgressBar value="0"/> <ProgressBar value="0.6"/> </VStack> </Theme> <Theme themeId="xmlui-red"> <VStack backgroundColor="$backgroundColor-primary"> <H3>Use the 'xmlui-red' theme:</H3> <ProgressBar value="0"/> <ProgressBar value="0.6"/> </VStack> </Theme> </App> ``` %-PROP-END %-PROP-START tone ```xmlui-pg copy {2,9} display name="Example: tone" <App> <Theme tone="light"> <VStack backgroundColor="$backgroundColor-primary" > <H3>Use the light tone of the base theme:</H3> <ProgressBar value="0"/> <ProgressBar value="0.6"/> </VStack> </Theme> <Theme tone="dark"> <VStack backgroundColor="$backgroundColor-primary"> <H3>Use the dark tone of the base theme:</H3> <ProgressBar value="0"/> <ProgressBar value="0.6"/> </VStack> </Theme> </App> ``` %-PROP-END %-PROP-START root If so, it will set a number of important settings for the app: - what favicon to use - sets up font links - specifies the base css - sets up the root for the toast notification system Otherwise, the `Theme` component will just provide the theme context to its children. %-PROP-END %-PROP-START applyIf The `applyIf` property controls whether the theme is conditionally applied to its children. When set to `false`, the children are rendered without the theme wrapper, effectively bypassing the theme styling. ```xmlui-pg copy {2,9,16} display name="Example: applyIf" <App var.apply="{false}"> <Theme backgroundColor-Button="rgb(255, 100, 100)" applyIf="true"> <VStack> <H3>Theme Applied (applyIf="true"):</H3> <Button>Themed Button</Button> </VStack> </Theme> <Theme backgroundColor-Button="rgb(255, 100, 100)" applyIf="false"> <VStack> <H3>Theme Not Applied (applyIf="false"):</H3> <Button>Default Button</Button> </VStack> </Theme> <Theme backgroundColor-Button="rgb(100, 192, 100)" applyIf="{apply}"> <VStack> <H3>Conditional Theme (dynamic):</H3> <Button onClick="apply = !apply"> {apply ? 'Themed' : 'Default'} - Click to Toggle </Button> </VStack> </Theme> </App> ``` This property is particularly useful for: - **Conditional styling**: Apply themes based on user preferences, feature flags, or application state - **Theme debugging**: Temporarily disable themes during development - **Progressive enhancement**: Provide fallback styling when themes fail to load - **Dynamic theming**: Switch themes on and off based on user interactions or data conditions %-PROP-END %-STYLE-START The `Theme` component is a styling wrapper that influences the nested components' visual appearance. It cannot be styled. %-STYLE-END ``` -------------------------------------------------------------------------------- /packages/xmlui-os-frames/src/IPhoneFrame.module.scss: -------------------------------------------------------------------------------- ```scss @use "xmlui/themes.scss" as t; // --- This code snippet is required to collect the theme variables used in this module $themeVars: (); @function createThemeVar($componentVariable) { $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global; @return t.getThemeVar($themeVars, $componentVariable); } .device { position: relative; transform: scale(1); z-index: 1 } .device .deviceFrame { z-index: 1 } .device .deviceScreen { background-color: t.$backgroundColor; background-position: center center; background-size: cover; object-fit: cover; position: relative; padding: t.$space-4; gap: t.$space-4; display: flex; flex-direction: column; } .deviceIphone14Pro { height: 868px; width: 428px; flex-shrink: 0; } .deviceIphone14Pro .deviceFrame { background: #010101; border: 1px solid #1b1721; border-radius: 68px; box-shadow: inset 0 0 4px 2px #c0b7cd, inset 0 0 0 6px #342c3f; height: 100%; padding: 19px; width: 100%; } .deviceIphone14Pro .deviceScreen { border-radius: 49px; height: 100%; width: 100%; padding-top: calc(44px + #{t.$space-4}); } .deviceIphone14Pro .deviceStripe::after, .deviceIphone14Pro .deviceStripe::before { border: solid rgba(1, 1, 1, .25); border-width: 0 7px; content: ""; height: 7px; left: 0; position: absolute; width: 100%; z-index: 9; } .deviceIphone14Pro .deviceStripe::after { top: 85px; } .deviceIphone14Pro .deviceStripe::before { bottom: 85px; } .deviceIphone14Pro .deviceHeader { background: #010101; border-radius: 20px; height: 35px; left: 50%; margin-left: -60px; position: absolute; top: 29px; width: 120px; } .deviceIphone14Pro .deviceSensors::after, .deviceIphone14Pro .deviceSensors::before { content: ""; position: absolute; } .deviceIphone14Pro .deviceSensors::after { background: #010101; border-radius: 17px; height: 33px; left: 50%; margin-left: -60px; top: 30px; width: 74px; } .deviceIphone14Pro .deviceSensors::before { background: radial-gradient(farthest-corner at 20% 20%, #6074bf 0, transparent 40%), radial-gradient(farthest-corner at 80% 80%, #513785 0, #24555e 20%, transparent 50%); border-radius: 50%; box-shadow: 0 0 1px 1px rgba(255, 255, 255, .05); height: 9px; left: 50%; margin-left: 36px; top: 42px; width: 9px; } .deviceIphone14Pro .deviceBtns { background: #1b1721; border-radius: 2px; height: 32px; left: -2px; position: absolute; top: 115px; width: 3px; } .deviceIphone14Pro .deviceBtns::after, .deviceIphone14Pro .deviceBtns::before { background: #1b1721; border-radius: 2px; content: ""; height: 62px; left: 0; position: absolute; width: 3px; } .deviceIphone14Pro .deviceBtns::after { top: 60px; } .deviceIphone14Pro .deviceBtns::before { top: 140px; } .deviceIphone14Pro .devicePower { background: #1b1721; border-radius: 2px; height: 100px; position: absolute; right: -2px; top: 200px; width: 3px; } .deviceIphone14Pro .deviceHome::after, .deviceIphone14Pro .deviceHome::before { border: solid rgba(1, 1, 1, .25); border-width: 6px 0; content: ""; height: 6px; position: absolute; width: 6px; z-index: 9; } .deviceIphone14Pro .deviceHome::after { right: 86px; top: 0; } .deviceIphone14Pro .deviceHome::before { bottom: 0; left: 86px; } .deviceIphone14Pro.deviceSilver .deviceFrame { border-color: #c8cacb; box-shadow: inset 0 0 4px 2px #fff, inset 0 0 0 6px #e2e3e4; } .deviceIphone14Pro.deviceSilver .deviceBtns { background: #c8cacb; } .deviceIphone14Pro.deviceSilver .deviceBtns::after, .deviceIphone14Pro.deviceSilver .deviceBtns::before { background: #c8cacb; } .deviceIphone14Pro.deviceSilver .devicePower { background: #c8cacb; } .deviceIphone14Pro.deviceBlack .deviceFrame { border-color: #5c5956; box-shadow: inset 0 0 4px 2px #fff, inset 0 0 0 6px #76726f; } .deviceIphone14Pro.deviceBlack .deviceBtns { background: #5c5956; } .deviceIphone14Pro.deviceBlack .deviceBtns::after, .deviceIphone14Pro.deviceBlack .deviceBtns::before { background: #5c5956; } .deviceIphone14Pro.deviceBlack .devicePower { background: #5c5956; } .deviceIphone14Pro.deviceGold .deviceFrame { border-color: #e7d19e; box-shadow: inset 0 0 4px 2px #fff, inset 0 0 0 6px #d2ab4c; } .deviceIphone14Pro.deviceGold .deviceBtns { background: #e7d19e; } .deviceIphone14Pro.deviceGold .deviceBtns::after, .deviceIphone14Pro.deviceGold .deviceBtns::before { background: #e7d19e; } .deviceIphone14Pro.deviceGold .devicePower { background: #e7d19e; } // --- We export the theme variables to add them to the component renderer :export{ themeVars: t.json-stringify($themeVars) } ``` -------------------------------------------------------------------------------- /xmlui/src/components/ValidationSummary/ValidationSummary.tsx: -------------------------------------------------------------------------------- ```typescript import { useMemo } from "react"; import { useAutoAnimate } from "@formkit/auto-animate/react"; import classnames from "classnames"; import styles from "./ValidationSummary.module.scss"; import { EMPTY_ARRAY, EMPTY_OBJECT } from "../../components-core/constants"; import type { SingleValidationResult, ValidationResult, ValidationSeverity, } from "../Form/FormContext"; import { Stack } from "../Stack/StackNative"; import { Icon } from "../Icon/IconNative"; import { Text } from "../Text/TextNative"; import { SpaceFiller } from "../SpaceFiller/SpaceFillerNative"; import { Button } from "../Button/ButtonNative"; export type ValidationSummaryProps = { fieldValidationResults?: Record<string, ValidationResult>; generalValidationResults: Array<SingleValidationResult>; }; export const defaultProps: Pick<ValidationSummaryProps, "fieldValidationResults" | "generalValidationResults"> = { fieldValidationResults: EMPTY_OBJECT, generalValidationResults: EMPTY_ARRAY, }; type ValidationDisplayProps = { heading?: string; issues: Array<ValidationIssue>; severity?: ValidationSeverity | "info"; onClose?: (...args: any[]) => any; }; type ValidationIssue = { field?: string; message: string }; export function ValidationSummary({ fieldValidationResults = defaultProps.fieldValidationResults, generalValidationResults = defaultProps.generalValidationResults, }: ValidationSummaryProps) { const [animateContainerRef] = useAutoAnimate({ duration: 100 }); const groupedInvalidResults = useMemo(() => { const ret: Record<ValidationSeverity | string, Array<ValidationIssue>> = {}; Object.entries(fieldValidationResults).forEach(([field, validationResult]) => { validationResult.validations.forEach((singleValidationResult) => { if (!singleValidationResult.isValid) { ret[singleValidationResult.severity] = ret[singleValidationResult.severity] || []; ret[singleValidationResult.severity].push({ field, message: singleValidationResult.invalidMessage || "", }); } }); }); generalValidationResults.forEach((singleValidationResult) => { ret[singleValidationResult.severity] = ret[singleValidationResult.severity] || []; ret[singleValidationResult.severity].push({ message: singleValidationResult.invalidMessage || "", }); }); return ret; }, [fieldValidationResults, generalValidationResults]); return ( <div ref={animateContainerRef} className={styles.summaryContainer} data-validation-summary > <ValidationDisplay issues={groupedInvalidResults.warning} severity={"warning"} heading={"Validation warnings"} /> <ValidationDisplay issues={groupedInvalidResults.error} severity={"error"} heading={"Validation errors"} /> </div> ); } const ValidationDisplay = ({ heading, issues = EMPTY_ARRAY, severity = "error", onClose, }: ValidationDisplayProps) => { const [animateContainerRef] = useAutoAnimate({ duration: 100 }); if (issues.length === 0) { return null; } return ( <div className={classnames(styles.validationContainer, { [styles.valid]: severity === "valid", [styles.info]: severity === "info", [styles.warning]: severity === "warning", [styles.error]: severity === "error", })} style={{ paddingTop: !onClose ? "0.5rem" : undefined }} data-validation-display-severity={severity} > <Stack orientation="horizontal" verticalAlignment="center" style={{ gap: "0.5rem" }}> <Icon className={styles.heading} name={severity} size="md" /> <div className={styles.heading}> <Text>{heading}</Text> </div> {!!onClose && ( <> <SpaceFiller /> <Button onClick={onClose} variant={"ghost"} themeColor={"secondary"} icon={<Icon name={"close"} size={"sm"} />} orientation={"vertical"} /> </> )} </Stack> <ul ref={animateContainerRef}> {issues.map((issue, i) => ( <ValidationEntry key={i} issue={issue} /> ))} </ul> </div> ); }; // --- ValidationEntry const ValidationEntry = ({ issue }: { issue: ValidationIssue }) => { const { field, message } = issue; return ( <li> <span style={{ display: "inline-flex", gap: field ? "0.25rem" : undefined }}> {field && <Text variant="small" fontWeight="bold">{`${field}:`}</Text>} <Text variant="small" preserveLinebreaks={true}> {message} </Text> </span> </li> ); }; ``` -------------------------------------------------------------------------------- /xmlui/src/components/Button/ButtonNative.tsx: -------------------------------------------------------------------------------- ```typescript import React, { type CSSProperties, useRef, useEffect } from "react"; import classnames from "classnames"; import styles from "./Button.module.scss"; import { isSizeType, type SizeType, type AlignmentOptions, type ButtonAria, type ButtonThemeColor, type ButtonType, type ButtonVariant, type IconPosition, type OrientationOptions, } from "../abstractions"; import { composeRefs } from "@radix-ui/react-compose-refs"; import { VisuallyHidden } from "../VisuallyHidden"; type Props = { id?: string; type?: ButtonType; variant?: ButtonVariant; themeColor?: ButtonThemeColor; size?: SizeType; disabled?: boolean; children?: React.ReactNode | React.ReactNode[]; icon?: React.ReactNode; iconPosition?: IconPosition; contentPosition?: AlignmentOptions; orientation?: OrientationOptions; formId?: string; style?: CSSProperties; gap?: string | number; autoFocus?: boolean; contextualLabel?: string; } & Pick< React.HTMLAttributes<HTMLButtonElement>, | "onClick" | "onFocus" | "onBlur" | "onMouseEnter" | "onMouseLeave" | ButtonAria | "tabIndex" | "className" | "role" >; export const defaultProps: Pick< Props, | "type" | "iconPosition" | "contentPosition" | "orientation" | "variant" | "themeColor" | "size" | "autoFocus" > = { type: "button", iconPosition: "start", contentPosition: "center", orientation: "horizontal", variant: "solid", themeColor: "primary", size: "sm", autoFocus: false, }; export const Button = React.forwardRef(function Button( { id, type = defaultProps.type, icon, iconPosition = defaultProps.iconPosition, contentPosition = defaultProps.contentPosition, orientation = defaultProps.orientation, variant = defaultProps.variant, themeColor = defaultProps.themeColor, size = defaultProps.size, disabled, children, formId, onClick, onFocus, onBlur, style, gap, className, autoFocus = defaultProps.autoFocus, contextualLabel, ...rest }: Props, ref: React.ForwardedRef<HTMLButtonElement>, ) { const innerRef = useRef<HTMLButtonElement>(null); const composedRef = ref ? composeRefs(ref, innerRef) : innerRef; useEffect(() => { if (autoFocus) { setTimeout(() => { innerRef.current?.focus(); }, 0); } }, [autoFocus]); const iconToLeft = iconPosition === "start"; if (!isSizeType(size)) { size = defaultProps.size; } return ( <button {...rest} id={id} type={type} ref={composedRef} className={classnames( styles.button, { [styles.buttonHorizontal]: orientation === "horizontal", [styles.buttonVertical]: orientation === "vertical", [styles.xs]: size === "xs", [styles.sm]: size === "sm", [styles.md]: size === "md", [styles.lg]: size === "lg", [styles.solidPrimary]: variant === "solid" && themeColor === "primary", [styles.solidSecondary]: variant === "solid" && themeColor === "secondary", [styles.solidAttention]: variant === "solid" && themeColor === "attention", [styles.outlinedPrimary]: variant === "outlined" && themeColor === "primary", [styles.outlinedSecondary]: variant === "outlined" && themeColor === "secondary", [styles.outlinedAttention]: variant === "outlined" && themeColor === "attention", [styles.ghostPrimary]: variant === "ghost" && themeColor === "primary", [styles.ghostSecondary]: variant === "ghost" && themeColor === "secondary", [styles.ghostAttention]: variant === "ghost" && themeColor === "attention", [styles.alignStart]: contentPosition === "start", [styles.alignEnd]: contentPosition === "end", }, className, )} autoFocus={autoFocus} disabled={disabled} form={formId} style={style} onClick={onClick} onFocus={onFocus} onBlur={onBlur} > {icon && iconToLeft && <>{icon}</>} {children} {icon && !children && <IconLabel icon={icon} accessibleName={contextualLabel} />} {icon && !iconToLeft && <>{icon}</>} </button> ); }); type IconLabelProps = { icon: React.ReactNode; accessibleName?: string; }; const IconLabel = ({ icon, accessibleName = "" }: IconLabelProps) => { // NOTE: the icon object provided is a React object with accessible props attribute. // Typing might be off, because TS thinks props is not accessible. const iconProps: Record<string, any> | undefined = (icon as any).props; return ( <VisuallyHidden> <span>{accessibleName || iconProps?.name || iconProps?.alt}</span> </VisuallyHidden> ); }; ``` -------------------------------------------------------------------------------- /xmlui/src/components/NavLink/NavLink.md: -------------------------------------------------------------------------------- ```markdown %-DESC-START **Key features:** - **Custom actions**: Execute JavaScript code instead of navigation when using onClick handlers - **Visual customization**: Support for icons, labels, and completely custom nested content - **Accessibility support**: Proper focus management and keyboard navigation ## Using NavLink ### `NavLink` Appearance You can use the `label` and `icon` properties of a `NavLink` to set its text and icon to display. If you want a custom appearance, you can nest define custom visuals for the `NavLink` by nesting: ```xmlui-pg copy {6-14} display name="Example: NavLink appearance" height="250px" <App layout="horizontal"> <AppHeader> <H1>MyApp</H1> </AppHeader> <NavPanel> <NavLink to="/"> <Stack width="16px" height="16px" backgroundColor="purple" /> Home </NavLink> <NavLink to="/about"> <Stack width="16px" height="16px" backgroundColor="green" /> About </NavLink> </NavPanel> <Pages> <Page url="/">Home</Page> <Page url="/about">About</Page> </Pages> </App> ``` ### Actions By default, activating (clicking) a link navigates to the target URL. However, you can create a link that executes an explicit action responding to the `click` event instead of the default navigation: ```xmlui-pg copy {7} display name="Example: custom NavLink action" height="250px" <App layout="horizontal"> <AppHeader> <H1>MyApp</H1> </AppHeader> <NavPanel> <NavLink to="/" label="Home" /> <NavLink label="Danger!" onClick="toast('Be careful with this action!')" /> </NavPanel> <Pages> <Page url="/">Home</Page> </Pages> </App> ``` %-DESC-END %-PROP-START label ```xmlui-pg copy display name="Example: label" height="250px" <App layout="horizontal"> <NavPanel> <NavLink to="/" label="Home" /> </NavPanel> <Pages> <Page url="/">Home</Page> </Pages> </App> ``` %-PROP-END %-PROP-START vertical Usually, you do not need to use this property. However, if you create a custom navigation menu component that runs vertically, you need to manually set this property for the active state to be displayed properly. The default value for this property is `false`. ```xmlui-pg copy display name="Example: vertical" height="250px" <App layout="horizontal"> <NavPanel> <NavLink to="/" label="Home" vertical="true" /> </NavPanel> <Pages> <Page url="/">Home</Page> </Pages> </App> ``` %-PROP-END %-PROP-START icon ```xmlui-pg copy {6-7} display name="Example: icon" height="250px" <App layout="horizontal"> <AppHeader> <H1>MyApp</H1> </AppHeader> <NavPanel> <NavLink label="Home" to="/" icon="home" /> <NavLink label="Drives" to="/drives" icon="drive" /> </NavPanel> <Pages> <Page url="/">Home</Page> <Page url="/drives">Drives Page</Page> </Pages> </App> ``` %-PROP-END %-PROP-START displayActive ```xmlui-pg copy display name="Example: displayActive" height="250px" <App layout="horizontal"> <NavPanel> <NavLink to="/" label="Home" displayActive="false" /> </NavPanel> <Pages> <Page url="/">Home</Page> </Pages> </App> ``` %-PROP-END %-PROP-START enabled In the following app, the "Hotels" link is disabled: ```xmlui-pg copy {8} display name="Example: enabled" height="250px" <App layout="horizontal"> <AppHeader> <H1>MyTravel App</H1> </AppHeader> <NavPanel> <NavLink label="Home" to="/" /> <NavLink label="Flights" to="/flights" /> <NavLink label="Hotels" to="/hotels" enabled="false" /> </NavPanel> <Pages> <Page url="/">Home</Page> <Page url="/flights">Flights Page</Page> <Page url="/hotels">Hotels Page</Page> </Pages> </App> ``` %-PROP-END %-PROP-START target The following example opens the "About XMLUI" link in a new tab: ```xmlui-pg copy {7} display name="Example: target" height="250px" <App layout="horizontal"> <AppHeader> <H1>MyApp</H1> </AppHeader> <NavPanel> <NavLink label="Home" to="/" /> <NavLink label="About XMLUI" to="https://docs.xmlui.org/" target="_blank" /> </NavPanel> <Pages> <Page url="/">Home</Page> <Page url="/drives">Drives Page</Page> </Pages> </App> ``` %-PROP-END %-EVENT-START click The following example shows a message and navigates to the "/status" link after closing the message window: ```xmlui-pg copy {7} display name="Example: click" height="250px" <App layout="horizontal"> <AppHeader> <H1>MyApp</H1> </AppHeader> <NavPanel> <NavLink to="/" label="Home" /> <NavLink label="Check my status" onClick=" toast('You will be redirected'); Actions.navigate('/status'); " /> </NavPanel> <Pages> <Page url="/">Home</Page> <Page url="/status">My Status</Page> </Pages> </App> ``` %-EVENT-END ``` -------------------------------------------------------------------------------- /xmlui/src/components/NumberBox/NumberBox.module.scss: -------------------------------------------------------------------------------- ```scss @use "../../components-core/theming/themes" as t; // --- This code snippet is required to collect the theme variables used in this module $themeVars: ( ); @function createThemeVar($componentVariable) { $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global; @return t.getThemeVar($themeVars, $componentVariable); } $componentName: "NumberBox"; $themeVars: t.composePaddingVars($themeVars, $componentName); // --- CSS properties of a particular NumberBox variant @mixin variant($variantName) { border-radius: createThemeVar("Input:borderRadius-#{$componentName}-#{$variantName}"); border-color: createThemeVar("Input:borderColor-#{$componentName}-#{$variantName}"); border-width: createThemeVar("Input:borderWidth-#{$componentName}-#{$variantName}"); border-style: createThemeVar("Input:borderStyle-#{$componentName}-#{$variantName}"); font-size: createThemeVar("Input:fontSize-#{$componentName}-#{$variantName}"); background-color: createThemeVar("Input:backgroundColor-#{$componentName}-#{$variantName}"); box-shadow: createThemeVar("Input:boxShadow-#{$componentName}-#{$variantName}"); color: createThemeVar("Input:textColor-#{$componentName}-#{$variantName}"); &:hover { border-color: createThemeVar("Input:borderColor-#{$componentName}-#{$variantName}--hover"); background-color: createThemeVar("Input:backgroundColor-#{$componentName}-#{$variantName}--hover" ); box-shadow: createThemeVar("Input:boxShadow-#{$componentName}-#{$variantName}--hover"); color: createThemeVar("Input:textColor-#{$componentName}-#{$variantName}--hover"); } &:focus-within { border-color: createThemeVar("Input:borderColor-#{$componentName}-#{$variantName}--focus"); background-color: createThemeVar("Input:backgroundColor-#{$componentName}-#{$variantName}--focus" ); box-shadow: createThemeVar("Input:boxShadow-#{$componentName}-#{$variantName}--focus"); color: createThemeVar("Input:textColor-#{$componentName}-#{$variantName}--focus"); } &:has(.input:focus-visible) { outline-width: createThemeVar("Input:outlineWidth-#{$componentName}-#{$variantName}--focus"); outline-color: createThemeVar("Input:outlineColor-#{$componentName}-#{$variantName}--focus"); outline-style: createThemeVar("Input:outlineStyle-#{$componentName}-#{$variantName}--focus"); outline-offset: createThemeVar("Input:outlineOffset-#{$componentName}-#{$variantName}--focus"); } .input { &::placeholder { color: createThemeVar("Input:textColor-placeholder-#{$componentName}-#{$variantName}"); font-size: createThemeVar("Input:fontSize-placeholder-#{$componentName}-#{$variantName}"); } } .adornment { color: createThemeVar("Input:color-adornment-#{$componentName}-#{$variantName}"); * { color: inherit; } } } @layer components { .inputRoot { display: flex; align-items: center; gap: createThemeVar("Input:gap-adornment-#{$componentName}"); width: 100%; border-style: solid; border-width: 1px; transition: background-color ease-in 0.1s; overflow: hidden; @include t.paddingVars($themeVars, $componentName); @include variant("default"); &.error { @include variant("error"); } &.warning { @include variant("warning"); } &.valid { @include variant("success"); } &:has(.input:is(:disabled)) { cursor: not-allowed; background-color: createThemeVar("Input:backgroundColor-NumberBox--disabled"); color: createThemeVar("Input:textColor-NumberBox--disabled"); border-color: createThemeVar("Input:borderColor-NumberBox--disabled"); } &.rtl { flex-direction: row-reverse; } .input { font-size: inherit; color: inherit; border: 0; outline: none; background-color: transparent; padding: 0; width: 100%; cursor: inherit; /* Remove default spinners */ /* Chrome, Safari, Edge, Opera */ &::-webkit-outer-spin-button, &::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } /* Firefox */ -webkit-appearance: textfield; -moz-appearance: textfield; appearance: textfield; } .spinnerBox { display: flex; flex-direction: column; height: 0; align-items: center; justify-content: center; } .spinnerButton { padding: 0 !important; width: t.$space-6; border: none; // &:hover { // background-color: transparent !important; // } } } .readOnly { cursor: text; } } // --- We export the theme variables to add them to the component renderer :export { themeVars: t.json-stringify($themeVars); } ``` -------------------------------------------------------------------------------- /xmlui/src/components/TextBox/TextBox.md: -------------------------------------------------------------------------------- ```markdown %-DESC-START **Key features:** - **Visual enhancements**: Add icons and text at start/end positions for context and branding - **Validation states**: Built-in visual indicators for valid, warning, and error states - **Input control**: Support for initial values, programmatic focus, and value setting Often used in forms, see [this guide](/forms) for details. %-DESC-END %-PROP-START placeholder ```xmlui-pg copy display name="Example: placeholder" <App> <TextBox placeholder="This is a placeholder" /> </App> ``` %-PROP-END %-PROP-START initialValue ```xmlui-pg copy display name="Example: initialValue" <App> <TextBox initialValue="Example text" /> </App> ``` %-PROP-END %-PROP-START maxLength Try to enter a longer value into the input field below. ```xmlui-pg copy display name="Example: maxLength" <App> <TextBox maxLength="16" /> </App> ``` %-PROP-END %-PROP-START readOnly ```xmlui-pg copy display name="Example: readOnly" <App> <TextBox initialValue="Example text" readOnly="true" /> </App> ``` %-PROP-END %-PROP-START enabled ```xmlui-pg copy display name="Example: enabled" <App> <TextBox enabled="false" /> </App> ``` %-PROP-END %-PROP-START validationStatus ```xmlui-pg copy display name="Example: validationStatus" <App> <TextBox /> <TextBox validationStatus="valid" /> <TextBox validationStatus="warning" /> <TextBox validationStatus="error" /> </App> ``` %-PROP-END %-PROP-START startText ```xmlui-pg copy display name="Example: startText" <App> <TextBox startText="www." /> </App> ``` It is possible to set the other adornments as well: [`endIcon`](#endicon), [`startIcon`](#starticon) and [`endText`](#endtext). ```xmlui-pg copy display name="Example: all adornments" <App> <TextBox startIcon="hyperlink" startText="www." endIcon="email" endText=".com" /> </App> ``` %-PROP-END %-PROP-START startIcon ```xmlui-pg copy display name="Example: startIcon" <App> <TextBox startIcon="hyperlink" /> </App> ``` It is possible to set the other adornments as well: [`endText`](#endtext), [`startIcon`](#starticon) and [`startText`](#starttext). ```xmlui-pg copy display name="Example: all adornments" <App> <TextBox startIcon="hyperlink" startText="www." endIcon="email" endText=".com" /> </App> ``` %-PROP-END %-PROP-START endText ```xmlui-pg copy display name="Example: endText" <App> <TextBox endText=".com" /> </App> ``` It is possible to set the other adornments as well: [`endIcon`](#endicon), [`startIcon`](#starticon) and [`startText`](#starttext). ```xmlui-pg copy display name="Example: all adornments" <App> <TextBox startIcon="hyperlink" startText="www." endIcon="email" endText=".com" /> </App> ``` %-PROP-END %-PROP-START endIcon ```xmlui-pg copy display name="Example: endIcon" <App> <TextBox endIcon="email" /> </App> ``` It is possible to set the other adornments as well: [`endText`](#endtext), [`startIcon`](#starticon) and [`startText`](#starttext). ```xmlui-pg copy display name="Example: all adornments" <App> <TextBox startIcon="hyperlink" startText="www." endIcon="email" endText=".com" /> </App> ``` %-PROP-END %-EVENT-START didChange Write in the input field and see how the `Text` underneath it is updated in parallel. ```xmlui-pg copy {3} display name="Example: didChange" <App var.field=""> <TextBox initialValue="{field}" onDidChange="(val) => field = val" /> <Text value="{field}" /> </App> ``` %-EVENT-END %-EVENT-START gotFocus Clicking on the `TextBox` in the example demo changes the label text. Note how clicking elsewhere resets the text to its original. ```xmlui-pg copy {4-5} display name="Example: gotFocus/lostFocus" <App> <TextBox initialValue="{focused === true ? 'I got focused!' : 'I lost focus...'}" onGotFocus="focused = true" onLostFocus="focused = false" var.focused="{false}" /> </App> ``` %-EVENT-END %-API-START value In the example below, typing in the `TextBox` will also display the length of the text typed into it above the field: ```xmlui-pg copy {2-3} display name="Example: value" <App> <Text value="TextBox content length: {inputComponent.value.length}" /> <TextBox id="inputComponent" /> </App> ``` %-API-END %-API-START setValue ```xmlui-pg copy {10} display name="Example: setValue" <App var.changes=""> <TextBox id="inputField" readOnly="true" onDidChange="changes++" /> <HStack> <Button label="Check" onClick="inputField.setValue('example ')" /> <Text value="Number of changes: {changes}" /> </HStack> </App> ``` %-API-END %-API-START focus ```xmlui-pg copy {2-3} display name="Example: focus" <App> <Button label="Trigger Focus" onClick="inputComponent.focus()" /> <TextBox id="inputComponent" /> </App> ``` %-API-END ``` -------------------------------------------------------------------------------- /tools/create-app/index.ts: -------------------------------------------------------------------------------- ```typescript #!/usr/bin/env node import { bold, cyan, green, red, yellow } from "picocolors"; import Commander from "commander"; import path from "path"; import prompts from "prompts"; import checkForUpdate from "update-check"; import { createApp } from "./create-app"; import { validateNpmName } from "./helpers/validate-pkg"; import packageJson from "./package.json"; import { isFolderEmpty } from "./helpers/is-folder-empty"; import fs from "fs"; let projectPath: string = ""; const handleSigTerm = () => process.exit(0); process.on("SIGINT", handleSigTerm); process.on("SIGTERM", handleSigTerm); const onPromptState = (state: any) => { if (state.aborted) { // If we don't re-enable the terminal cursor before exiting // the program, the cursor will remain hidden process.stdout.write("\x1B[?25h"); process.stdout.write("\n"); process.exit(1); } }; const program = new Commander.Command(packageJson.name) .version(packageJson.version) .arguments("<project-directory>") .usage(`${green("<project-directory>")} [options]`) .action((name) => { projectPath = name; }) .option( "--use-git", `Explicitly tell the CLI to initialize a git repository` ) .allowUnknownOption() .parse(process.argv); const packageManager = "npm"; async function run(): Promise<void> { if (typeof projectPath === "string") { projectPath = projectPath.trim(); } if (!projectPath) { const res = await prompts({ onState: onPromptState, type: "text", name: "path", message: "What is your project named?", initial: "my-app", validate: (name) => { const validation = validateNpmName(path.basename(path.resolve(name))); if (validation.valid) { return true; } return "Invalid project name: " + validation.problems![0]; }, }); if (typeof res.path === "string") { projectPath = res.path.trim(); } } if (!projectPath) { console.log( "\nPlease specify the project directory:\n" + ` ${cyan(program.name())} ${green("<project-directory>")}\n` + "For example:\n" + ` ${cyan(program.name())} ${green("my-xmlui-app")}\n\n` + `Run ${cyan(`${program.name()} --help`)} to see all options.` ); process.exit(1); } const resolvedProjectPath = path.resolve(projectPath); const projectName = path.basename(resolvedProjectPath); const { valid, problems } = validateNpmName(projectName); if (!valid) { console.error(`Could not create a project called ${red(`"${projectName}"`)} because of npm naming restrictions:`); problems!.forEach((p) => console.error(` ${red(bold("*"))} ${p}`)); process.exit(1); } /** * Verify the project dir is empty or doesn't exist */ const root = path.resolve(resolvedProjectPath); const appName = path.basename(root); const folderExists = fs.existsSync(root); if (folderExists && !isFolderEmpty(root, appName)) { process.exit(1); } if (program.useGit === undefined) { const { useGit } = await prompts( { type: 'toggle', name: 'useGit', message: `Would you like to initialize a git repository?`, initial: false, active: 'Yes', inactive: 'No', }, { /** * User inputs Ctrl+C or Ctrl+D to exit the prompt. We should close the * process and not write to the file system. */ onCancel: () => { console.error('Exiting.') process.exit(1) }, } ) /** * Depending on the prompt response, set the appropriate program flags. */ program.useGit = Boolean(useGit) } await createApp({ appPath: resolvedProjectPath, packageManager, useGit: !!program.useGit }); } const update = checkForUpdate(packageJson).catch(() => null); async function notifyUpdate(): Promise<void> { try { const res = await update; if (res?.latest) { const updateMessage = "npm i -g create-xmlui-app"; console.log( yellow(bold("A new version of `create-xmlui-app` is available!")) + "\n" + "You can update by running: " + cyan(updateMessage) + "\n" ); } process.exit(); } catch { // ignore error } } run() .then(notifyUpdate) .catch(async (reason) => { console.log(); console.log("Aborting installation."); if (reason.command) { console.log(` ${cyan(reason.command)} has failed.`); } else { console.log(red("Unexpected error. Please report it as a bug:") + "\n", reason); } console.log(); await notifyUpdate(); process.exit(1); }); ``` -------------------------------------------------------------------------------- /xmlui/src/components/Icon/svg/admonition_danger.svg: -------------------------------------------------------------------------------- ``` <svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M18 6C11.3831 6 6 11.3832 6 18C6 24.6168 11.3831 30 18 30C24.6169 30 30 24.6168 30 18C30 11.3832 24.6169 6 18 6ZM18 8.32256C20.2505 8.32256 22.324 9.09473 23.9696 10.3881L10.3881 23.9696C9.09478 22.324 8.32256 20.2505 8.32256 18C8.32256 12.6639 12.6639 8.32256 18 8.32256ZM18 27.6774C15.7495 27.6774 13.676 26.9053 12.0304 25.6119L25.6119 12.0304C26.9053 13.676 27.6774 15.7495 27.6774 18C27.6774 23.3361 23.3361 27.6774 18 27.6774Z" fill="url(#paint0_linear_11264_27095)"/> <path d="M29.9167 18C29.9167 11.4292 24.5709 6.08333 18 6.08333C11.4291 6.08333 6.08333 11.4292 6.08333 18C6.08333 24.5708 11.4291 29.9167 18 29.9167C24.5709 29.9167 29.9167 24.5708 29.9167 18ZM26.527 18C26.527 16.4591 26.1141 15.0148 25.3961 13.7661L13.7661 25.3961C15.0148 26.1141 16.4591 26.527 18 26.527V27.6104L17.5832 27.6008C15.5096 27.5119 13.6043 26.763 12.0723 25.559L25.559 12.0723C26.8433 13.7064 27.6104 15.7652 27.6104 18L27.5977 18.4941C27.3396 23.5644 23.1334 27.6104 18 27.6104V26.527C22.7007 26.527 26.527 22.7007 26.527 18ZM18.4168 8.39917C20.4904 8.4881 22.3957 9.23699 23.9277 10.441L10.441 23.9277C9.23704 22.3957 8.4881 20.4904 8.39917 18.4168L8.38965 18C8.38965 12.7009 12.7009 8.38965 18 8.38965L18.4168 8.39917ZM9.47298 18C9.47298 19.5404 9.88531 20.9844 10.6029 22.2328L22.2328 10.6029C20.9844 9.8853 19.5404 9.47298 18 9.47298C13.2993 9.47298 9.47298 13.2993 9.47298 18ZM31 18C31 25.1691 25.1692 31 18 31C10.8308 31 5 25.1691 5 18C5 10.8309 10.8308 5 18 5C25.1692 5 31 10.8309 31 18Z" fill="url(#paint1_linear_11264_27095)"/> <path d="M31 18C31 10.9428 25.35 5.18214 18.335 5.00391L18 5C10.8308 5 5 10.8309 5 18L5.00391 18.335C5.17931 25.2386 10.7613 30.8207 17.665 30.9961L18 31C25.0573 31 30.8179 25.3499 30.9961 18.335L31 18ZM29.6162 18C29.6162 11.5949 24.4052 6.38379 18 6.38379C11.5948 6.38379 6.38379 11.5949 6.38379 18C6.38379 24.4051 11.5948 29.6162 18 29.6162V29.917L17.6934 29.9131C11.3655 29.7525 6.24754 24.6344 6.08691 18.3066L6.08301 18C6.08301 11.4292 11.4291 6.08301 18 6.08301L18.3066 6.08691C24.7364 6.25013 29.917 11.5317 29.917 18L29.9131 18.3066C29.7499 24.7363 24.4684 29.917 18 29.917V29.6162C24.4052 29.6162 29.6162 24.4051 29.6162 18ZM25.7949 11.8867C27.1191 13.5716 27.9102 15.6954 27.9102 18V18.0078L27.8975 18.502V18.5098C27.6311 23.7386 23.2939 27.9102 18 27.9102H17.9932L17.5762 27.9004H17.5703C15.4317 27.8087 13.4663 27.0363 11.8867 25.7949L11.6211 25.5859L25.5859 11.6211L25.7949 11.8867ZM12.0723 25.5586C13.6042 26.7626 15.5095 27.5116 17.583 27.6006L18 27.6104C22.9728 27.6104 27.0751 23.8136 27.5615 18.9668L27.5977 18.4941L27.6104 18C27.6104 15.7652 26.8429 13.7063 25.5586 12.0723L12.0723 25.5586ZM26.2266 18C26.2266 16.6539 25.8993 15.3851 25.3232 14.2627L14.2627 25.3232C15.3851 25.8993 16.6539 26.2266 18 26.2266C22.5351 26.2266 26.2266 22.5351 26.2266 18ZM18.0068 8.08984L18.4238 8.09961H18.4297L18.8281 8.125C20.8122 8.28992 22.6323 9.04117 24.1133 10.2051L24.3789 10.4141L10.4141 24.3789L10.2051 24.1133C8.96372 22.5337 8.19133 20.5683 8.09961 18.4297V18.4238L8.08984 18.0068V18L8.10254 17.4912C8.36824 12.262 12.7059 8.08984 18 8.08984H18.0068ZM18 8.38965C12.7009 8.38965 8.38965 12.7009 8.38965 18L8.39941 18.417C8.48838 20.4905 9.23748 22.3958 10.4414 23.9277L23.9277 10.4414C22.3958 9.23743 20.4905 8.48838 18.417 8.39941L18 8.38965ZM18.2881 9.47754C19.7202 9.52552 21.0622 9.93004 22.2324 10.6025L10.6025 22.2324C9.93005 21.0622 9.52552 19.7202 9.47754 18.2881L9.47266 18C9.47266 13.2993 13.2993 9.47266 18 9.47266L18.2881 9.47754ZM9.77344 18C9.77344 19.3452 10.0995 20.6135 10.6748 21.7354L21.7354 10.6748C20.6135 10.0995 19.3452 9.77344 18 9.77344C13.4649 9.77344 9.77344 13.4649 9.77344 18ZM26.5156 18.4385C26.2865 22.9364 22.5537 26.5273 18 26.5273C16.4593 26.5273 15.0152 26.1143 13.7666 25.3965L25.3965 13.7666C26.1143 15.0152 26.5273 16.4593 26.5273 18L26.5156 18.4385ZM31.2998 18C31.2998 25.3348 25.3349 31.2998 18 31.2998C10.6651 31.2998 4.7002 25.3348 4.7002 18C4.7002 10.6652 10.6651 4.7002 18 4.7002C25.3349 4.7002 31.2998 10.6652 31.2998 18Z" fill="url(#paint2_linear_11264_27095)"/> <defs> <linearGradient id="paint0_linear_11264_27095" x1="18" y1="6" x2="18" y2="30" gradientUnits="userSpaceOnUse"> <stop stop-color="#ED5A3B"/> <stop offset="1" stop-color="#AF3117"/> </linearGradient> <linearGradient id="paint1_linear_11264_27095" x1="18" y1="5" x2="18" y2="31" gradientUnits="userSpaceOnUse"> <stop stop-color="#EC5A3A"/> <stop offset="1" stop-color="#AF3117"/> </linearGradient> <linearGradient id="paint2_linear_11264_27095" x1="18" y1="5" x2="18" y2="31" gradientUnits="userSpaceOnUse"> <stop stop-color="#EB5A39"/> <stop offset="1" stop-color="#B03117"/> </linearGradient> </defs> </svg> ``` -------------------------------------------------------------------------------- /xmlui/src/components/FileUploadDropZone/FileUploadDropZoneNative.tsx: -------------------------------------------------------------------------------- ```typescript import type * as React from "react"; import type { CSSProperties, ForwardedRef, ReactNode } from "react"; import { forwardRef, useCallback, useEffect } from "react"; import * as dropzone from "react-dropzone"; import styles from "./FileUploadDropZone.module.scss"; import classnames from "classnames"; import type { RegisterComponentApiFn, UpdateStateFn } from "../../abstractions/RendererDefs"; import { useEvent } from "../../components-core/utils/misc"; import { asyncNoop } from "../../components-core/constants"; import { Icon } from "../Icon/IconNative"; import { getComposedRef } from "../component-utils"; // https://github.com/react-dropzone/react-dropzone/issues/1259 const { useDropzone } = dropzone; // ===================================================================================================================== // React FileUploadDropZone component implementation type Props = { children: ReactNode; onUpload: (files: File[]) => void; uid?: string; registerComponentApi: RegisterComponentApiFn; style?: CSSProperties; className?: string; allowPaste?: boolean; text?: string; disabled?: boolean; updateState?: UpdateStateFn; acceptedFileTypes?: string; maxFiles?: number; }; export const defaultProps: Pick<Props, "onUpload" | "uid" | "allowPaste" | "text" | "disabled"> = { onUpload: asyncNoop, uid: "fileUploadDialog", allowPaste: true, text: "Drop files here", disabled: false, }; export const FileUploadDropZone = forwardRef(function FileUploadDropZone( { children, onUpload = defaultProps.onUpload, uid = defaultProps.uid, registerComponentApi, style, className, allowPaste = defaultProps.allowPaste, text = defaultProps.text, disabled = defaultProps.disabled, updateState, acceptedFileTypes, maxFiles, ...rest }: Props, forwardedRef: ForwardedRef<HTMLDivElement>, ) { //accept is in the format {'image/*': [], 'video/*': []} see https://react-dropzone.js.org/#section-accepting-specific-file-types const accept = acceptedFileTypes ? acceptedFileTypes.split(",").reduce((acc, type) => ({ ...acc, [type.trim()]: [] }), {}) : undefined; const onDrop = useCallback( (acceptedFiles: File[]) => { if (!acceptedFiles.length) { return; } updateState?.({ value: acceptedFiles, }); onUpload?.(acceptedFiles); }, [onUpload, updateState], ); const { getRootProps, getInputProps, isDragActive, open, inputRef, isDragAccept } = useDropzone({ onDrop, noClick: true, noKeyboard: true, noDragEventsBubbling: true, disabled, accept, maxFiles, }); const doOpen = useEvent(() => { open(); }); const handleOnPaste = useCallback( (event: React.ClipboardEvent) => { if (!allowPaste) { return; } if (!inputRef.current) { return; } if (event.clipboardData?.files) { const items = event.clipboardData?.items || []; const files: File[] = []; for (let i = 0; i < items.length; i++) { const item = items[i]; if (item.kind === "file") { const file = item.getAsFile(); if (file !== null) { files.push(file); } } } if (files.length > 0) { //the clipboardData.files doesn't necessarily contains files... so we have to double check it event.stopPropagation(); //it's for nested file upload dropzones event.preventDefault(); // and this one is for preventing to paste in the file name, if we a have stored file on the clipboard (copied from finder/windows explorer for example) //stolen from here: https://github.com/react-dropzone/react-dropzone/issues/1210#issuecomment-1537862105 (inputRef.current as unknown as HTMLInputElement).files = event.clipboardData.files; inputRef.current.dispatchEvent(new Event("change", { bubbles: true })); } } }, [allowPaste, inputRef], ); useEffect(() => { registerComponentApi({ open: doOpen, }); }, [doOpen, registerComponentApi, uid]); const { ref, ...rootProps } = getRootProps({ ...rest, style, className: classnames(styles.wrapper, className), onPaste: handleOnPaste, } as React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>); const rootRef = getComposedRef(ref, forwardedRef); return ( <div {...rootProps} data-drop-enabled={!disabled} ref={rootRef}> <input {...getInputProps()} /> {children} {isDragActive && isDragAccept && ( <div className={styles.dropPlaceholder}> <Icon name={"upload"}></Icon> {text} </div> )} </div> ); }); ``` -------------------------------------------------------------------------------- /xmlui/tests/components-core/scripts-runner/eval-tree-func-decl-async.test.ts: -------------------------------------------------------------------------------- ```typescript import { describe, expect, it } from "vitest"; import { evalBindingAsync } from "../../../src/components-core/script-runner/eval-tree-async"; import {createEvalContext} from "./test-helpers"; import { Parser } from "../../../src/parsers/scripting/Parser"; describe("Evaluate function expressions (exp)", () => { it("Funcion decl #1", async () => { // --- Arrange const source = "(function(x) { return 2 * x })(4)"; const context = createEvalContext({}); // --- Act const value = await evalAsync(source, context); // --- Arrange expect(value).equal(8); }); it("Function decl #2", async () => { // --- Arrange const source = "(function (x, y) { return x + y })(1, 2)"; const context = createEvalContext({}); // --- Act const value = await evalAsync(source, context); // --- Arrange expect(value).equal(3); }); it("Function decl #3", async () => { // --- Arrange const source = "(function (x, y) { return x + y })(1, 2)"; const context = createEvalContext({}); // --- Act const value = await evalAsync(source, context); // --- Arrange expect(value).equal(3); }); it("Function decl #4", async () => { // --- Arrange const source = "(function (x) { return (++x.h); })(count)"; const context = createEvalContext({ localContext: { count: { h: 3 } } }); // --- Act const value = await evalAsync(source, context); // --- Arrange expect(value).equal(4); }); it("Function decl #5", async () => { // --- Arrange const source = "(function (x) { return x += 2 })(count)"; const context = createEvalContext({ localContext: { count: 3 } }); // --- Act const value = await evalAsync(source, context); // --- Arrange expect(value).equal(5); }); it("Function decl #6", async () => { // --- Arrange const source = "(function (x) { return x += 2 })(count + 4)"; const context = createEvalContext({ localContext: { count: 3 } }); // --- Act const value = await evalAsync(source, context); // --- Arrange expect(value).equal(9); }); it("Function decl #7", async () => { // --- Arrange const source = "[1,2,3,4,5].filter(function (x) { return x % 2 === 0})[1]"; const context = createEvalContext({ localContext: { count: 3 } }); // --- Act const value = await evalAsync(source, context); // --- Arrange expect(value).equal(4); }); it("Function decl #8", async () => { // --- Arrange const source = "containsArray.array.filter(function (item) {return item % 2 === 0})[1]"; const context = createEvalContext({ localContext: { containsArray: { array: [5, 4, 3, 2, 1] } } }); // --- Act const value = await evalAsync(source, context); // --- Arrange expect(value).equal(2); }); it("Function decl #9", async () => { // --- Arrange const source = "array.reduce(function (acc, item) { return acc + item}, 0)"; const context = createEvalContext({ localContext: { array: [5, 4, 3, 2, 1] } }); // --- Act const value = await evalAsync(source, context); // --- Arrange expect(value).equal(15); }); it("Function decl with rest #1", async () => { // --- Arrange const source = "(function (...a) { return a[0] + a[1] })(1, 2)"; const context = createEvalContext({}); // --- Act const value = await evalAsync(source, context); // --- Arrange expect(value).equal(3); }); it("Function decl with rest #2", async () => { // --- Arrange const source = "(function (x, ...a) { return x + a[0] + a[1] })(1, 2, 3)"; const context = createEvalContext({}); // --- Act const value = await evalAsync(source, context); // --- Arrange expect(value).equal(6); }); it("Function decl reccursive #1", async () => { // --- Arrange const source = "(function factorial(n) { return n <= 0 ? 1 : n * factorial(n - 1)})(3)"; const context = createEvalContext({}); // --- Act const value = await evalAsync(source, context); // --- Arrange expect(value).equal(6); }); }); async function evalAsync(source: string, evalContext: any): Promise<any> { const wParser = new Parser(source); const tree = wParser.parseExpr(); if (tree === null) { // --- This should happen only when an expression is empty return undefined; } // --- Check for expression termination if (!wParser.isEof) { throw new Error("Expression is not terminated properly"); } // --- Ok, valid source, evaluate return await evalBindingAsync(tree, evalContext, undefined); } ```