This is page 17 of 140. Use http://codebase.md/xmlui-org/xmlui/mockApiDef.js?page={x} to view the full context. # Directory Structure ``` ├── .changeset │ ├── config.json │ └── cool-queens-look.md ├── .eslintrc.cjs ├── .github │ ├── build-checklist.png │ ├── ISSUE_TEMPLATE │ │ ├── bug_report.md │ │ └── feature_request.md │ └── workflows │ ├── deploy-blog.yml │ ├── deploy-docs-optimized.yml │ ├── deploy-docs.yml │ ├── prepare-versions.yml │ ├── release-packages.yml │ ├── run-all-tests.yml │ └── run-smoke-tests.yml ├── .gitignore ├── .prettierrc.js ├── .vscode │ ├── launch.json │ └── settings.json ├── blog │ ├── .gitignore │ ├── .gitkeep │ ├── CHANGELOG.md │ ├── extensions.ts │ ├── index.html │ ├── index.ts │ ├── package.json │ ├── public │ │ ├── blog │ │ │ ├── images │ │ │ │ ├── blog-page-component.png │ │ │ │ ├── blog-scrabble.png │ │ │ │ ├── integrated-blog-search.png │ │ │ │ └── lorem-ipsum.png │ │ │ ├── lorem-ipsum.md │ │ │ ├── newest-post.md │ │ │ ├── older-post.md │ │ │ └── welcome-to-the-xmlui-blog.md │ │ ├── mockServiceWorker.js │ │ ├── netlify.toml │ │ ├── resources │ │ │ ├── favicon.ico │ │ │ ├── files │ │ │ │ └── for-download │ │ │ │ └── xmlui │ │ │ │ └── xmlui-standalone.umd.js │ │ │ ├── github.svg │ │ │ ├── llms.txt │ │ │ ├── logo-dark.svg │ │ │ ├── logo.svg │ │ │ ├── pg-popout.svg │ │ │ └── xmlui-logo.svg │ │ ├── serve.json │ │ └── web.config │ ├── scripts │ │ ├── download-latest-xmlui.js │ │ ├── generate-rss.js │ │ ├── get-releases.js │ │ └── utils.js │ ├── src │ │ ├── components │ │ │ ├── BlogOverview.xmlui │ │ │ ├── BlogPage.xmlui │ │ │ ├── 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/Link/Link.tsx: -------------------------------------------------------------------------------- ```typescript import styles from "./Link.module.scss"; import { createComponentRenderer } from "../../components-core/renderers"; import { parseScssVar } from "../../components-core/theming/themeVars"; import { createMetadata, d, dEnabled, dLabel } from "../metadata-helpers"; import { LinkTargetMd } from "../abstractions"; import { LinkNative, defaultProps } from "./LinkNative"; const COMP = "Link"; export const LinkMd = createMetadata({ status: "stable", description: "`Link` creates clickable navigation elements for internal app routes or " + "external URLs. You can use the `label` and `icon` properties for simple text " + "links, or embed custom components like buttons, cards, or complex layouts " + "for rich interactive link presentations.", props: { to: d( "This property defines the URL of the link. If the value is not defined, the link cannot be activated.", ), enabled: dEnabled(), active: { description: `Indicates whether this link is active or not. If so, it will have a distinct visual appearance.`, type: "boolean", defaultValue: defaultProps.active, }, target: { description: `This property specifies where to open the link represented by the \`${COMP}\`. This ` + `property accepts the following values (in accordance with the HTML standard):`, availableValues: LinkTargetMd, type: "string", }, label: dLabel(), icon: d( `This property allows you to add an optional icon (specify the icon's name) to the link.`, ), }, events: { click: { description: "This event is triggered when the link is clicked." } }, themeVars: parseScssVar(styles.themeVars), themeVarDescriptions: { [`gap-icon-${COMP}`]: "This property defines the size of the gap between the icon and the label.", }, defaultThemeVars: { [`border-${COMP}`]: "0px solid $borderColor", [`textColor-${COMP}`]: "$color-primary-500", [`textDecorationColor-${COMP}`]: `textDecorationColor-${COMP}`, [`textColor-${COMP}--hover`]: `$color-primary-400`, [`textDecorationColor-${COMP}--hover`]: `textColor-${COMP}--hover`, [`textColor-${COMP}--active`]: "$color-primary-400", [`textDecorationColor-${COMP}--active`]: `textColor-${COMP}--active`, [`textColor-${COMP}--hover--active`]: `$textColor-${COMP}--active`, [`textUnderlineOffset-${COMP}`]: "$space-1", [`textDecorationLine-${COMP}`]: "underline", [`textDecorationStyle-${COMP}`]: "solid", [`outlineColor-${COMP}--focus`]: "$outlineColor--focus", [`outlineWidth-${COMP}--focus`]: "$outlineWidth--focus", [`outlineStyle-${COMP}--focus`]: "$outlineStyle--focus", [`outlineOffset-${COMP}--focus`]: "$outlineOffset--focus", [`fontSize-${COMP}`]: "inherit", [`fontWeight-${COMP}--active`]: "$fontWeight-bold", [`gap-icon-${COMP}`]: "$gap-tight", [`padding-icon-${COMP}`]: "$space-0_5", dark: { [`textColor-${COMP}`]: "$color-primary-600", [`textColor-${COMP}--hover`]: `$color-primary-500`, [`textColor-${COMP}--active`]: "$color-primary-500", } }, }); /** * This function define the renderer for the Limk component. */ export const localLinkComponentRenderer = createComponentRenderer( COMP, LinkMd, ({ node, extractValue, renderChild, lookupEventHandler, className }) => { return ( <LinkNative to={extractValue(node.props.to)} icon={extractValue(node.props.icon)} active={extractValue.asOptionalBoolean(node.props.active, false)} target={extractValue(node.props?.target)} className={className} disabled={!extractValue.asOptionalBoolean(node.props.enabled ?? true)} onClick={lookupEventHandler("click")} > {node.props.label ? extractValue.asDisplayText(node.props.label) : renderChild(node.children)} </LinkNative> ); }, ); ``` -------------------------------------------------------------------------------- /docs/public/pages/howto/use-the-same-modaldialog-to-add-or-edit.md: -------------------------------------------------------------------------------- ```markdown # Use the same ModalDialog to add or edit See also the [refactoring](/refactoring) guide. Briefly: props flow down, events flow up. ```xmlui-pg noHeader height="400px" ---app <App> <Test /> </App> ---comp display <Component name="Test" var.editingProductId="{null}" var.showAddModal="{false}"> <DataSource id="products" url="/api/products" /> <HStack alignItems="center"> <Text variant="strong" fontSize="$fontSize-2xl">Product Inventory</Text> <SpaceFiller /> <Button label="Add New Product" size="sm" onClick="showAddModal = true" /> </HStack> <Table data="{products}"> <Column bindTo="name" /> <Column bindTo="price" width="120px"/> <Column header="Actions" width="240px"> <HStack> <Button label="Edit" icon="pencil" size="sm" variant="outlined" onClick="editingProductId = $item.id" /> <Button label="Delete" icon="trash" size="sm" variant="outlined" themeColor="attention"> <event name="click"> <APICall method="delete" url="/api/products/{$item.id}" confirmMessage="Are you sure you want to delete '{$item.name}'?" /> </event> </Button> </HStack> </Column> </Table> <ProductModal when="{showAddModal}" mode="add" onClose="showAddModal = false" onSaved="products.refetch()" /> <ProductModal when="{editingProductId}" mode="edit" productId="{editingProductId}" onClose="editingProductId = null" onSaved="products.refetch()" /> </Component> ---comp display <Component name="ProductModal"> <DataSource id="productDetails" url="/api/products/{$props.productId}" when="{$props.mode === 'edit' && $props.productId}" /> <ModalDialog title="{$props.mode === 'edit' ? 'Edit Product' : 'Add Product'}" when="{$props.mode === 'add' || productDetails.loaded}" onClose="emitEvent('close')" > <Form data="{$props.mode === 'edit' ? productDetails.value : {}}" submitUrl="{$props.mode === 'edit' ? '/api/products/' + $props.productId : '/api/products'}" submitMethod="{$props.mode === 'edit' ? 'put' : 'post'}" onSuccess="emitEvent('saved')" > <FormItem bindTo="name" label="Product Name" required="true" /> <FormItem bindTo="price" label="Price" type="number" required="true" /> </Form> </ModalDialog> </Component> ---api { "apiUrl": "/api", "initialize": "$state.products = [ { id: 1, name: 'Laptop Pro', price: 1299 }, { id: 2, name: 'Wireless Mouse', price: 29 } ]", "operations": { "get-products": { "url": "/products", "method": "get", "handler": "$state.products" }, "get-product": { "url": "/products/:id", "method": "get", "pathParamTypes": { "id": "integer" }, "handler": "return $state.products.find(p => p.id === $pathParams.id)" }, "insert-product": { "url": "/products", "method": "post", "handler": " const newId = $state.products.length > 0 ? Math.max(...$state.products.map(p => p.id)) + 1 : 1; $state.products.push({ id: newId, name: $requestBody.name, price: Number($requestBody.price) }); " }, "update-product": { "url": "/products/:id", "method": "put", "pathParamTypes": { "id": "integer" }, "handler": " const oldItem = $state.products.find(p => p.id === $pathParams.id); if (oldItem) { oldItem.name = $requestBody.name; oldItem.price = Number($requestBody.price); } " }, "delete-product": { "url": "/products/:id", "method": "delete", "pathParamTypes": { "id": "integer" }, "handler": "$state.products = $state.products.filter(p => p.id !== $pathParams.id)" } } } ``` ``` -------------------------------------------------------------------------------- /xmlui/src/components/AppHeader/AppHeader.tsx: -------------------------------------------------------------------------------- ```typescript import styles from "./AppHeader.module.scss"; import { createComponentRenderer } from "../../components-core/renderers"; import { parseScssVar } from "../../components-core/theming/themeVars"; import { paddingSubject } from "../../components-core/theming/themes/base-utils"; import { createMetadata, dComponent } from "../../components/metadata-helpers"; import { SlotItem } from "../../components/SlotItem"; import { AppContextAwareAppHeader, defaultProps } from "./AppHeaderNative"; import classnames from "classnames"; const COMP = "AppHeader"; export const AppHeaderMd = createMetadata({ status: "stable", description: "`AppHeader` defines the top navigation bar of your application within the " + "[`App`](/components/App) component. It automatically handles logo placement, " + "application title, and user profile areas with built-in responsive behavior.", props: { profileMenuTemplate: dComponent( `This property makes the profile menu slot of the \`${COMP}\` component customizable.`, ), logoTemplate: dComponent( "This property defines the template to use for the logo. With this property, you can " + "construct your custom logo instead of using a single image.", ), titleTemplate: dComponent( "This property defines the template to use for the title. With this property, you can " + "construct your custom title instead of using a single image.", ), title: { description: "Title for the application logo", valueType: "string", }, showLogo: { description: "Show the logo in the header", valueType: "boolean", defaultValue: defaultProps.showLogo, }, }, themeVars: parseScssVar(styles.themeVars), themeVarDescriptions: { [`padding‑logo‑${COMP}`]: "This theme variable sets the padding of the logo in the app header (including all " + "`padding` variants, such as `paddingLeft-logo-AppHeader` and others).", [`width‑logo‑${COMP}`]: "Sets the width of the displayed logo", }, defaultThemeVars: { [`padding-drawerToggle-${COMP}`]: "$space-0_5", [`height-${COMP}`]: "$space-14", [`maxWidth-content-${COMP}`]: "$maxWidth-content-App", [`maxWidth-${COMP}`]: "$maxWidth-App", [`borderBottom-${COMP}`]: "1px solid $borderColor", ...paddingSubject(`logo-${COMP}`, { horizontal: "$space-0", vertical: "$space-0" }), ...paddingSubject(COMP, { horizontal: "$space-4", vertical: "$space-0" }), [`borderRadius-${COMP}`]: "0px", [`backgroundColor-${COMP}`]: "$color-surface-raised", }, }); export const appHeaderComponentRenderer = createComponentRenderer( COMP, AppHeaderMd, ({ node, renderChild, className, layoutContext, extractValue }) => { // --- Convert the plain (text) logo template into component definition const logoTemplate = node.props.logoTemplate || node.slots?.logoSlot; const titleTemplate = node.props.titleTemplate || node.slots?.titleSlot; return ( <AppContextAwareAppHeader profileMenu={renderChild(extractValue(node.props.profileMenuTemplate, true))} title={extractValue(node.props.title)} showLogo={extractValue.asOptionalBoolean(node.props.showLogo)} titleContent={ titleTemplate && ( <SlotItem node={titleTemplate} renderChild={renderChild} slotProps={{ title: extractValue(node.props.title) }} /> ) } logoContent={renderChild(logoTemplate, { type: "Stack", orientation: "horizontal", })} className={classnames(layoutContext?.themeClassName, className)} renderChild={renderChild} > {renderChild(node.children, { // Since the AppHeader is a flex container, it's children should behave the same as in a stack type: "Stack", })} </AppContextAwareAppHeader> ); }, ); ``` -------------------------------------------------------------------------------- /docs/content/components/Items.md: -------------------------------------------------------------------------------- ```markdown # Items [#items] `Items` renders data arrays without built-in layout or styling, providing a lightweight alternative to `List`. Unlike `List`, it provides no virtualization, grouping, or visual formatting — just pure data iteration. **Key features:** - **Simple iteration**: Maps data arrays to components using `$item`, `$itemIndex`, `$isFirst`, and `$isLast` context - **Layout agnostic**: No built-in styling or container—children determine the visual presentation - **Reverse ordering**: Optional `reverse` property to display data in opposite order - **Performance**: Lightweight alternative to `List` when you don't need virtualization or grouping >[!INFO] > `Items` is not a container! It does not wrap its items into a container; it merely renders its children. The `Items` component does not use virtualization; it maps each data item into a component. Thus, passing many items to a component instance will use many resources and slow down your app. If you plan to work with many items (more than a few dozen), use the [`List`](./List) and [`Table`](./Table) components instead. ### Inline Data [#inline-data] You can set the list of data to be rendered via the `data` property, as the following sample shows. The nested child component describes a template to display each data entry in `Items`. In the template, you can refer to a particular entry with the [`$item`](#&item) identifier: ```xmlui-pg copy {8} display name="Example: inline data" <App> <VStack> <Items data="{[ { idx: 1, value: 'One lion' }, { idx: 2, value: 'Two monkeys' }, { idx: 3, value: 'Three rabbits' }, ]}"> <Text>{$item.idx} - {$item.value}</Text> </Items> </VStack> </App> ``` ### Data Binding [#data-binding] You can use also API bindings to display data: ```xmlui-pg copy {4-6} display name="Example: data binding" <App> <VStack> <Items> <property name="data"> <DataSource url="https://api.spacexdata.com/v3/rockets"/> </property> <Image height="80px" width="110px" fit="cover" src="{$item.flickr_images[0]}"/> </Items> </VStack> </App> ``` **Context variables available during execution:** - `$isFirst`: Boolean indicating if this is the first item - `$isLast`: Boolean indicating if this is the last item - `$item`: Current data item being rendered - `$itemIndex`: Zero-based index of current item ## Use children as Content Template [#use-children-as-content-template] The [itemTemplate](#itemtemplate) property can be replaced by setting the item template component directly as the Items's child. In the following example, the two Items are functionally the same: ```xmlui copy <App> <!-- This is the same --> <Items> <property name="itemTemplate"> <Text>Template</Text> </property> </Items> <!-- As this --> <Items> <Text>Template</Text> </Items> </App> ``` ## Properties [#properties] ### `data` [#data] This property contains the list of data items (obtained from a data source) this component renders. ### `itemTemplate` [#itemtemplate] The component template to display a single item ### `reverse` (default: false) [#reverse-default-false] This property reverses the order in which data is mapped to template components. ```xmlui-pg copy {4} display name="Example: reverse" <App> <VStack> <Items reverse="true" data="{[ { idx: 1, value: 'One lion' }, { idx: 2, value: 'Two monkeys' }, { idx: 3, value: 'Three rabbits' }, ]}"> <Text>{$item.idx} - {$item.value}</Text> </Items> </VStack> </App> ``` ## Events [#events] This component does not have any events. ## Exposed Methods [#exposed-methods] This component does not expose any methods. ## Styling [#styling] The `Items` component does not support styling. You should style the container component that wraps `Items`. You can also style the individual items via specifying a template component. ``` -------------------------------------------------------------------------------- /xmlui/src/components/RadioGroup/RadioGroup.tsx: -------------------------------------------------------------------------------- ```typescript import styles from "./RadioGroup.module.scss"; import { createComponentRenderer } from "../../components-core/renderers"; import { parseScssVar } from "../../components-core/theming/themeVars"; import { createMetadata, dAutoFocus, dDidChange, dEnabled, dGotFocus, dInitialValue, dInternal, dLostFocus, dReadonly, dRequired, dValidationStatus, } from "../metadata-helpers"; import { RadioGroup, defaultProps } from "./RadioGroupNative"; const COMP = "RadioGroup"; const RGOption = `RadioGroupOption`; export const RadioGroupMd = createMetadata({ status: "stable", description: "`RadioGroup` creates a mutually exclusive selection interface where users can " + "choose only one option from a group of radio buttons. It manages the selection " + "state and ensures that selecting one option automatically deselects all others in " + "the group." + "\n\n" + "Radio options store their values as strings. Numbers and booleans are converted to strings " + "when assigned, while objects, functions and arrays default to an empty string unless resolved " + "via binding expressions.", props: { initialValue: { ...dInitialValue(), defaultValue: defaultProps.initialValue, }, autoFocus: dAutoFocus(), required: { ...dRequired(), defaultValue: defaultProps.required, }, readOnly: dReadonly(), enabled: { ...dEnabled(), defaultValue: defaultProps.enabled, }, validationStatus: { ...dValidationStatus(), defaultValue: defaultProps.validationStatus, }, orientation: dInternal( `(*** NOT IMPLEMENTED YET ***) This property sets the orientation of the ` + `options within the radio group.`, ), }, events: { gotFocus: dGotFocus(COMP), lostFocus: dLostFocus(COMP), didChange: dDidChange(COMP), }, themeVars: parseScssVar(styles.themeVars), defaultThemeVars: { [`gap-${RGOption}`]: "$space-1_5", [`borderWidth-${RGOption}`]: "1px", [`borderWidth-${RGOption}-validation`]: `2px`, [`borderColor-${RGOption}-default`]: "$color-surface-500", [`borderColor-checked-${RGOption}`]: "$color-primary-500", [`borderColor-${RGOption}-default--hover`]: "$color-surface-700", [`borderColor-${RGOption}-default--active`]: "$color-primary-500", [`borderColor-${RGOption}-error`]: `$borderColor-Input-default--error`, [`borderColor-${RGOption}-warning`]: `$borderColor-Input-default--warning`, [`borderColor-${RGOption}-success`]: `$borderColor-Input-default--success`, [`backgroundColor-${RGOption}--disabled`]: "$backgroundColor--disabled", [`backgroundColor-checked-${RGOption}`]: "$color-primary-500", [`backgroundColor-checked-${RGOption}--disabled`]: `$textColor--disabled`, [`fontSize-${RGOption}`]: "$fontSize-sm", [`fontWeight-${RGOption}`]: "$fontWeight-bold", }, }); export const radioGroupRenderer = createComponentRenderer( COMP, RadioGroupMd, ({ node, extractValue, className, state, updateState, lookupEventHandler, renderChild, registerComponentApi, }) => { return ( <RadioGroup autofocus={extractValue.asOptionalBoolean(node.props.autoFocus)} enabled={extractValue.asOptionalBoolean(node.props.enabled)} className={className} initialValue={extractValue(node.props.initialValue)} value={state?.value} updateState={updateState} validationStatus={extractValue(node.props.validationStatus)} onDidChange={lookupEventHandler("didChange")} onFocus={lookupEventHandler("gotFocus")} onBlur={lookupEventHandler("lostFocus")} registerComponentApi={registerComponentApi} required={extractValue.asOptionalBoolean(node.props.required)} readOnly={extractValue.asOptionalBoolean(node.props.readOnly)} > {renderChild(node.children)} </RadioGroup> ); }, ); ``` -------------------------------------------------------------------------------- /xmlui/src/parsers/scripting/modules.ts: -------------------------------------------------------------------------------- ```typescript import type { ScriptModule} from "../../components-core/script-runner/ScriptingSourceTree"; import { T_FUNCTION_DECLARATION, type FunctionDeclaration, type Statement, } from "../../components-core/script-runner/ScriptingSourceTree"; import type { ErrorCodes, ParserErrorMessage } from "./ParserError"; import { Parser } from "./Parser"; import { errorMessages } from "./ParserError"; import { TokenType } from "./TokenType"; /** * Represents a module error */ export type ModuleErrors = Record<string, ParserErrorMessage[]>; /** * Checks if the result is a module error * @param result Result to check * @returns True if the result is a module error */ export function isModuleErrors(result: ScriptModule | ModuleErrors): result is ModuleErrors { return (result as any).type !== "ScriptModule"; } /** * Parses a script module * @param moduleName Name of the module * @param source Source code to parse * @param moduleResolver A function that resolves a module path to the text of the module * @returns The parsed and resolved module */ export function parseScriptModule( moduleName: string, source: string ): ScriptModule | ModuleErrors { // --- Keep track of parsed modules to avoid circular references const parsedModules = new Map<string, ScriptModule>(); const moduleErrors: ModuleErrors = {}; const parsedModule = doParseModule(moduleName, source); return !parsedModule || Object.keys(moduleErrors).length > 0 ? moduleErrors : parsedModule; // --- Do the parsing, allow recursion function doParseModule( moduleName: string, source: string ): ScriptModule | null | undefined { // --- Do not parse the same module twice if (parsedModules.has(moduleName)) { return parsedModules.get(moduleName); } // --- Parse the source code const parser = new Parser(source); let statements: Statement[] = []; try { statements = parser.parseStatements()!; } catch (error) { moduleErrors[moduleName] = parser.errors; return null; } // --- Check for unparsed tail const lastToken = parser.current; if (lastToken.type !== TokenType.Eof) { moduleErrors[moduleName] ??= []; moduleErrors[moduleName].push({ code: "W002", text: errorMessages["W002"].replace(/\{(\d+)}/g, () => lastToken.text), position: lastToken.startLine, line: lastToken.startLine, column: lastToken.startColumn, }); return null; } const errors: ParserErrorMessage[] = []; // --- Collect functions const functions: Record<string, FunctionDeclaration> = {}; statements .filter((stmt) => stmt.type === T_FUNCTION_DECLARATION) .forEach((stmt) => { const func = stmt as FunctionDeclaration; if (functions[func.id.name]) { addErrorMessage("W020", stmt, func.id.name); return; } functions[func.id.name] = func; }); // --- Successful module parsing const parsedModule: ScriptModule = { type: "ScriptModule", name: moduleName, functions, statements: statements, sources: new Map(), }; // --- Sign this module as parsed parsedModules.set(moduleName, parsedModule); // --- Catch errors if (errors.length > 0) { moduleErrors[moduleName] = errors; return null; } // --- Done. return parsedModule; function addErrorMessage(code: ErrorCodes, stmt: Statement, ...args: any[]): void { let errorText = errorMessages[code]; if (args) { args.forEach( (o, idx) => (errorText = errorText.replaceAll(`{${idx}}`, args[idx].toString())), ); } errors.push({ code, text: errorMessages[code].replace(/\{(\d+)}/g, (_, index) => args[index]), position: stmt.startToken?.startPosition, line: stmt.startToken?.startLine, column: stmt.startToken?.startColumn, }); } } } ``` -------------------------------------------------------------------------------- /xmlui/src/components/Badge/Badge.tsx: -------------------------------------------------------------------------------- ```typescript import styles from "./Badge.module.scss"; import { createComponentRenderer } from "../../components-core/renderers"; import { parseScssVar } from "../../components-core/theming/themeVars"; import { Badge, badgeVariantValues, defaultProps, isBadgeColors, type BadgeColors, } from "./BadgeNative"; import { createMetadata, dInternal } from "../metadata-helpers"; import { toCssVar } from "../../parsers/style-parser/StyleParser"; const COMP = "Badge"; export const BadgeMd = createMetadata({ status: "stable", description: "`Badge` displays small text labels with colored backgrounds, commonly used for " + "status indicators, categories, tags, and counts. It supports dynamic color " + "mapping based on content values, useful for status systems and data categorization.", props: { value: { description: "The text that the component displays. If this is not defined, the component renders " + "its children as the content of the badge. If neither text nor any child is defined, " + "the component renders a single frame for the badge with a non-breakable space.", type: "string", isRequired: true, }, variant: { description: "Modifies the shape of the component. Comes in the regular \`badge\` variant or the \`pill\` variant " + "with fully rounded corners.", type: "string", availableValues: badgeVariantValues, defaultValue: defaultProps.variant, }, colorMap: { description: `The \`${COMP}\` component supports the mapping of a list of colors using the \`value\` prop as the ` + `key. If this property is not set, no color mapping is used.`, }, themeColor: dInternal(`(**NOT IMPLEMENTED YET**) The theme color of the component.`), indicatorText: dInternal( `(**NOT IMPLEMENTED YET**) This property defines the text to display in the indicator. If it is not ` + `defined or empty, no indicator is displayed unless the \`forceIndicator\` property is set.`, ), forceIndicator: dInternal( `(**NOT IMPLEMENTED YET**) This property forces the display of the indicator, even if ` + `the \`indicatorText\` property is not defined or empty.`, ), indicatorThemeColor: dInternal(`(**NOT IMPLEMENTED YET**) The theme color of the indicator.`), indicatorPosition: dInternal(`(**NOT IMPLEMENTED YET**) The position of the indicator.`), }, events: {}, themeVars: parseScssVar(styles.themeVars), defaultThemeVars: { [`padding-${COMP}`]: `$space-0_5 $space-2`, [`border-${COMP}`]: `0px solid $borderColor`, [`padding-${COMP}-pill`]: `$space-0_5 $space-2`, [`borderRadius-${COMP}`]: "4px", [`fontSize-${COMP}`]: "0.8em", [`fontSize-${COMP}-pill`]: "0.8em", [`backgroundColor-${COMP}`]: "rgb(from $color-secondary-500 r g b / 0.6)", [`textColor-${COMP}`]: "$const-color-surface-0", }, }); export const badgeComponentRenderer = createComponentRenderer( COMP, BadgeMd, ({ node, extractValue, renderChild, className }) => { const value = extractValue.asDisplayText(node.props.value); const colorMap: Record<string, string | BadgeColors> | undefined = extractValue( node.props?.colorMap, ); let colorValue: string | BadgeColors | undefined; if (colorMap && value) { const resolvedColor = colorMap[value]; if (typeof resolvedColor === "string") { colorValue = resolveColor(resolvedColor); } else if (isBadgeColors(resolvedColor)) { colorValue = { label: resolveColor(resolvedColor.label), background: resolveColor(resolvedColor.background), }; } } return ( <Badge variant={extractValue(node.props.variant)} color={colorValue} className={className}> {value || (node.children && renderChild(node.children)) || String.fromCharCode(0xa0)} </Badge> ); }, ); function resolveColor(value: string): string { return value.startsWith("$") ? toCssVar(value) : value; } ``` -------------------------------------------------------------------------------- /docs/public/pages/tutorial-02.md: -------------------------------------------------------------------------------- ```markdown # Main.xmlui At the root of every XMLUI app is `Main.xmlui` which declares the [App](/components/App) that defines layout and navigation. ## App ```xmlui <App name="XMLUI Invoice" layout="vertical-full-header" defaultTheme="invoice" loggedInUser="{currentUser.value}" > ``` `vertical-full-header` means the header and the navigation bar dock to the top of the app's window, while the footer sticks to the bottom. There are a half-dozen other flavors, see the [App](/components/App) page for the full story. ### Optional default theme The `defaultTheme` points to a file called `invoice.json`. ```json { "name": "Invoice", "id": "invoice", "themeVars": { "color-primary": "hsl(205, 76%, 58%)", "color-secondary": "hsl(210, 30%, 60%)", "color-surface": "hsl(0, 0%, 96%)", "gap-adornment-Input": "2px", "borderRadius-Avatar": "50%" } } ``` It's concise but very powerful. The choices for `color-primary`, `color-secondary`, and `color-surface` define a set of coordinated [palettes](/palettes) that form the core of a [Theme](/themes-intro). Then there's a small handful of theme variables. `gap-adornment-Input` adjusts the space between a dollar sign and its value. `borderRadius-Avatar` does what it says on the tin. These things are optional. XMLUI's mission is to ensure what you build looks good with little if any explicit styling. ## AppState ```xmlui <AppState id="settings" bucket="settingsState" /> ``` [AppState](/components/AppState) is a blackboard where components can post and read data. In our demo it defines a data structure for the app settings. ## AppHeader ```xmlui <AppHeader> <H1>XMLUI Invoice</H1> <property name="profileMenuTemplate"> <HStack verticalAlignment="center" onClick="Actions.navigate('/settings')" > <Avatar url="{loggedInUser.avatar_url}" name="{loggedInUser.display_name}" /> <Text>{loggedInUser.display_name}</Text> </HStack> </property> </AppHeader> ``` Our demo uses `profileMenuTemplate`, one of the templates [AppHeader](/components/AppHeader) provides so you can compose what goes into common header slots. ## NavPanel ```xmlui <NavPanel> <NavLink label="Dashboard" to="/" /> <NavLink label="Invoices" to="/invoices" /> <NavLink label="Clients" to="/clients" /> <NavLink label="Products" to="/products" /> <NavLink label="Search" to="/search" /> <NavLink label="Import" to="/importProducts" /> <NavLink label="Settings" to="/settings" /> </NavPanel> ``` The [NavPanel](/components/NavPanel) defines [NavLink](/components/NavLink)s which are routes, or local URLs, navigable within and from outside the app. ## Pages ```xmlui <Pages> <Page url="/"> <Dashboard /> </Page> <Page url="/invoices"> <Invoices /> </Page> <Page url="/invoices/new"> <CreateInvoice /> </Page> <Page url="/clients"> <Clients /> </Page> <Page url="/clients/new"> <CreateClient /> </Page> <Page url="/products"> <Products /> </Page> <Page url="/search"> <Search /> </Page> <Page url="/search/invoices-after"> <SearchInvoicesAfter /> </Page> <Page url="/search/everything"> <SearchEverything /> </Page> <Page url="/importProducts"> <ImportProducts /> </Page> <Page url="/settings"> <Settings /> </Page> <Page url="/users/new"> <CreateUser /> </Page> </Pages> ``` Each [Page](/components/Pages) is a container that binds to a route and holds XMLUI markup. Although you can put anything in there, our demo shows what we think is a best practice: use [Components](/components-intro) to encapsulate your business logic. ## Footer ```xmlui-pg display name="Try clicking the ToneSwitch" <App> <Footer> Built with XMLUI <SpaceFiller /> <ToneSwitch /> </Footer> </App> ``` Our `Main.xmlui` concludes with a [Footer](/components/Footer) that embeds a [ToneSwitch](/components/ToneSwitch). ``` -------------------------------------------------------------------------------- /xmlui/src/abstractions/ActionDefs.ts: -------------------------------------------------------------------------------- ```typescript import type { AppContextObject } from "../abstractions/AppContextDefs"; import type { AsyncFunction, SyncFunction } from "./FunctionDefs"; import type { ContainerState } from "./ContainerDefs"; import type { ArrowExpression } from "../components-core/script-runner/ScriptingSourceTree"; import type { ApiInterceptor } from "../components-core/interception/ApiInterceptor"; // This type represents the options to use for looking up actions. export type LookupActionOptions = { // This property (by default, true) indicates that any error should be signed // while executing an event handler. Set it to `false` to suppress error // indication. signError?: boolean; // We can use the event's name to get info about a particular event's progress. // Note: Multiple events can run simultaneously. eventName?: string; // By default, we cache resolved action functions. This property signs that we // don't want to cache this function. Use true on one-off handlers, like the // ones in Actions (e.g., `MutateAction`). ephemeral?: boolean; // This property declares the script code to use as a default for a particular // event handler. defaultHandler?: string; // extra context to pass to the action context?: any; }; // This function resolves an action by its name (within the component node that // runs the action) with the specified options and returns the action function if // found. Otherwise, return undefined. export type LookupAsyncFnInner = ( action: string | undefined, // The unique identifier of the container that the action is executed in. uid: symbol, actionOptions?: LookupActionOptions ) => AsyncFunction | undefined; // This function resolves an action by its name with the specified options and // returns the action function if found. Otherwise, return undefined. export type LookupAsyncFn = ( action: string | undefined, actionOptions?: LookupActionOptions ) => AsyncFunction | undefined; // This function resolves a sync action by its name (within the component node // running it) and returns the action function if found. Otherwise, it returns // undefined. export type LookupSyncFnInner = (action: ArrowExpression | undefined, uid: symbol) => SyncFunction | undefined; // This function resolves a sync action by its name and returns the action // function if it is found. Otherwise, it returns undefined. export type LookupSyncFn = (action: string | undefined) => SyncFunction | undefined; // XMLUI keeps a registry of available actions to store information about // out-of-the-box and custom actions (contributions implemented in external // packages or files). This type describes the information the registry needs // about the action to be able to render it. export interface ActionRendererDef { // The name of the action that will be used to reference it in XMLUI. actionName: string; // The function that executes the action. actionFn: ActionFunction; } // This type represents the context in which an action is executed. export interface ActionExecutionContext { // A unique identifier for the container that the action is executed in. uid: symbol; // The state of the container that the action is executed in. state: ContainerState; getCurrentState: ()=>ContainerState; // The appContext object passed to the current app appContext: AppContextObject; apiInstance?: ApiInterceptor; // The lookup function to resolve a sync action by its name. lookupAction: LookupAsyncFnInner; navigate: any; // TEMPORARY stuff, we could use the one in the appContext, but until // https://github.com/remix-run/react-router/issues/7634 fixed we can't location: any; // TEMPORARY stuff, we could use the one in the appContext, but until // https://github.com/remix-run/react-router/issues/7634 fixed we can't } // This type represents a function that executes a particular action within the // specified context using the given arguments. export type ActionFunction = (executionContext: ActionExecutionContext, ...args: any[]) => any; ``` -------------------------------------------------------------------------------- /xmlui/src/components/RadioGroup/RadioGroup.md: -------------------------------------------------------------------------------- ```markdown %-DESC-START **Key features:** - **Exclusive selection**: Only one option can be selected at a time within the group - **Form integration**: Built-in validation states and seamless form compatibility - **Flexible layout**: Contains [Option](/components/Option) that can be arranged in any layout structure - **State management**: Automatically handles selection state and change events - **Accessibility support**: Proper keyboard navigation and screen reader compatibility - **Validation indicators**: Visual feedback for error, warning, and valid states `RadioGroup` is often used in forms. See [this guide](/forms) for details. %-DESC-END %-PROP-START initialValue This property defines the initial value of the selected option within the group. ```xmlui-pg copy display name="Example: initialValue" <App> <RadioGroup initialValue="first"> <HStack padding="$space-normal"> <Option label="First Item" value="first"/> <Option label="Second Item" value="second"/> <Option label="Third Item" value="third"/> </HStack> </RadioGroup> </App> ``` %-PROP-END %-PROP-START enabled This property indicates whether the input accepts user actions (`true`) or not (`false`). The default value is `true`. ```xmlui-pg copy display name="Example: enabled" <App> <RadioGroup initialValue="first" enabled="false"> <HStack padding="$space-normal"> <Option label="First Item" value="first"/> <Option label="Second Item" value="second"/> <Option label="Third Item" value="third"/> </HStack> </RadioGroup> </App> ``` %-PROP-END %-PROP-START validationStatus This prop is used to visually indicate status changes reacting to form field validation. | Value | Description | | :-------- | :---------------------------------------------------- | | `valid` | Visual indicator for an input that is accepted | | `warning` | Visual indicator for an input that produced a warning | | `error` | Visual indicator for an input that produced an error | ```xmlui-pg copy display name="Example: validationStatus" <App> <HStack> <RadioGroup initialValue="first" validationStatus="error"> <Option label="First Item" value="first"/> <Option label="Second Item" value="second"/> </RadioGroup> <RadioGroup initialValue="first" validationStatus="warning"> <Option label="First Item" value="first"/> <Option label="Second Item" value="second"/> </RadioGroup> <RadioGroup initialValue="first" validationStatus="valid"> <Option label="First Item" value="first"/> <Option label="Second Item" value="second"/> </RadioGroup> </HStack> </App> ``` %-PROP-END %-API-START setValue You can use this method to set the component's current value programmatically. ```xmlui-pg copy {2, 7-8} display name="Example: value and setValue" <App> <RadioGroup id="radio"> <Option label="First Item" value="first"/> <Option label="Second Item" value="second"/> </RadioGroup> <HStack> <Button label="Set First" onClick="radio.setValue('first')" /> <Button label="Set Second" onClick="radio.setValue('second')" /> </HStack> </App> ``` %-API-END %-EVENT-START didChange This event is triggered after the user has changed the field value. The following example uses this event to display the selected option's value: ```xmlui-pg ---app copy display name="Example: didChange" <App var.field=""> <RadioGroup initialValue="{field}" onDidChange="(val) => field = val"> <Option label="First Item" value="first"/> <Option label="Second Item" value="second"/> </RadioGroup> <Text value="{field}" /> </App> ---desc Select one of the available options and see how the `Text` underneath it is updated in parallel: ``` %-EVENT-END %-STYLE-START `RadioGroup` is a component that governs its children and stores the selected value. It does not support styling; however, you can style the options within the group. When you set the theme variables for the group's options, use the `RadioGroupOption` name. %-STYLE-END ``` -------------------------------------------------------------------------------- /xmlui/tests/parsers/scripting/parser-binary.test.ts: -------------------------------------------------------------------------------- ```typescript import { describe, expect, it } from "vitest"; import { Parser } from "../../../src/parsers/scripting/Parser"; import { BinaryExpression, T_BINARY_EXPRESSION, T_CALCULATED_MEMBER_ACCESS_EXPRESSION, T_CONDITIONAL_EXPRESSION, T_FUNCTION_INVOCATION_EXPRESSION, T_IDENTIFIER, T_LITERAL, T_MEMBER_ACCESS_EXPRESSION, T_SEQUENCE_EXPRESSION, T_UNARY_EXPRESSION, } from "../../../src/components-core/script-runner/ScriptingSourceTree"; describe("Parser - Binary operations", () => { const binaryOpCases = [ { src: "a**b", op: "**" }, { src: "a+b", op: "+" }, { src: "a-b", op: "-" }, { src: "a*b", op: "*" }, { src: "a/b", op: "/" }, { src: "a%b", op: "%" }, { src: "a>>b", op: ">>" }, { src: "a<<b", op: "<<" }, { src: "a>>>b", op: ">>>" }, { src: "a == b", op: "==" }, { src: "a != b", op: "!=" }, { src: "a < b", op: "<" }, { src: "a <= b", op: "<=" }, { src: "a>b", op: ">" }, { src: "a>=b", op: ">=" }, { src: "a ?? b", op: "??" }, { src: "a | b", op: "|" }, { src: "a & b", op: "&" }, { src: "a ^ b", op: "^" }, { src: "a || b", op: "||" }, { src: "a && b", op: "&&" }, { src: "a in b", op: "in" }, ]; binaryOpCases.forEach((c) => { it(`Binary (operator): ${c.src}`, () => { // --- Arrange const wParser = new Parser(c.src); // --- Act const expr = wParser.parseExpr(); // --- Assert expect(expr).not.equal(null); if (!expr) return; expect(expr.type).equal(T_BINARY_EXPRESSION); const binary = expr as BinaryExpression; expect(binary.op).equal(c.op); }); }); const binaryLeftOperandCases = [ { src: "a+b", op: "+", exp: T_IDENTIFIER }, { src: "a+(b+c)", op: "+", exp: T_IDENTIFIER }, { src: "a+b+c", op: "+", exp: T_BINARY_EXPRESSION }, { src: "a+b*c", op: "+", exp: T_IDENTIFIER }, { src: "!a+b", op: "+", exp: T_UNARY_EXPRESSION }, { src: "a.c+b", op: "+", exp: T_MEMBER_ACCESS_EXPRESSION }, { src: "a[c]+b", op: "+", exp: T_CALCULATED_MEMBER_ACCESS_EXPRESSION }, { src: "(a ? b : c)+b", op: "+", exp: T_CONDITIONAL_EXPRESSION }, { src: "123+b", op: "+", exp: T_LITERAL }, { src: "a(b,c)+b", op: "+", exp: T_FUNCTION_INVOCATION_EXPRESSION }, { src: "(123, 1+c)+b", op: "+", exp: T_SEQUENCE_EXPRESSION }, ]; binaryLeftOperandCases.forEach((c) => { it(`Binary (left operand): ${c.src}`, () => { // --- Arrange const wParser = new Parser(c.src); // --- Act const expr = wParser.parseExpr(); // --- Assert expect(expr).not.equal(null); if (!expr) return; expect(expr.type).equal(T_BINARY_EXPRESSION); const binary = expr as BinaryExpression; expect(binary.op).equal(c.op); expect(binary.left.type).equal(c.exp); }); }); const binaryRightOperandCases = [ { src: "a+b", op: "+", exp: T_IDENTIFIER }, { src: "a+(b+c)", op: "+", exp: T_BINARY_EXPRESSION }, { src: "a+b+c", op: "+", exp: T_IDENTIFIER }, { src: "a+b*c", op: "+", exp: T_BINARY_EXPRESSION }, { src: "a*b+c", op: "+", exp: T_IDENTIFIER }, { src: "a+!b", op: "+", exp: T_UNARY_EXPRESSION }, { src: "a+b.c", op: "+", exp: T_MEMBER_ACCESS_EXPRESSION }, { src: "a+b[c]", op: "+", exp: T_CALCULATED_MEMBER_ACCESS_EXPRESSION }, { src: "a +(a ? b : c)", op: "+", exp: T_CONDITIONAL_EXPRESSION }, { src: "b+123", op: "+", exp: T_LITERAL }, { src: "b+a(b,c)", op: "+", exp: T_FUNCTION_INVOCATION_EXPRESSION }, { src: "b+(123, 1+c)", op: "+", exp: T_SEQUENCE_EXPRESSION }, ]; binaryRightOperandCases.forEach((c) => { it(`Binary (left operand): ${c.src}`, () => { // --- Arrange const wParser = new Parser(c.src); // --- Act const expr = wParser.parseExpr(); // --- Assert expect(expr).not.equal(null); if (!expr) return; expect(expr.type).equal(T_BINARY_EXPRESSION); const binary = expr as BinaryExpression; expect(binary.op).equal(c.op); expect(binary.right.type).equal(c.exp); }); }); }); ``` -------------------------------------------------------------------------------- /xmlui/src/components/NestedApp/AppWithCodeView.tsx: -------------------------------------------------------------------------------- ```typescript import styles from "./AppWithCodeView.module.scss"; import { createComponentRenderer } from "../../components-core/renderers"; import { parseScssVar } from "../../components-core/theming/themeVars"; import { AppWithCodeViewNative } from "./AppWithCodeViewNative"; import { defaultProps } from "./defaultProps"; import { createMetadata } from "../metadata-helpers"; const COMP = "AppWithCodeView"; export const AppWithCodeViewMd = createMetadata({ status: "stable", description: `The ${COMP} component displays a combination of markdown content and a nested xmlui app. It supports both side-by-side and stacked layouts.`, props: { markdown: { description: "The markdown content to be displayed alongside the app", valueType: "string", }, splitView: { description: "Whether to render the markdown and app side by side or stacked vertically", valueType: "boolean", }, app: { description: "The source markup of the app to be nested", }, api: { description: "This property defines an optional emulated API to be used with the nested app.", }, components: { description: "This property defines an optional list of components to be used with the nested app.", defaultValue: defaultProps.components, }, config: { description: "You can define the nested app's configuration with this property.", }, activeTheme: { description: "This property defines the active theme for the nested app. " + "If not set, the default theme is used.", }, activeTone: { description: "This property defines the active tone for the nested app. " + "If not set, the default tone is used.", }, title: { description: "The optional title of the nested app. If not set, no title is displayed.", }, height: { description: "The height of the nested app. If not set, the height is determined automatically.", }, allowPlaygroundPopup: { description: "This property defines whether the nested app can be opened in the xmlui playground.", valueType: "boolean", defaultValue: defaultProps.allowPlaygroundPopup, }, withFrame: { description: "This property defines whether the nested app should be displayed with a frame.", valueType: "boolean", defaultValue: defaultProps.withFrame, }, }, themeVars: parseScssVar(styles.themeVars), defaultThemeVars: {}, }); export const appWithCodeViewComponentRenderer = createComponentRenderer( COMP, AppWithCodeViewMd, ({ node, extractValue, renderChild }) => { let renderedChildren = ""; // 1. Static content prop fallback if (!renderedChildren) { renderedChildren = extractValue.asString(node.props.markdown); } // 2. "data" property fallback if (!renderedChildren && typeof (node.props as any).data === "string") { renderedChildren = extractValue.asString((node.props as any).data); } // 3. Children fallback if (!renderedChildren) { (node.children ?? []).forEach((child) => { const renderedChild = renderChild(child); console.log("renderedChild", renderedChild); if (typeof renderedChild === "string") { renderedChildren += renderedChild; } }); } return ( <AppWithCodeViewNative markdown={renderedChildren} splitView={extractValue.asOptionalBoolean(node.props?.splitView)} app={node.props?.app} api={extractValue(node.props?.api)} components={extractValue(node.props?.components)} config={extractValue(node.props?.config)} activeTheme={extractValue(node.props?.activeTheme)} activeTone={extractValue(node.props?.activeTone)} title={extractValue(node.props?.title)} height={extractValue(node.props?.height)} allowPlaygroundPopup={extractValue.asOptionalBoolean(node.props?.allowPlaygroundPopup)} withFrame={extractValue.asOptionalBoolean(node.props?.withFrame)} /> ); }, ); ``` -------------------------------------------------------------------------------- /xmlui/src/language-server/services/common/syntax-node-utilities.ts: -------------------------------------------------------------------------------- ```typescript import type { Node, GetText} from "../../../parsers/xmlui-parser"; import { SyntaxKind } from "../../../parsers/xmlui-parser"; export function findTagNameNodeInStack(nodeStack: Node[]): Node{ const elementNode = nodeStack.findLast(n => n.kind === SyntaxKind.ElementNode); if (!elementNode){ return null; } const tagNameNode = elementNode.children!.find(n => n.kind === SyntaxKind.TagNameNode); return tagNameNode; } export function compNameForTagNameNode(tagNameNode: Node, getText: GetText): string | null { const colonIdx = tagNameNode.children!.findIndex((n) => n.kind === SyntaxKind.Colon); const hasNs = colonIdx === -1 ? false : tagNameNode.children!.slice(0, colonIdx).findIndex((n:Node) => n.kind === SyntaxKind.Identifier) !== -1; if(hasNs){ return null; } const nameNode = tagNameNode.children!.findLast(c => c.kind === SyntaxKind.Identifier) return getText(nameNode); } /** * * @param pathToElementNode nodes from the inner most node to the closest ElementNode * @returns */ export function insideClosingTag(pathToElementNode:Node[]): boolean { if(pathToElementNode === null){ return false; } const elementNode = pathToElementNode.at(-1)!; const nodeBeforeElementNode = pathToElementNode.at(-2); const inputWasOnlyAnElementNode = !nodeBeforeElementNode if (inputWasOnlyAnElementNode){ return false; } const idxClosingStartToken = elementNode.children!.findIndex((n) => n.kind === SyntaxKind.CloseNodeStart); if(idxClosingStartToken === -1){ return false; } const idxElementNodeChild = elementNode.children!.findIndex((n) => n === nodeBeforeElementNode) if(idxClosingStartToken <= idxElementNodeChild ){ return true; } // just in case the guard statements missed a case return false; } /** * Visits ancestors in the chain from the innermost to the outermost node. * @param chain The chain of nodes to visit. * @param predicate A function that takes a node and returns a boolean. * @returns An array of nodes from the innermost to the node that satisfies the predicate, or null if no such node is found. */ export function visitAncestorsInChain(chain: Node[], predicate: (node: Node) => boolean): Node[] | null { let currentIdx = -1; let current = chain.at(currentIdx); let path = [current]; while(current) { if(predicate(current)) { return path; } currentIdx--; current = chain.at(currentIdx); path.push(current) } return null; } /** * Finds the first ancestor node of a specific kind in a chain of nodes. * @param chain The chain of nodes to search through. * @param kind The SyntaxKind to look for. * @returns The first ancestor node of the specified kind, or null if not found. */ export function getFirstNodeFromAncestorChain(chain: Node[], kind: SyntaxKind): Node | null { for (let i = chain.length - 1; i >= 0; i--) { if (chain[i].kind === kind) { return chain[i]; } } return null; } /** * Finds all ancestor nodes of a specific kind in a chain of nodes. * @param chain The chain of nodes to search through. * @param kind The SyntaxKind to look for. * @returns An array of ancestor nodes of the specified kind. */ export function getAllRelevantNodesFromAncestorChain(chain: Node[], kind: SyntaxKind): Node[] { const relevantNodes: Node[] = []; for (let i = chain.length - 1; i >= 0; i--) { if (chain[i].kind === kind) { relevantNodes.push(chain[i]); } } return relevantNodes; } /** * * @param node an ElementNode */ function isPairedNode(node: Node): boolean{ for (const c of node.children){ if (c.kind === SyntaxKind.CloseNodeStart){ return true; } else if (c.kind === SyntaxKind.NodeClose){ return false; } } return true; } /** * * @param node an ElementNode */ function isSelfClosingNode(node: Node): boolean{ return !isPairedNode(node); } export function getTriviaNodes(node: Node): Node[] { if (node.pos === node.start){ return []; } else if (node.triviaBefore){ return node.triviaBefore; } else { return getTriviaNodes(node.children[0]); } } ``` -------------------------------------------------------------------------------- /xmlui/src/components/ModalDialog/ConfirmationModalContextProvider.tsx: -------------------------------------------------------------------------------- ```typescript import React, { type ReactNode, useCallback, useContext, useEffect, useMemo, useRef, useState, } from "react"; import type { ButtonVariant, ButtonThemeColor } from "../abstractions"; import { Button } from "../Button/ButtonNative"; import { Stack } from "../Stack/StackNative"; import { Dialog } from "./Dialog"; const ConfirmationModalContext = React.createContext({ confirm: (title: string, message?: string, actionLabel?: string) => Promise.resolve(false), }); export const useConfirm = () => useContext(ConfirmationModalContext); type Props = { children: ReactNode; }; type ConfirmButtonParams = { variant?: ButtonVariant; themeColor?: ButtonThemeColor; label: string; value: any; }; type ConfirmParams = { message?: string; title: string; buttons: Array<ConfirmButtonParams>; }; export const ConfirmationModalContextProvider = ({ children }: Props) => { // State const [showConfirmationModal, setShowConfirmationModal] = useState(false); const [title, setTitle] = useState<string>("Are you sure?"); const [message, setMessage] = useState<string | undefined>(); const [buttons, setButtons] = useState<Array<ConfirmButtonParams>>([]); // Refs and other const resolver = useRef<((confirmationResult: boolean) => void) | null>(null); const buttonsRef = useRef<HTMLDivElement>(null); useEffect(() => { if (showConfirmationModal) { setTimeout(() => { const focusable: NodeListOf<HTMLButtonElement> | undefined = buttonsRef.current?.querySelectorAll("button"); if (focusable) { focusable[focusable.length - 1].focus(); } }, 0); } }, [showConfirmationModal]); const handleShow = useCallback( (title: string | ConfirmParams, message?: string, actionLabel?: string) => { if (typeof title === "string") { setTitle(title); setButtons([ { label: actionLabel || "Ok", value: true, }, ]); setMessage(message); } else { setTitle(title.title); setButtons(title.buttons); setMessage(title.message); } setShowConfirmationModal(true); return new Promise<boolean>(function (resolve) { resolver.current = resolve; }); }, [], ); const handleOk = useCallback((value: any) => { if (resolver.current) { resolver.current(value); } setShowConfirmationModal(false); }, []); const handleCancel = useCallback(() => { if (resolver.current) { resolver.current(false); } setShowConfirmationModal(false); }, []); const contextValue = useMemo(() => { return { confirm: handleShow, }; }, [handleShow]); return ( <ConfirmationModalContext.Provider value={contextValue}> {children} {showConfirmationModal && ( <Dialog title={title} description={message} isOpen={true} onClose={handleCancel} buttons={ <Stack orientation="horizontal" horizontalAlignment="end" style={{ width: "100%", gap: "1em" }} ref={buttonsRef} > <Button variant="ghost" themeColor="secondary" size="sm" onClick={handleCancel}> Cancel </Button> {buttons.length > 1 ? <div style={{ flex: 1 }} /> : undefined} {buttons.map( ({ label, value, variant = "solid", themeColor = "attention" }, index) => { return ( <Button key={index} variant={variant} themeColor={themeColor} size="sm" type="submit" onClick={() => { handleOk(value); }} > {label} </Button> ); }, )} </Stack> } /> )} </ConfirmationModalContext.Provider> ); }; ``` -------------------------------------------------------------------------------- /xmlui/dev-docs/next/error-handling-standardization-summary.md: -------------------------------------------------------------------------------- ```markdown # Error Handling Standardization - Implementation Summary ## Overview Successfully implemented standardized error handling across the XMLUI documentation generation scripts to improve consistency, maintainability, and debugging capabilities. ## Changes Made ### 1. Created New Error Handling Infrastructure #### `error-handling.mjs` - New Utility Module - **`handleFatalError()`** - Standardized fatal error handling with appropriate exit codes - **`handleNonFatalError()`** - Consistent non-fatal error logging - **`validateDependencies()`** - Centralized dependency validation - **`withErrorHandling()`** - Wrapper for async operations with error handling - **`withFileErrorHandling()`** - Specialized file operation error handling - **`createMetadataError()`** - Standardized metadata error creation #### Enhanced `constants.mjs` - Added `ERROR_HANDLING.EXIT_CODES` for consistent exit code usage - Expanded `ERROR_MESSAGES` with comprehensive error message constants ### 2. Updated Scripts to Use Standardized Error Handling #### `create-theme-files.mjs` - **Before**: Used `console.log()` + `process.exit(1)` for errors - **After**: - Wrapped in async function with try-catch - Uses `validateDependencies()` for checking required metadata - Uses `withFileErrorHandling()` for file write operations - Uses `handleFatalError()` for graceful error handling and exit - Uses centralized logger instead of console.log #### `input-handler.mjs` - **Before**: Basic error handling with minimal validation - **After**: - Enhanced error handling with specific error types (ENOENT, SyntaxError) - Uses `ErrorWithSeverity` for consistent error classification - Improved error messages using centralized constants #### `get-docs.mjs` - **Before**: Mixed error handling patterns with try-catch blocks - **After**: - Replaced manual try-catch blocks with `withErrorHandling()` wrapper - Uses `handleNonFatalError()` for non-critical errors - Imports and uses standardized `loadConfig()` from input-handler - Removed duplicate loadConfig function ### 3. Key Benefits Achieved #### Consistency - All scripts now use the same error handling patterns - Standardized error messages and exit codes - Consistent logging approach across all scripts #### Maintainability - Centralized error handling logic in reusable utilities - Easy to modify error behavior in one place - Clear separation between fatal and non-fatal errors #### Debugging - Better error context with operation names - Structured error information - Appropriate exit codes for different error types #### Robustness - Proper dependency validation - Enhanced file operation error handling - Graceful error recovery where appropriate ## Usage Examples ### Fatal Error Handling ```javascript // Before console.log("Error occurred"); process.exit(1); // After handleFatalError(error, ERROR_HANDLING.EXIT_CODES.METADATA_ERROR, "theme generation"); ``` ### Async Operation Wrapping ```javascript // Before try { await someOperation(); } catch (error) { logger.error("Failed:", error); } // After await withErrorHandling( () => someOperation(), "operation description", ERROR_HANDLING.EXIT_CODES.GENERAL_ERROR ); ``` ### Dependency Validation ```javascript // Before if (!data) { console.log("Missing data"); process.exit(1); } // After validateDependencies({ DATA: data, CONFIG: config }); ``` ## Files Modified - ✅ `scripts/generate-docs/error-handling.mjs` (new) - ✅ `scripts/generate-docs/constants.mjs` (enhanced) - ✅ `scripts/generate-docs/create-theme-files.mjs` (refactored) - ✅ `scripts/generate-docs/input-handler.mjs` (enhanced) - ✅ `scripts/generate-docs/get-docs.mjs` (standardized) ## Next Steps This standardization provides a solid foundation for: 1. Applying similar error handling patterns to remaining scripts 2. Adding more sophisticated error recovery mechanisms 3. Implementing structured logging and monitoring 4. Adding automated error reporting and debugging tools The error handling infrastructure is now consistent, maintainable, and provides better debugging capabilities across the documentation generation pipeline. ``` -------------------------------------------------------------------------------- /xmlui/src/components/NavGroup/NavGroup.md: -------------------------------------------------------------------------------- ```markdown %-DESC-START **Key features:** - **Hierarchical organization**: Creates nested menu structures by containing NavLinks and other NavGroups - **Expand/collapse behavior**: Users can toggle visibility of grouped navigation items - **Customizable icons**: Different icons for expanded/collapsed states and layout orientations - **Flexible placement**: Works within NavPanel for app navigation or standalone for custom menus - **Initial state control**: Configure whether groups start expanded or collapsed ## Using `NavGroup` The primary use of a `NavGroup` is to create an application menu with submenus, as the following example shows: ```xmlui-pg copy display name="Example: NavGroup in App" height="280px" ---app <App layout="condensed"> <NavPanel> <NavLink label="Home" to="/" icon="home"/> <NavGroup label="Pages"> <NavLink label="Page 1" to="/page/1"/> <NavGroup label="Page 2-4"> <NavLink label="Page 2" to="/page/2"/> <NavLink label="Page 3" to="/page/3"/> <NavLink label="Page 4" to="/page/4"/> </NavGroup> <NavLink label="Page 5" to="/page/5"/> <NavLink label="Page Other" to="/page/Other"/> </NavGroup> </NavPanel> <Pages fallbackPath="/"> <Page url="/"> Home </Page> <Page url="/page/:id"> <Text value="Page {$routeParams.id}" /> </Page> </Pages> </App> ---desc Here, the highlighted `NavGroup` element nests other `NavLink` and `NavGroup` elements to create a hierarchical menu: ``` You do not have to use `NavGroup` within `NavPanel`; you can nest it into other components to represent a menu, like in the following example: ```xmlui-pg copy display name="Example: NavGroup in a Stack" height="280px" <App> <HStack verticalAlignment="center"> <Text>Use this menu:</Text> <NavGroup label="Pages"> <NavLink label="Page 1" /> <NavGroup label="Page 2-4"> <NavLink label="Page 2" /> <NavLink label="Page 3" /> <NavLink label="Page 4" /> </NavGroup> <NavLink label="Page 5" /> <NavLink label="Page Other" /> </NavGroup> </HStack> </App> ``` ### Custom Icons You can also provide custom icons for a specific NavGroup component via it's respective property: - [iconHorizontalCollapsed](#iconHorizontalCollapsed) - [iconHorizontalExpanded](#iconHorizontalExpanded) - [iconVerticalCollapsed](#iconVerticalCollapsed) - [iconVerticalExpanded](#iconVerticalExpanded) See the following for an example of all variants: ```xmlui-pg copy display name="Example: custom icons in horizontal layout" height="250px" <App layout="horizontal"> <NavGroup icon="email" label="Send To" iconVerticalExpanded="arrowup" iconVerticalCollapsed="arrowbottom"> <NavLink icon="arrowup" label="Boss" /> <NavGroup icon="users" label="Team" iconHorizontalExpanded="arrowleft" iconHorizontalCollapsed="arrowright"> <NavLink label="Jane" /> <NavLink label="Will" /> </NavGroup> <NavLink icon="cube" label="Support" /> </NavGroup> </App> ``` %-DESC-END %-PROP-START icon Look at this example: ```xmlui-pg copy {3, 5} display name="Example: label and icon" height="280px" <App> <HStack verticalAlignment="center"> <NavGroup icon="email" label="Send To" > <NavLink icon="arrowup" label="Boss" /> <NavGroup icon="users" label="Team"> <NavLink label="Jane" /> <NavLink label="Will" /> <NavLink label="Sandra" /> </NavGroup> <NavLink icon="cube" label="Support" /> </NavGroup> </HStack> </App> ``` %-PROP-END %-PROP-START iconHorizontalCollapsed For an example, see the [Custom Icons section](#custom-icons). %-PROP-END %-PROP-START iconHorizontalExpanded For an example, see the [Custom Icons section](#custom-icons). %-PROP-END %-PROP-START iconVerticalCollapsed For an example, see the [Custom Icons section](#custom-icons). %-PROP-END %-PROP-START iconVerticalExpanded For an example, see the [Custom Icons section](#custom-icons). %-PROP-END %-PROP-START label This property sets the text displayed as the name of the `NavGroup`. For an example, see the [section on the icon property](#icon). %-PROP-END ``` -------------------------------------------------------------------------------- /xmlui/tests/parsers/style-parser/parser.test.ts: -------------------------------------------------------------------------------- ```typescript import { describe, expect, it, assert } from "vitest"; import { StyleParser } from "../../../src/parsers/style-parser/StyleParser"; describe("Style parser", () => { const sizeCases = [ { src: "0", val: 0, unit: "" }, { src: "0px", val: 0, unit: "px" }, { src: "0cm", val: 0, unit: "cm" }, { src: "0mm", val: 0, unit: "mm" }, { src: "0in", val: 0, unit: "in" }, { src: "0pt", val: 0, unit: "pt" }, { src: "0pc", val: 0, unit: "pc" }, { src: "0em", val: 0, unit: "em" }, { src: "0rem", val: 0, unit: "rem" }, { src: "0vw", val: 0, unit: "vw" }, { src: "0vh", val: 0, unit: "vh" }, { src: "0ex", val: 0, unit: "ex" }, { src: "0ch", val: 0, unit: "ch" }, { src: "0vmin", val: 0, unit: "vmin" }, { src: "0vmax", val: 0, unit: "vmax" }, { src: "0%", val: 0, unit: "%" }, { src: "123px", val: 123, unit: "px" }, { src: "123cm", val: 123, unit: "cm" }, { src: "123.5mm", val: 123.5, unit: "mm" }, { src: "123.5in", val: 123.5, unit: "in" }, { src: "234.5pt", val: 234.5, unit: "pt" }, { src: "-12.5pc", val: -12.5, unit: "pc" }, { src: "-123.5em", val: -123.5, unit: "em" }, { src: "45rem", val: 45, unit: "rem" }, { src: "55vw", val: 55, unit: "vw" }, { src: "678vh", val: 678, unit: "vh" }, { src: "1ex", val: 1, unit: "ex" }, { src: "3ch", val: 3, unit: "ch" }, { src: "4vmin", val: 4, unit: "vmin" }, { src: "-12.5vmax", val: -12.5, unit: "vmax" }, { src: "66%", val: 66, unit: "%" }, ]; sizeCases.forEach((s) => it(`parseSize '${s.src}'`, () => { // --- Arrange const sp = new StyleParser(s.src); // --- Act const size = sp.parseSize()!; // --- Assert expect(size.value).equal(s.val); expect(size.unit).equal(s.unit); }) ); const sizeErrorCases = ["12left", "12wavy"]; sizeErrorCases.forEach((s) => it(`parseSize '${s}'`, () => { // --- Arrange const sp = new StyleParser(s); // --- Act try { sp.parseSize(); } catch (err: any) { expect(err.toString()).contains("Unexpected"); return; } assert.fail("Exception expected"); }) ); const borderCases = [ { src: "0", wv: 0, wu: "", cv: undefined, sv: undefined }, { src: "1px", wv: 1, wu: "px", cv: undefined, sv: undefined }, { src: "dotted", wv: undefined, wu: undefined, cv: undefined, sv: "dotted" }, { src: "blue", wv: undefined, wu: undefined, cv: "blue", sv: undefined }, { src: "1px dotted", wv: 1, wu: "px", cv: undefined, sv: "dotted" }, { src: "dotted 1px", wv: 1, wu: "px", cv: undefined, sv: "dotted" }, { src: "1px blue", wv: 1, wu: "px", cv: "blue", sv: undefined }, { src: "blue 1px", wv: 1, wu: "px", cv: "blue", sv: undefined }, { src: "dotted blue", wv: undefined, wu: undefined, cv: "blue", sv: "dotted" }, { src: "blue dotted", wv: undefined, wu: undefined, cv: "blue", sv: "dotted" }, { src: "1px dotted blue", wv: 1, wu: "px", cv: "blue", sv: "dotted" }, { src: "1px blue dotted", wv: 1, wu: "px", cv: "blue", sv: "dotted" }, { src: "dotted 1px blue", wv: 1, wu: "px", cv: "blue", sv: "dotted" }, { src: "dotted blue 1px", wv: 1, wu: "px", cv: "blue", sv: "dotted" }, { src: "blue dotted 1px", wv: 1, wu: "px", cv: "blue", sv: "dotted" }, { src: "blue 1px dotted", wv: 1, wu: "px", cv: "blue", sv: "dotted" }, ]; borderCases.forEach((c) => it(`parseBorder ${c.src}`, () => { // --- Arrange const sp = new StyleParser(c.src); // --- Act const b = sp.parseBorder()!; // --- Assert expect(b.widthValue).equal(c.wv); expect(b.widthUnit).equal(c.wu); expect(b.styleValue).equal(c.sv); expect(b.color).equal(c.cv); }) ); const borderErrorCases = ["start"]; borderErrorCases.forEach((s) => it(`parseBorder error '${s}'`, () => { // --- Arrange const sp = new StyleParser(s); // --- Act try { sp.parseBorder(); } catch (err: any) { expect(err.toString()).contains("Unexpected"); return; } assert.fail("Exception expected"); }) ); }); ``` -------------------------------------------------------------------------------- /xmlui/tests/components-core/scripts-runner/eval-tree-arrow-async.test.ts: -------------------------------------------------------------------------------- ```typescript import { describe, expect, it } from "vitest"; import { evalBindingAsync } from "../../../src/components-core/script-runner/eval-tree-async"; import { Parser } from "../../../src/parsers/scripting/Parser"; import {createEvalContext} from "./test-helpers"; describe("Evaluate arrow expressions (epx)", () => { it("Arrow #1", async () => { // --- Arrange const source = "(x => 2 * x)(4)"; const context = createEvalContext({}); // --- Act const value = await evalAsync(source, context); // --- Arrange expect(value).equal(8); }); it("Arrow #2", async () => { // --- Arrange const source = "((x, y) => x + y)(1, 2)"; const context = createEvalContext({}); // --- Act const value = await evalAsync(source, context); // --- Arrange expect(value).equal(3); }); it("Arrow #3", async () => { // --- Arrange const source = "((x, y) => { return x + y })(1, 2)"; const context = createEvalContext({}); // --- Act const value = await evalAsync(source, context); // --- Arrange expect(value).equal(3); }); it("Arrow #4", async () => { // --- Arrange const source = "(x => (++x.h))(count)"; const context = createEvalContext({ localContext: { count: { h: 3 } } }); // --- Act const value = await evalAsync(source, context); // --- Arrange expect(value).equal(4); }); it("Arrow #5", async () => { // --- Arrange const source = "(x => x += 2)(count)"; const context = createEvalContext({ localContext: { count: 3 } }); // --- Act const value = await evalAsync(source, context); // --- Arrange expect(value).equal(5); }); it("Arrow #6", async () => { // --- Arrange const source = "(x => x += 2)(count + 4)"; const context = createEvalContext({ localContext: { count: 3 } }); // --- Act const value = await evalAsync(source, context); // --- Arrange expect(value).equal(9); }); it("Arrow #7", async () => { // --- Arrange const source = "[1,2,3,4,5].filter(x => x % 2 === 0)[1]"; const context = createEvalContext({ localContext: { count: 3 } }); // --- Act const value = await evalAsync(source, context); // --- Arrange expect(value).equal(4); }); it("Arrow #8", async () => { // --- Arrange const source = "containsArray.array.filter(item => item % 2 === 0)[1]"; const context = createEvalContext({ localContext: { containsArray: { array: [5, 4, 3, 2, 1] } } }); // --- Act const value = await evalAsync(source, context); // --- Arrange expect(value).equal(2); }); it("Arrow #9", async () => { // --- Arrange const source = "array.reduce((acc, item) => 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("Arrow with rest #1", async () => { // --- Arrange const source = "((...a) => a[0] + a[1])(1, 2)"; const context = createEvalContext({}); // --- Act const value = await evalAsync(source, context); // --- Arrange expect(value).equal(3); }); it("Arrow with rest #2", async () => { // --- Arrange const source = "((x, ...a) => x + a[0] + a[1])(1, 2, 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); } ``` -------------------------------------------------------------------------------- /tools/create-xmlui-hello-world/index.js: -------------------------------------------------------------------------------- ```javascript #!/usr/bin/env node const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process'); console.log('Creating minimal XMLUI test app for HelloWorld component...\n'); // Get project name from command line or use default const projectName = process.argv[2] || 'test-hello-world'; const projectPath = path.resolve(process.argv[2] ? process.argv[2] : path.join(process.env.HOME || process.env.USERPROFILE, projectName)); // Create project directory if (!fs.existsSync(projectPath)) { fs.mkdirSync(projectPath, { recursive: true }); fs.mkdirSync(path.join(projectPath, 'xmlui'), { recursive: true }); } else { console.log(`Directory ${projectPath} already exists!`); process.exit(1); } // Check if we need to build the standalone XMLUI engine const xmluiStandalonePath = path.join(__dirname, '../../xmlui/dist/standalone/xmlui-standalone.umd.js'); const docsStandalonePath = path.join(__dirname, '../../docs/public/resources/files/for-download/xmlui/xmlui-standalone.umd.js'); let standaloneSource = null; if (fs.existsSync(docsStandalonePath)) { standaloneSource = docsStandalonePath; console.log('Using existing XMLUI standalone from docs...'); } else if (fs.existsSync(xmluiStandalonePath)) { standaloneSource = xmluiStandalonePath; console.log('Using existing XMLUI standalone from build...'); } else { console.log('Building XMLUI standalone engine...'); try { execSync('npm run build:xmlui-standalone', { cwd: path.join(__dirname, '../../xmlui'), stdio: 'inherit' }); standaloneSource = xmluiStandalonePath; console.log('XMLUI standalone built successfully!'); } catch (error) { console.log('Failed to build XMLUI standalone. Please run: cd xmlui && npm run build:xmlui-standalone'); process.exit(1); } } // Copy the standalone XMLUI engine const xmluiLatestPath = path.join(projectPath, 'xmlui/xmlui-latest.js'); fs.copyFileSync(standaloneSource, xmluiLatestPath); console.log('Copied XMLUI engine to xmlui/xmlui-latest.js'); // Check if HelloWorld extension exists and copy it const helloWorldSource = path.join(__dirname, '../../packages/xmlui-hello-world/dist/xmlui-hello-world.js'); const xmluiHelloWorldPath = path.join(projectPath, 'xmlui/xmlui-hello-world.js'); if (fs.existsSync(helloWorldSource)) { fs.copyFileSync(helloWorldSource, xmluiHelloWorldPath); console.log('Copied HelloWorld extension to xmlui/xmlui-hello-world.js'); } else { console.log('HelloWorld extension not found. You may need to build it first:'); console.log(' cd packages/xmlui-hello-world && npm run build:extension'); // Create a placeholder file fs.writeFileSync(xmluiHelloWorldPath, '// HelloWorld extension not found - please build it first\n'); } // Main.xmlui const mainXmlui = `<App xmlns:Extensions="component-ns:XMLUIExtensions"> <VStack gap="2rem" padding="2rem"> <Heading>HelloWorld Component Test</Heading> <Extensions:HelloWorld message="Hello from XMLUI!" /> </VStack> </App>`; // index.html const indexHtml = `<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>HelloWorld Extension Test</title> <script src="xmlui/xmlui-latest.js"></script> <script src="xmlui/xmlui-hello-world.js"></script> </head> <body> </body> </html>`; // Write files fs.writeFileSync(path.join(projectPath, 'Main.xmlui'), mainXmlui); fs.writeFileSync(path.join(projectPath, 'index.html'), indexHtml); console.log(`\n Created minimal XMLUI test app at: ${projectPath}`); console.log(` Project structure:`); console.log(` ${path.basename(projectPath)}/`); console.log(` ├── Main.xmlui`); console.log(` ├── index.html`); console.log(` └── xmlui/`); console.log(` ├── xmlui-latest.js`); console.log(` └── xmlui-hello-world.js`); console.log(`\n To run the test app:`); console.log(` cd ${projectPath}`); console.log(` python3 -m http.server`); console.log(` # Or`); console.log(` npx serve`); if (!fs.existsSync(helloWorldSource)) { console.log(`\n Note: HelloWorld extension needs to be built first. Run:`); console.log(` cd packages/xmlui-hello-world && npm run build:extension`); } ``` -------------------------------------------------------------------------------- /xmlui/src/components/Stack/CVStack.spec.ts: -------------------------------------------------------------------------------- ```typescript import { test, expect } from "../../testing/fixtures"; // ============================================================================= // BASIC FUNCTIONALITY TESTS // ============================================================================= test.describe("Basic Functionality", () => { test("renders items vertically and centers them", async ({ initTestBed, page }) => { await initTestBed(` <CVStack testId="cvstack" width="200px" height="200px" backgroundColor="lightgray"> <Stack testId="item1" height="32px" width="32px" backgroundColor="red" /> <Stack testId="item2" height="32px" width="32px" backgroundColor="blue" /> <Stack testId="item3" height="32px" width="32px" backgroundColor="green" /> </CVStack> `); const cvstack = page.getByTestId("cvstack"); const item1 = page.getByTestId("item1"); const item2 = page.getByTestId("item2"); const item3 = page.getByTestId("item3"); await expect(cvstack).toBeVisible(); await expect(item1).toBeVisible(); await expect(item2).toBeVisible(); await expect(item3).toBeVisible(); // Get bounding boxes to verify vertical layout and centering const cvstackBox = await cvstack.boundingBox(); const item1Box = await item1.boundingBox(); const item2Box = await item2.boundingBox(); const item3Box = await item3.boundingBox(); // Verify items are stacked vertically (item2 should be below item1, item3 below item2) expect(item2Box!.y).toBeGreaterThan(item1Box!.y + item1Box!.height - 1); // -1 for floating point tolerance expect(item3Box!.y).toBeGreaterThan(item2Box!.y + item2Box!.height - 1); // -1 for floating point tolerance // Verify items are horizontally centered within the container const cvstackCenterX = cvstackBox!.x + cvstackBox!.width / 2; const item1CenterX = item1Box!.x + item1Box!.width / 2; const item2CenterX = item2Box!.x + item2Box!.width / 2; const item3CenterX = item3Box!.x + item3Box!.width / 2; expect(Math.abs(item1CenterX - cvstackCenterX)).toBeLessThan(1); expect(Math.abs(item2CenterX - cvstackCenterX)).toBeLessThan(1); expect(Math.abs(item3CenterX - cvstackCenterX)).toBeLessThan(1); // Verify the entire content is vertically centered within the container const allItemsTopY = item1Box!.y; const allItemsBottomY = item3Box!.y + item3Box!.height; const allItemsHeight = allItemsBottomY - allItemsTopY; const cvstackCenterY = cvstackBox!.y + cvstackBox!.height / 2; const contentCenterY = allItemsTopY + allItemsHeight / 2; expect(Math.abs(contentCenterY - cvstackCenterY)).toBeLessThan(1); }); test("renders empty CVStack", async ({ initTestBed, page }) => { await initTestBed(`<CVStack testId="cvstack"></CVStack>`); const cvstack = page.getByTestId("cvstack"); await expect(cvstack).toBeAttached(); await expect(cvstack).toBeEmpty(); }); test("ignores orientation property", async ({ initTestBed, page }) => { await initTestBed(` <CVStack testId="cvstack" orientation="horizontal" width="200px" height="100px" backgroundColor="lightgray"> <Stack testId="item1" height="32px" width="32px" backgroundColor="red" /> <Stack testId="item2" height="32px" width="32px" backgroundColor="blue" /> </CVStack> `); const cvstack = page.getByTestId("cvstack"); const item1 = page.getByTestId("item1"); const item2 = page.getByTestId("item2"); await expect(item1).toBeVisible(); await expect(item2).toBeVisible(); // Get bounding boxes to verify still renders vertically and centered despite orientation="horizontal" const cvstackBox = await cvstack.boundingBox(); const item1Box = await item1.boundingBox(); const item2Box = await item2.boundingBox(); // Verify items are still stacked vertically (orientation prop should be ignored) expect(item2Box!.y).toBeGreaterThan(item1Box!.y + item1Box!.height - 1); // Verify items are still horizontally centered const cvstackCenterX = cvstackBox!.x + cvstackBox!.width / 2; const item1CenterX = item1Box!.x + item1Box!.width / 2; expect(Math.abs(item1CenterX - cvstackCenterX)).toBeLessThan(1); }); }); ``` -------------------------------------------------------------------------------- /xmlui/src/index.scss: -------------------------------------------------------------------------------- ```scss @use "./components-core/theming/themes.scss" as t; /* 1. Declare the order of all layers in your application. 'dynamic' is last, so it will override all other layers. IF YOU ARE CHANGING THE ORDER, MAKE SURE TO UPDATE THE ORDER IN NestedApp, too. */ @layer reset, base, components, dynamic; @layer base { /************************/ /* Normalization */ /************************/ /* Normal box-sizing model */ *, *::before, *::after { box-sizing: border-box; } /* Remove default margin */ * { margin: 0; } /* Allow percentage-based heights in the application */ html, body { height: 100%; } #root{ min-height: 100%; height: 100%; } html { -webkit-text-size-adjust: 100%; tab-size: 4; line-height: 1.7; } body { line-height: inherit; margin: 0; } /* Create a root stacking context */ #root, #__next { isolation: isolate; } //tailwind css preflight *, :before, :after { box-sizing: border-box; border: 0 solid #e5e7eb } hr { color: inherit; border-top-width: 1px; height: 0 } abbr:where([title]) { -webkit-text-decoration: underline dotted; text-decoration: underline dotted } h1, h2, h3, h4, h5, h6 { // font-size: inherit; font-weight: inherit } a { color: inherit; -webkit-text-decoration: inherit; text-decoration: inherit } b, strong { font-weight: bolder } code, kbd, samp, pre { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace; font-size: 1em } small { font-size: 80% } sub, sup { vertical-align: baseline; font-size: 75%; line-height: 0; position: relative } sub { bottom: -.25em } sup { top: -.5em } table { text-indent: 0; border-color: inherit; border-collapse: collapse } button, input, optgroup, select, textarea { font-feature-settings: inherit; font-variation-settings: inherit; font-family: inherit; font-size: 100%; font-weight: inherit; line-height: inherit; color: inherit; margin: 0; padding: 0 } button, select { text-transform: none } button { -webkit-appearance: button; background-color: transparent; background-image: none } [type=button] { -webkit-appearance: button; background-color: transparent; background-image: none } [type=reset] { -webkit-appearance: button; background-color: transparent; background-image: none } [type=submit] { -webkit-appearance: button; background-color: transparent; background-image: none } :-moz-focusring { outline: auto } :-moz-ui-invalid { box-shadow: none } *:focus-visible { outline: t.$focus-outline; outline-offset: t.$outlineOffset--focus; } progress { vertical-align: baseline } ::-webkit-inner-spin-button { height: auto } ::-webkit-outer-spin-button { height: auto } [type=search] { -webkit-appearance: textfield; outline-offset: -2px } ::-webkit-search-decoration { -webkit-appearance: none } ::-webkit-file-upload-button { -webkit-appearance: button; font: inherit } summary { display: list-item } blockquote, dl, dd, h1, h2, h3, h4, h5, h6, hr, figure, p, pre { margin: 0 } fieldset { margin: 0; padding: 0 } legend { padding: 0 } // ol, ul, menu { // margin: 0; // padding: 0; // list-style: none // } dialog { padding: 0 } textarea { resize: vertical } input::-ms-input-placeholder { opacity: 1; color: #9ca3af } input::placeholder { opacity: 1; color: #9ca3af } textarea::-ms-input-placeholder { opacity: 1; color: #9ca3af } textarea::placeholder { opacity: 1; color: #9ca3af } button, [role=button] { cursor: pointer } :disabled { cursor: default } img, svg, video, canvas, audio, iframe, embed, object { vertical-align: middle; display: block } img, video { max-width: 100%; height: auto } [hidden] { display: none } } ``` -------------------------------------------------------------------------------- /xmlui/tests/components-core/scripts-runner/eval-tree-func-decl.test.ts: -------------------------------------------------------------------------------- ```typescript import { describe, expect, it } from "vitest"; import { evalBindingExpression } from "../../../src/components-core/script-runner/eval-tree-sync"; import {createEvalContext} from "./test-helpers"; describe("Evaluate function expressions (exp)", () => { it("Function decl #1", () => { // --- Arrange const source = "(function (x) {return 2 * x})(4)"; const context = createEvalContext({}); // --- Act const value = evalBindingExpression(source, context); // --- Arrange expect(value).equal(8); }); it("Function decl #2", () => { // --- Arrange const source = "(function (x, y) {return x + y})(1, 2)"; const context = createEvalContext({}); // --- Act const value = evalBindingExpression(source, context); // --- Arrange expect(value).equal(3); }); it("Function decl #3", () => { // --- Arrange const source = "(function myFunc(x, y) { return x + y })(1, 2)"; const context = createEvalContext({}); // --- Act const value = evalBindingExpression(source, context); // --- Arrange expect(value).equal(3); }); it("Function decl #4", () => { // --- Arrange const source = "(function (x) { return (++x.h) })(count)"; const context = createEvalContext({ localContext: { count: { h: 3 } } }); // --- Act const value = evalBindingExpression(source, context); // --- Arrange expect(value).equal(4); }); it("Function decl #5", () => { // --- Arrange const source = "(function (x) { return x += 2; })(count)"; const context = createEvalContext({ localContext: { count: 3 } }); // --- Act const value = evalBindingExpression(source, context); // --- Arrange expect(value).equal(5); }); it("Function decl #6", () => { // --- Arrange const source = "(function (x) { return x += 2 })(count + 4)"; const context = createEvalContext({ localContext: { count: 3 } }); // --- Act const value = evalBindingExpression(source, context); // --- Arrange expect(value).equal(9); }); it("Function decl #7", () => { // --- 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 = evalBindingExpression(source, context); // --- Arrange expect(value).equal(4); }); it("Function decl #8", () => { // --- 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 = evalBindingExpression(source, context); // --- Arrange expect(value).equal(2); }); it("Function decl #9", () => { // --- 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 = evalBindingExpression(source, context); // --- Arrange expect(value).equal(15); }); it("Function decl with rest #1", () => { // --- Arrange const source = "(function (...a) { return a[0] + a[1] })(1, 2)"; const context = createEvalContext({}); // --- Act const value = evalBindingExpression(source, context); // --- Arrange expect(value).equal(3); }); it("Function decl with rest #2", () => { // --- Arrange const source = "(function (x, ...a) { return x + a[0] + a[1] })(1, 2, 3)"; const context = createEvalContext({}); // --- Act const value = evalBindingExpression(source, context); // --- Arrange expect(value).equal(6); }); it("Function decl reccursive #1", () => { // --- Arrange const source = "(function factorial(n) { return n <= 0 ? 1 : n * factorial(n - 1)})(3)"; const context = createEvalContext({}); // --- Act const value = evalBindingExpression(source, context); // --- Arrange expect(value).equal(6); }); }); ```