This is page 15 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/scripts/generate-docs/logger.mjs: -------------------------------------------------------------------------------- ``` /** * Logger class. * - severity indicates message importance * - levels control what messages will be logged */ class Logger { // TODO: make class a singleton static severity = { info: "info", warning: "warning", error: "error", }; constructor(...levels) { this.setLevels(...levels); } isValidSeverity(severity) { return Object.keys(Logger.severity).includes(severity); } isValidLevel(level) { return Object.keys(LOGGER_LEVELS).includes(level); } defaultSeverity = Logger.severity.error; defaultLogLevel = LOGGER_LEVELS.all; setLevels(...levels) { levels = Array.from(new Set(levels)); let validLevels = levels.filter((level) => this.isValidLevel(level)); if (validLevels.length === 0) { this._logError(`No valid log levels provided. Using defaults: ${this.defaultLogLevel}.`); validLevels = [this.defaultLogLevel]; } this.info = this._noop; this.warning = this._noop; this.error = this._noop; if (validLevels.find((level) => level === LOGGER_LEVELS.none)) { return; } if (validLevels.find((level) => level === LOGGER_LEVELS.all)) { this.info = this._logInfo; this.warning = this._logWarning; this.error = this._logError; return; } for (const level of validLevels) { this[level] = this[`_log${level.charAt(0).toUpperCase() + level.slice(1)}`]; } } log(severity = Logger.severity.info, ...args) { if (!this.isValidSeverity(severity)) { this.warning( `Invalid log severity: ${severity}. Defaulting to message severity: ${this.defaultSeverity}.`, ); severity = this.defaultSeverity; } if (severity === Logger.severity.info) { this.info(...args); } else if (severity === Logger.severity.warning) { this.warning(...args); } else if (severity === Logger.severity.error) { this.error(...args); } } info(...args) {} warning(...args) {} warn(...args) { // Alias for warning() for consistency this.warning(...args); } error(...args) {} _logInfo(...args) { console.log("[INFO]", ...args); } _logWarning(...args) { console.log("[WARN]", ...args); } _logError(...args) { if (args[0] instanceof Error) { console.error("[ERR]", args[0].message + "\n", args[0].stack.split("\n").slice(1).join("\n ")); } else { console.error("[ERR]", ...args); } } _noop(...args) {} } export const LOGGER_LEVELS = { ...Logger.severity, all: "all", none: "none", }; // --- Usable logger instance export const logger = new Logger(LOGGER_LEVELS.all); // --- Error classes export class ErrorWithSeverity extends Error { constructor(message, severity = Logger.severity.error) { super(message); this.name = "ErrorWithSeverity"; this.severity = severity; } } /** * Logs error to console depending on the type of the error thrown. * - ErrorWithSeverity type errors are logged with the severity specified. * - Other errors are logged with severity ERROR. * @param {ErrorWithSeverity | Error | string} error */ export function processError(error) { if (error instanceof ErrorWithSeverity) { // We log the stack trace only for errors with severity ERROR error.severity === Logger.severity.error ? logger.log(error.severity, error) : logger.log(error.severity, error.message); } else if (error instanceof Error) { logger.error(error); } else { logger.error(error); } } ``` -------------------------------------------------------------------------------- /xmlui/src/components/NavLink/NavLinkNative.tsx: -------------------------------------------------------------------------------- ```typescript import type { CSSProperties, MouseEventHandler, ReactNode, Ref } from "react"; import type React from "react"; import { forwardRef, useContext, useMemo } from "react"; import { NavLink as RrdNavLink } from "@remix-run/react"; import type { To } from "react-router-dom"; import classnames from "classnames"; import styles from "./NavLink.module.scss"; import type { LinkAria, LinkTarget } from "../abstractions"; import { createUrlWithQueryParams } from "../component-utils"; import { getAppLayoutOrientation } from "../App/AppNative"; import { useAppLayoutContext } from "../App/AppLayoutContext"; import { NavPanelContext } from "../NavPanel/NavPanelNative"; import { NavGroupContext } from "../NavGroup/NavGroupContext"; // Default props for NavLink component export const defaultProps = { active: false, displayActive: true, }; type Props = { uid?: string; to?: string; target?: LinkTarget; disabled?: boolean; children?: ReactNode; displayActive?: boolean; forceActive?: boolean; vertical?: boolean; style?: CSSProperties; className?: string; onClick?: MouseEventHandler; icon?: React.ReactNode; accessibilityProps?: any; } & Pick<React.HTMLAttributes<HTMLAnchorElement>, LinkAria>; export const NavLink = forwardRef(function NavLink( { /* eslint-disable react/prop-types */ uid, children, disabled, to, displayActive = defaultProps.displayActive, vertical, style, onClick, icon, forceActive, className, ...rest }: Props, ref: Ref<any>, ) { const appLayoutContext = useAppLayoutContext(); const layoutIsVertical = !!appLayoutContext && getAppLayoutOrientation(appLayoutContext.layout).includes("vertical"); const navPanelContext = useContext(NavPanelContext); const inDrawer = navPanelContext?.inDrawer; const { level } = useContext(NavGroupContext); let safeVertical = vertical; if (safeVertical === undefined) { safeVertical = layoutIsVertical || inDrawer; } const smartTo = useMemo(() => { if (to) { return createUrlWithQueryParams(to) as To; } }, [to]) as To; const styleObj = useMemo(() => { return { "--nav-link-level": layoutIsVertical ? level + 1 : 0, ...style, }; }, [level, style, layoutIsVertical]); const baseClasses = classnames(styles.content, styles.base, className, { [styles.disabled]: disabled, [styles.vertical]: safeVertical, [styles.includeHoverIndicator]: displayActive, [styles.navItemActive]: displayActive && forceActive, }); let innerContent = ( <div className={styles.innerContent}> {icon} {children} </div> ); let content: React.ReactNode = null; if (disabled || !smartTo) { content = ( <button {...rest} ref={ref} onClick={onClick} className={baseClasses} style={styleObj} disabled={disabled} > {innerContent} </button> ); } else { content = ( <RrdNavLink {...rest} id={uid} ref={ref} to={smartTo as To} style={styleObj} onClick={onClick} className={({ isActive }) => classnames(baseClasses, { [styles.displayActive]: displayActive, [styles.navItemActive]: displayActive && (isActive || forceActive), "xmlui-navlink-active": isActive || forceActive, }) } > {innerContent} </RrdNavLink> ); } return content; }); ``` -------------------------------------------------------------------------------- /xmlui/src/parsers/scripting/code-behind-collect.ts: -------------------------------------------------------------------------------- ```typescript import { T_ARROW_EXPRESSION, T_FUNCTION_DECLARATION, T_VAR_STATEMENT, type ArrowExpression, type CodeDeclaration, type CollectedDeclarations, type Expression, type FunctionDeclaration, type Statement, } from "../../components-core/script-runner/ScriptingSourceTree"; import type { VisitorState } from "./tree-visitor"; import { visitNode } from "./tree-visitor"; import { isModuleErrors, parseScriptModule } from "./modules"; export const PARSED_MARK_PROP = "__PARSED__"; // --- Collect module statements from a parsed module export function collectCodeBehindFromSource( moduleName: string, source: string ): CollectedDeclarations { const result: CollectedDeclarations = { vars: {}, moduleErrors: {}, functions: {}, }; const collectedFunctions: Record<string, CodeDeclaration> = {}; // --- Parse the module (recursively, including imported modules) in restrictive mode const parsedModule = parseScriptModule(moduleName, source); if (isModuleErrors(parsedModule)) { return { ...result, moduleErrors: parsedModule }; } // --- Collect statements from the module parsedModule.statements.forEach((stmt) => { switch (stmt.type) { case T_VAR_STATEMENT: stmt.decls.forEach((decl) => { if (decl.id.name in result.vars) { throw new Error(`Duplicated var declaration: '${decl.id.name}'`); } result.vars[decl.id.name] = { [PARSED_MARK_PROP]: true, tree: decl.expr, }; }); break; case T_FUNCTION_DECLARATION: addFunctionDeclaration(stmt); break; default: throw new Error(`Only reactive variable and function definitions are allowed in a code-behind module.`); } }); return result; // --- Collect function declaration data function addFunctionDeclaration(stmt: FunctionDeclaration): void { if (collectedFunctions?.[stmt.id.name] !== undefined) { return; } if (stmt.id.name in result.functions) { throw new Error(`Duplicated function declaration: '${stmt.id.name}'`); } const arrow: ArrowExpression = { type: T_ARROW_EXPRESSION, args: stmt.args.slice(), statement: stmt.stmt, // closureContext: obtainClosures({ // childThreads: [], // blocks: [{ vars: {} }], // loops: [], // breakLabelValue: -1, // }), } as ArrowExpression; collectedFunctions[stmt.id.name] = { [PARSED_MARK_PROP]: true, tree: arrow, }; result.functions[stmt.id.name] = { [PARSED_MARK_PROP]: true, tree: arrow, }; } } // --- Remove all code-behind tokens from the tree export function removeCodeBehindTokensFromTree(declarations: CollectedDeclarations): void { if (!declarations) return; const state: VisitorState = { data: null, cancel: false, skipChildren: false, }; Object.keys(declarations.vars).forEach((key) => { removeTokens(declarations.vars[key]); }); Object.keys(declarations.functions).forEach((key) => { removeTokens(declarations.functions[key]); }); function removeTokens(declaration: CodeDeclaration): void { const nodeVisitor = (before: boolean, visited: Expression | Statement, state: VisitorState) => { if (before) { if (visited) { delete visited.startToken delete visited.endToken; } } return state; }; visitNode(declaration.tree, state, nodeVisitor, nodeVisitor); } } ``` -------------------------------------------------------------------------------- /xmlui/scripts/generate-docs/error-handling.mjs: -------------------------------------------------------------------------------- ``` import { logger, ErrorWithSeverity, LOGGER_LEVELS, processError } from "./logger.mjs"; import { ERROR_HANDLING, ERROR_MESSAGES } from "./constants.mjs"; /** * Standardized error handling utilities for documentation generation scripts */ /** * Handles errors and exits gracefully with appropriate exit codes * @param {Error | ErrorWithSeverity | string} error - The error to handle * @param {number} exitCode - Optional exit code (defaults to GENERAL_ERROR) * @param {string} context - Optional context about where the error occurred */ export function handleFatalError(error, exitCode = ERROR_HANDLING.EXIT_CODES.GENERAL_ERROR, context = null) { if (context) { logger.error(`Error in ${context}:`); } processError(error); process.exit(exitCode); } /** * Handles non-fatal errors that should be logged but don't stop execution * @param {Error | ErrorWithSeverity | string} error - The error to handle * @param {string} context - Optional context about where the error occurred */ export function handleNonFatalError(error, context = null) { if (context) { logger.warn(`Warning in ${context}:`); } if (error instanceof ErrorWithSeverity) { logger.log(error.severity, error.message); } else if (error instanceof Error) { logger.warn(error.message); } else { logger.warn(error); } } /** * Validates required dependencies and throws appropriate errors * @param {Object} dependencies - Object with dependency checks * @throws {ErrorWithSeverity} If any required dependency is missing */ export function validateDependencies(dependencies) { for (const [name, value] of Object.entries(dependencies)) { if (value === undefined || value === null) { throw new ErrorWithSeverity( ERROR_MESSAGES[`NO_${name.toUpperCase()}`] || `Missing required dependency: ${name}`, LOGGER_LEVELS.error ); } } } /** * Wraps async operations with standardized error handling * @param {Function} operation - The async operation to execute * @param {string} operationName - Name of the operation for logging * @param {number} exitCode - Exit code to use if operation fails * @returns {Promise<any>} The result of the operation */ export async function withErrorHandling(operation, operationName, exitCode = ERROR_HANDLING.EXIT_CODES.GENERAL_ERROR) { try { return await operation(); } catch (error) { handleFatalError(error, exitCode, operationName); } } /** * Wraps file operations with standardized error handling * @param {Function} fileOperation - The file operation to execute * @param {string} filePath - Path of the file being operated on * @param {string} operationType - Type of operation (read, write, delete, etc.) * @returns {Promise<any>} The result of the operation */ export async function withFileErrorHandling(fileOperation, filePath, operationType) { try { return await fileOperation(); } catch (error) { const errorMessage = `${ERROR_MESSAGES.FILE_WRITE_ERROR}: ${filePath} (${operationType})`; throw new ErrorWithSeverity(errorMessage, LOGGER_LEVELS.error); } } /** * Creates a standardized error for missing metadata * @param {string} metadataType - Type of metadata that's missing * @returns {ErrorWithSeverity} */ export function createMetadataError(metadataType) { const message = ERROR_MESSAGES[`NO_${metadataType.toUpperCase()}`] || `${ERROR_MESSAGES.METADATA_LOAD_ERROR}: ${metadataType}`; return new ErrorWithSeverity(message, LOGGER_LEVELS.error); } ``` -------------------------------------------------------------------------------- /xmlui/tests/components/Tree/Tree-states.test.ts: -------------------------------------------------------------------------------- ```typescript import { describe, expect, it } from "vitest"; import { NodeLoadingState } from '../../../src/components-core/abstractions/treeAbstractions'; // Helper functions to test (these would normally be extracted from TreeNative for testing) type NodeStatesMap = Map<string, NodeLoadingState>; function createNodeStateHelpers() { let nodeStates: NodeStatesMap = new Map(); const setNodeLoadingState = (nodeId: string, state: NodeLoadingState) => { nodeStates.set(nodeId, state); }; const getNodeLoadingState = (nodeId: string): NodeLoadingState => { return nodeStates.get(nodeId) || 'unloaded'; }; const clearNodeLoadingState = (nodeId: string) => { nodeStates.delete(nodeId); }; const getAllNodeStates = (): NodeStatesMap => { return new Map(nodeStates); }; const clearAllNodeStates = () => { nodeStates.clear(); }; return { setNodeLoadingState, getNodeLoadingState, clearNodeLoadingState, getAllNodeStates, clearAllNodeStates }; } describe('Tree Node States Management - Unit Tests', () => { it('should set and get node loading state', () => { const helpers = createNodeStateHelpers(); helpers.setNodeLoadingState('node1', 'loading'); expect(helpers.getNodeLoadingState('node1')).toBe('loading'); helpers.setNodeLoadingState('node1', 'loaded'); expect(helpers.getNodeLoadingState('node1')).toBe('loaded'); }); it('should return unloaded for unknown nodes', () => { const helpers = createNodeStateHelpers(); expect(helpers.getNodeLoadingState('unknown')).toBe('unloaded'); }); it('should clear node loading state', () => { const helpers = createNodeStateHelpers(); helpers.setNodeLoadingState('node1', 'loaded'); expect(helpers.getNodeLoadingState('node1')).toBe('loaded'); helpers.clearNodeLoadingState('node1'); expect(helpers.getNodeLoadingState('node1')).toBe('unloaded'); }); it('should handle multiple node states', () => { const helpers = createNodeStateHelpers(); helpers.setNodeLoadingState('node1', 'loading'); helpers.setNodeLoadingState('node2', 'loaded'); helpers.setNodeLoadingState('node3', 'unloaded'); expect(helpers.getNodeLoadingState('node1')).toBe('loading'); expect(helpers.getNodeLoadingState('node2')).toBe('loaded'); expect(helpers.getNodeLoadingState('node3')).toBe('unloaded'); const allStates = helpers.getAllNodeStates(); expect(allStates.size).toBe(3); expect(allStates.get('node1')).toBe('loading'); expect(allStates.get('node2')).toBe('loaded'); expect(allStates.get('node3')).toBe('unloaded'); }); it('should clear all node states', () => { const helpers = createNodeStateHelpers(); helpers.setNodeLoadingState('node1', 'loading'); helpers.setNodeLoadingState('node2', 'loaded'); expect(helpers.getAllNodeStates().size).toBe(2); helpers.clearAllNodeStates(); expect(helpers.getAllNodeStates().size).toBe(0); expect(helpers.getNodeLoadingState('node1')).toBe('unloaded'); expect(helpers.getNodeLoadingState('node2')).toBe('unloaded'); }); it('should validate NodeLoadingState enum values', () => { const helpers = createNodeStateHelpers(); // Test all valid enum values const validStates: NodeLoadingState[] = ['unloaded', 'loading', 'loaded']; validStates.forEach(state => { helpers.setNodeLoadingState('test', state); expect(helpers.getNodeLoadingState('test')).toBe(state); }); }); }); ``` -------------------------------------------------------------------------------- /docs/public/pages/howto/pass-data-to-a-modal-dialog.md: -------------------------------------------------------------------------------- ```markdown # Pass data to a Modal Dialog ```xmlui-pg name="Click on a team member to edit details" ---app <App> <Test /> </App> ---api { "apiUrl": "/api", "initialize": "$state.team_members = [ { id: 1, name: 'Sarah Chen', role: 'Product Manager', email: '[email protected]', avatar: 'https://i.pravatar.cc/100?u=sarah', department: 'Product', startDate: '2022-03-15' }, { id: 2, name: 'Marcus Johnson', role: 'Senior Developer', email: '[email protected]', avatar: 'https://i.pravatar.cc/100?u=marcus', department: 'Engineering', startDate: '2021-08-20' }, { id: 3, name: 'Elena Rodriguez', role: 'UX Designer', email: '[email protected]', avatar: 'https://i.pravatar.cc/100?u=elena', department: 'Design', startDate: '2023-01-10' } ]", "operations": { "get_team_members": { "url": "/team_members", "method": "get", "handler": "return $state.team_members" } } } ---comp display <Component name="Test"> <DataSource id="team_members" url="/api/team_members" /> <ModalDialog id="memberDetailsDialog" title="Team Member Details"> <Theme backgroundColor-overlay="$color-surface-900"> <VStack gap="1rem" padding="1rem"> <!-- Avatar and Basic Info --> <HStack gap="1rem" alignItems="center"> <Avatar url="{$param.avatar}" size="lg" name="{$param.name}" /> <VStack gap="0.25rem" alignItems="start"> <Text variant="strong" fontSize="1.2rem">{$param.name}</Text> <Text variant="caption">{$param.role}</Text> <Text variant="caption" color="blue">{$param.email}</Text> </VStack> </HStack> <!-- Details Card --> <Card padding="1rem"> <VStack gap="0.5rem"> <HStack> <Text variant="strong">Department:</Text> <Text>{$param.department}</Text> </HStack> <HStack> <Text variant="strong">Start Date:</Text> <Text>{$param.startDate}</Text> </HStack> <HStack> <Text variant="strong">Employee ID:</Text> <Text>#{$param.id}</Text> </HStack> </VStack> </Card> <!-- Actions --> <HStack gap="0.5rem"> <Button label="Send Email" size="sm" onClick="console.log('Email to:', $param.email)" /> <Button label="View Calendar" size="sm" variant="secondary" onClick="console.log('Calendar for:', $param.name)" /> </HStack> </VStack> </Theme> </ModalDialog> <Text variant="strong" marginBottom="1rem">Team Directory</Text> <VStack gap="0.5rem"> <Items data="{team_members}"> <Card padding="1rem" cursor="pointer" onClick="{ memberDetailsDialog.open({ id: $item.id, name: $item.name, role: $item.role, email: $item.email, avatar: $item.avatar, department: $item.department, startDate: $item.startDate }) }" > <HStack gap="1rem" alignItems="center"> <Avatar url="{$item.avatar}" size="sm" name="{$item.name}" /> <VStack gap="0.25rem" alignItems="start"> <Text variant="strong">{$item.name}</Text> <Text variant="caption">{$item.role} - {$item.department}</Text> </VStack> </HStack> </Card> </Items> </VStack> </Component> ``` ``` -------------------------------------------------------------------------------- /xmlui/src/components/Heading/Heading.md: -------------------------------------------------------------------------------- ```markdown %-DESC-START **Key features:** - **Semantic levels**: Choose from h1 through h6 for proper document structure and accessibility - **Text overflow control**: Automatic ellipses and line limiting for long headings - **Anchor generation**: Optional hover anchors for deep linking to specific sections For the shorthand versions see: [H1](./H1), [H2](./H2), [H3](./H3), [H4](./H4), [H5](./H5), [H6](./H6). ```xmlui-pg copy display name="Example: Headings with levels" <App> <Heading level="h1" value="Heading Level 1" /> <Text>Text following H1</Text> <Heading level="h2" value="Heading Level 2" /> <Text>Text following H2</Text> <Heading level="h3" value="Heading Level 3" /> <Text>Text following H3</Text> <Heading level="h4" value="Heading Level 4" /> <Text>Text following H4</Text> <Heading level="h5" value="Heading Level 5" /> <Text>Text following H5</Text> <Heading level="h6" value="Heading Level 6" /> <Text>Text following H6</Text> </App> ``` %-DESC-END %-PROP-START value ```xmlui-pg copy display name="Example: value" <App> <Heading value="This is level 3 (value)" level="h3" /> <Heading level="h3">This is level 3 (child)</Heading> <Heading value="Value" level="h3"><Icon name="trash" /></Heading> </App> ``` %-PROP-END %-PROP-START level | Value | Description | | :---- | :---------------------------------------------------- | | `h1` | **(default)** Equivalent to the `<h1 />` HTML element | | `h2` | Equivalent to the `<h2 />` HTML element | | `h3` | Equivalent to the `<h3 />` HTML element | | `h4` | Equivalent to the `<h4 />` HTML element | | `h5` | Equivalent to the `<h5 />` HTML element | | `h6` | Equivalent to the `<h6 />` HTML element | For a visual example, see the component description. %-PROP-END %-PROP-START maxLines ```xmlui-pg copy display name="Example: maxLines" <App> <H2 maxWidth="160px" backgroundColor="cyan" value="A long heading text that will likely overflow" maxLines="2" /> </App> ``` %-PROP-END %-PROP-START preserveLinebreaks ```xmlui-pg copy display name="Example: preserveLinebreaks" ---app copy display {5} <App> <HStack> <H3 width="200px" backgroundColor="cyan" preserveLinebreaks="true" value="(preserve) This long text with several line breaks does not fit into a viewport with a 200-pixel width." /> <H3 width="200px" backgroundColor="cyan" value="(do not preserve) This long text with several line breaks does not fit into a viewport with a 200-pixel width." /> </HStack> </App> ---desc You can observe the effect of using `preserveLinebreaks`: ``` >[!INFO] > Remember to use the `value` property of `Heading`. > Linebreaks are converted to spaces when nesting the text in the `Heading` component. %-PROP-END %-PROP-START ellipses ```xmlui-pg copy {4} display name="Example: ellipses" <App> <VStack width="200px"> <H3 backgroundColor="cyan" maxLines="1" ellipses="false"> Though this long text does is about to crop! </H3> <H3 backgroundColor="cyan" maxLines="1"> Though this long text does is about to crop! </H3> </VStack> </App> ``` %-PROP-END %-PROP-START showAnchor If this property is not set, the engine checks if `showHeadingAnchors` flag is turned on in the global configuration (in the `appGlobals` configuration object) and displays the heading anchor accordingly. %-PROP-END ``` -------------------------------------------------------------------------------- /docs/public/pages/helper-tags.md: -------------------------------------------------------------------------------- ```markdown # Helper Tags Helper tags provide alternative XML markup syntax for declaring variables, properties, and event handlers in XMLUI. ## variable Use `<variable>` as an alternative to the `var.` attribute prefix syntax. Instead of this: ```xmlui <App var.count="{0}" var.message="Hello, World!"> <Text>{message}</Text> <Button onClick="count++" label="Count: {count}" /> </App> ``` You can do this: ```xmlui <App> <variable name="count" value="{0}" /> <variable name="message" value="Hello, World!" /> <Text>{message}</Text> <Button onClick="count++" label="Count: {count}" /> </App> ``` ## property Use `<property>` to declare properties with nested markup ```xmlui <Form data='{{ name: "", email: "" }}'> <FormItem bindTo="name" label="Name" /> <FormItem bindTo="email" label="Email" /> <property name="buttonRowTemplate"> <HStack gap="1rem"> <Button type="submit" label="Save" variant="primary" /> <Button type="reset" label="Cancel" variant="secondary" /> </HStack> </property> </Form> ``` App headers can use logo templates for custom branding: ```xmlui <AppHeader> <property name="logoTemplate"> <HStack verticalAlignment="center" gap="0.5rem"> <Icon name="star" size="lg" color="primary" /> <H2>My App</H2> </HStack> </property> </AppHeader> ``` Lists and other data-driven components can use item templates: ```xmlui <List data="{users}"> <property name="itemTemplate"> <HStack gap="1rem" padding="0.5rem"> <Avatar url="{$item.avatar}" name="{$item.name}" /> <VStack> <Text weight="bold">{$item.name}</Text> <Text color="muted">{$item.email}</Text> </VStack> </HStack> </property> </List> ``` Dropdown components can have rich option layouts: ```xmlui <Select data="{countries}" bindTo="selectedCountry"> <property name="optionTemplate"> <HStack gap="0.5rem"> <Image src="{$item.flag}" width="20px" height="15px" /> <Text>{$item.name}</Text> <Text color="muted">({$item.code})</Text> </HStack> </property> </Select> ``` ## event Use `<event>` to declare event handlers as markup and enable the use of component-based handlers. Instead of using the `on` attribute prefix: ```xmlui <Button label="Click me" onClick="count++" /> ``` You can use the `<event>` tag: ```xmlui <Button label="Click me"> <event name="click"> count++ </event> </Button> ``` `<event>` is necessary when using `<APICall>` as an event handler. ```xmlui <Button label="Save Data"> <event name="click"> <APICall url="/api/save" method="POST" body="{formData}" onSuccess="toast('Data saved successfully!')" onError="toast('Failed to save data', 'error')" /> </event> </Button> ``` ## method Use `<method>` to export a method from a component. ```xmlui <App> <UsingInternalModal id="component"/> <Button label="Open the internal dialog" onClick="component.openDialog()" /> </App> Component name="UsingInternalModal"> <ModalDialog id="dialog" title="Example Dialog"> <Button label="Close Dialog" onClick="dialog.close()" /> </ModalDialog> <H1>Using an Internal Modal Dialog</H1> <method name="openDialog"> console.log('internal method called') dialog.open(); </method> </Component> ``` ## script Use `<script>` to declare inline JavaScript code. ```xmlui <Component name="ImportProducts"> <script> var parsedCsv = null; function isDuplicate(name) { return existingProducts.value.some(p => p.name === name); } </script> ... ``` ``` -------------------------------------------------------------------------------- /xmlui/src/parsers/xmlui-parser/utils.ts: -------------------------------------------------------------------------------- ```typescript import type { Node } from "./syntax-node"; import type { GetText } from "./parser"; import { SyntaxKind, getSyntaxKindStrRepr, isInnerNode } from "./syntax-kind"; export function toDbgString( node: Node, getText: (node: Node) => string, indentationLvl: number = 0, ): string { const prefix = `${" ".repeat(indentationLvl)} ${getSyntaxKindStrRepr(node.kind)} @${node.start}..${node.end}`; if (!isInnerNode(node.kind)) { let tokenText = getText(node); if (node.kind === SyntaxKind.NewLineTrivia) { tokenText = "*newline*"; } return prefix + ` "${tokenText}"`; } else { return ( prefix + "\n" + node.children?.map((c) => toDbgString(c, getText, indentationLvl + 1)).join("\n") ); } } /** Disregards error nodes amongst the children of the 2 compared name node. (Those reported an error earlyer anyways)*/ export function tagNameNodesWithoutErrorsMatch( name1: Node, name2: Node, getText: GetText, ): boolean { const children1 = name1.children?.filter((c) => c.kind !== SyntaxKind.ErrorNode) ?? []; const children2 = name2.children?.filter((c) => c.kind !== SyntaxKind.ErrorNode) ?? []; if (children1.length !== children2.length) { return false; } for (let i = 0; i < children1.length; ++i) { if (getText(children1[i]) !== getText(children2[i])) { return false; } } return true; } /** If the position is in-between two tokens, the chain to the token just before the cursor is provided as well*/ export type FindTokenSuccess = | { chainAtPos: Node[]; /** If the position is in-between two tokens, the chain to the token just before the position is provided. */ chainBeforePos: Node[]; /** * This field specifies the first index where * `chainBeforePos` differs from chainAtPos */ sharedParents: number; } | { chainBeforePos: undefined; chainAtPos: Node[]; sharedParents: undefined; }; export function findTokenAtPos(node: Node, position: number): FindTokenSuccess | undefined { const chain: Node[] = [node]; let sharedParents: number; if (node.start > position || position > node.end) { return undefined; } const res: FindTokenSuccess = { chainAtPos: chain, chainBeforePos: undefined, sharedParents: undefined, }; while (node.children !== undefined && node.children.length > 0) { //todo: make it a binary search before finding a fork const nodeAtPosIdx = node.children.findIndex( (n) => n.start <= position && (position < n.end || (n.kind === SyntaxKind.EndOfFileToken && n.start <= n.end)), ); const nodeAtPos = node.children[nodeAtPosIdx]; const nodeBeforePos = node.children[nodeAtPosIdx - 1]; if (nodeBeforePos !== undefined && position <= nodeAtPos.pos) { sharedParents = chain.length; return { chainBeforePos: chain.concat(findLastToken(nodeBeforePos)), sharedParents, chainAtPos: chain.concat(findFirstToken(nodeAtPos)), }; } node = nodeAtPos; res.chainAtPos!.push(node); } return res; } function findFirstToken(node: Node): Node[] { const chain = [node]; while (node.children !== undefined && node.children.length > 0) { node = node.children[0]; chain.push(node); } return chain; } function findLastToken(node: Node): Node[] { const chain = [node]; while (node.children !== undefined && node.children.length > 0) { node = node.children[node.children.length - 1]; chain.push(node); } return chain; } ``` -------------------------------------------------------------------------------- /xmlui/src/components-core/script-runner/ParameterParser.ts: -------------------------------------------------------------------------------- ```typescript import type { Expression } from "./ScriptingSourceTree"; import { Parser } from "../../parsers/scripting/Parser"; /** * This function parses a parameter string and splits them into string literal and binding expression sections * @param source String to parse * @returns Parameter string sections */ export function parseParameterString (source: string): (StringLiteralSection | ExpressionSection)[] { const result: (StringLiteralSection | ExpressionSection)[] = []; if (source === undefined || source === null) return result; let phase = ParsePhase.StringLiteral; let section = ""; let escape = ""; for (let i = 0; i < source.length; i++) { const ch = source[i]; switch (phase) { case ParsePhase.StringLiteral: if (ch === "\\") { phase = ParsePhase.Escape; escape = "\\"; } else if (ch === "{") { // --- A new expression starts, close the previous string literal if (section !== "") { result.push({ type: "literal", value: section }); } // --- Start a new section section = ""; phase = ParsePhase.ExprStart; } else { section += ch; } break; case ParsePhase.Escape: if (ch === "\\") { // --- Go on with escape escape += ch; break; } if (ch === "{") { // --- End escape as a literal section without the first "\" escape character section += escape.substring(1) + ch; } else { // --- End escape as a literal section with the full sequence section += escape + ch; } phase = ParsePhase.StringLiteral; break; case ParsePhase.ExprStart: const exprSource = source.substring(i); const parser = new Parser(source.substring(i)); let expr: Expression | null = null; try { expr = parser.parseExpr(); } catch (err) { throw new Error(`Cannot parse expression: '${exprSource}': ${err}`); } const tail = parser.getTail(); if (!tail || tail.trim().length < 1 || tail.trim()[0] !== "}") { // --- Unclosed expression, back to its beginning throw new Error(`Unclosed expression: '${source}'\n'${exprSource}'`); } else { // --- Successfully parsed expression result.push({ type: "expression", value: expr! }); // --- Skip the parsed part of the expression, and start a new literal section i = source.length - tail.length; section = ""; } phase = ParsePhase.StringLiteral; break; } } // --- Process the last segment switch (phase) { case ParsePhase.StringLiteral: if (section !== "") { result.push({ type: "literal", value: section }); } break; case ParsePhase.Escape: result.push({ type: "literal", value: section + escape }); break; case ParsePhase.ExprStart: result.push({ type: "literal", value: section + "{" }); break; } // --- Done. return result; } enum ParsePhase { StringLiteral, Escape, ExprStart } /** * Represents a literal segment */ type StringLiteralSection = { type: "literal"; // --- The string literal value: string; }; type ExpressionSection = { type: "expression"; // --- The expression string to parse value: Expression; }; ``` -------------------------------------------------------------------------------- /xmlui/src/components-core/theming/hvar.ts: -------------------------------------------------------------------------------- ```typescript export type HVar = { classes: Array<string>; attribute: string; component: string; traits: Array<string>; states: Array<string>; }; const parsedHVarCache: Record<string, HVar | null> = {}; //extremely dummy solution, will come back later export function parseHVar(input: string): HVar | null { if (parsedHVarCache[input] !== undefined) { return parsedHVarCache[input]; } // Split the input string into parts using regex const parts = input.split(/-[A-Z]+/); if (parts.length !== 2) { parsedHVarCache[input] = null; return parsedHVarCache[input]; } const firstPart = parts[0]; const classessParts = firstPart.split(":"); const attribute = classessParts[classessParts.length - 1]; const classes = classessParts.length > 1 ? classessParts.slice(0, classessParts.length - 1) : []; const secondPart = input.substring(firstPart.length + 1); const [compName, ...rest] = secondPart.split("-"); const traitsAndStates = secondPart.substring(compName.length).split("--"); const states: Array<string> = []; const traits: Array<string> = []; traitsAndStates.forEach((part) => { if (!part.includes("-") && part) { states.push(part); } else { part.split("-").forEach((trait) => { if (trait) { traits.push(trait); } }); } }); parsedHVarCache[input] = { classes: classes, attribute: attribute, component: compName, traits: traits, states: states, }; return parsedHVarCache[input]; } function createCombinations(arr: Array<any> = []) { const stateCombinations = []; for (let i = 1; i <= arr.length; i++) { for (let j = 0; j <= arr.length - i; j++) { stateCombinations.push(arr.slice(j, j + i)); } } return stateCombinations.sort((a, b) => b.length - a.length); } export type ThemeVarMatchResult = { forValue: string; matchedValue: string | undefined; from: Array<string>; }; export function matchThemeVar( themeVar: string, availableThemeVars: Array<Record<string, string>> = [] ): ThemeVarMatchResult | undefined { const hvar = parseHVar(themeVar); if (!hvar) { return; } const stateCombinations = createCombinations(hvar.states); const traitCombinations = createCombinations(hvar.traits); const sortedTraitCombinations: Array<string> = []; traitCombinations.forEach((traitComb) => { let result = ""; traitComb.forEach((t) => { result = `${result}-${t}`; }); sortedTraitCombinations.push(result); }); sortedTraitCombinations.push(""); const sortedStateCombinations: Array<string> = []; stateCombinations.forEach((stateComb) => { let result = ""; stateComb.forEach((s) => { result = `${result}--${s}`; }); sortedStateCombinations.push(result); }); sortedStateCombinations.push(""); const componentParts = [hvar.component, ...hvar.classes]; const from: Array<string> = []; sortedStateCombinations.forEach((stateComb) => { sortedTraitCombinations.forEach((traitComb) => { componentParts.forEach((componentPart) => { from.push(`${hvar.attribute}-${componentPart}${traitComb}${stateComb}`); }); }); }); let matchedValue; for (let i = availableThemeVars.length - 1; i >= 0; i--) { const themeVars = availableThemeVars[i]; let foundValue = from.find((themeVar) => themeVars[themeVar] !== undefined); if (foundValue) { matchedValue = foundValue; break; } } const forValue = from[0]; return { forValue: forValue, matchedValue: matchedValue, from: from, }; } ``` -------------------------------------------------------------------------------- /xmlui/scripts/generate-docs/build-pages-map.mjs: -------------------------------------------------------------------------------- ``` import { writeFileSync, statSync, readFileSync } from "fs"; import { extname } from "path"; import { gatherAndRemoveDuplicates, strBufferToLines, toHeadingPath, toNormalizedUpperCase, traverseDirectory, } from "./utils.mjs"; import { createScopedLogger } from "./logging-standards.mjs"; import { generateExportStatements, processDuplicatesWithLogging } from "./pattern-utilities.mjs"; import { PAGES_MAP_CONFIG } from "./constants.mjs"; const pathCutoff = PAGES_MAP_CONFIG.PATH_CUTOFF; const includedFileExtensions = PAGES_MAP_CONFIG.INCLUDED_FILE_EXTENSIONS; /** * Creates a file containing link constant variables to components/articles in the pages folder. * @param {string} pagesFolder The path to the pages folder (use UNIX delimiters) * @param {string} outFilePathAndName The path and name of the output file (use UNIX delimiters) */ export function buildPagesMap(pagesFolder, outFilePathAndName) { const logger = createScopedLogger("PagesMapBuilder"); logger.operationStart("building pages map"); const pages = []; traverseDirectory({ name: "", path: pagesFolder }, (item, _) => { /** * name: the folder's/file's name (eg. "hello-app-engine") * path: the path to the root of the given folder from the project root (eg. "src/apps/1_basic/samples/hello-app-engine") * parent: parent node * children: children file/folder names */ if (statSync(item.path).isDirectory()) { // Node is a folder } else { // Node is a file if (includedFileExtensions.includes(extname(item.name))) { const articleHeadings = getArticleIds(item); if (articleHeadings) { pages.push(...articleHeadings); } } } }); const { filtered: filteredPages, duplicates } = gatherAndRemoveDuplicates(pages); // Process duplicates with standardized logging processDuplicatesWithLogging(duplicates, logger, "article IDs and paths"); // Generate export statements using utility const pagesStr = generateExportStatements(filteredPages); writeFileSync(outFilePathAndName, pagesStr); } function getArticleIds(article) { const content = readFileSync(article.path, { encoding: "utf8" }); const relativeArticlePath = article.path.split(pathCutoff)[1]?.replace(extname(article.name), ""); const lines = strBufferToLines(content); const titleId = getTitleId(lines); if (!titleId) return null; const subHeadingIds = getSubHeadingIds(lines); return [ { id: toNormalizedUpperCase(titleId), path: relativeArticlePath }, ...subHeadingIds.map((id) => ({ id: `${toNormalizedUpperCase(titleId)}_${toNormalizedUpperCase(id)}`, path: `${relativeArticlePath}#${toHeadingPath(id)}`, })), ]; // --- function getTitleId(lines) { for (const line of lines) { const match = line.match(/^#\s+.+?\s*(\[#[\w-]+\])?$/); if (!match) continue; if (match[1]) { // Has ID, extract it and use that return match[1].replace(/\[#(.*?)\]/, (_, p1) => p1); } else { // Generate new ID from the heading title return match[0].slice(1); } } } function getSubHeadingIds(lines) { const headings = []; for (const line of lines) { // We only gather headings which have an explicit ID defined and they are not leveled as h1 const match = line.match(/^##+\s+.+?\s*(\[#[\w-]+\])$/); if (!match) continue; if (!match[1]) continue; // Has ID, extract it and use that headings.push(match[1].replace(/\[#(.*?)\]/, (_, p1) => p1)); } return headings; } } ``` -------------------------------------------------------------------------------- /packages/xmlui-website-blocks/src/HeroSection/HeroSection.module.scss: -------------------------------------------------------------------------------- ```scss @use "xmlui/themes.scss" as t; // --- This code snippet is required to collect the theme variables used in this module $themeVars: (); @function createThemeVar($componentVariable) { $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global; @return t.getThemeVar($themeVars, $componentVariable); } $component: "HeroSection"; $preamble: "preamble-#{$component}"; $headline: "headline-#{$component}"; $subheadline: "subheadline-#{$component}"; $mainText: "mainText-#{$component}"; $themeVars: t.composePaddingVars($themeVars, $component); $themeVars: t.composeTextVars($themeVars, $headline); $themeVars: t.composeTextVars($themeVars, $subheadline); $themeVars: t.composeTextVars($themeVars, $preamble); $themeVars: t.composeTextVars($themeVars, $mainText); @layer components { .heroWrapper { display: flex; flex-direction: column; position: relative; z-index: 0; // Create a stacking context to contain z-index values overflow: hidden; // Prevent background from extending beyond bounds .backgroundTemplate { position: absolute; top: 0; left: 0; right: 0; bottom: 0; z-index: -1; // Place behind content but within the hero wrapper's context } .heroContent { position: relative; z-index: 0; // Above background but within normal document flow display: flex; flex-direction: column; @include t.paddingVars($themeVars, $component); // --- Here, 2 * t.$space-4 accounts for the left and right padding of the page area width: calc(createThemeVar("maxWidth-content") - 2 * t.$space-4); margin: 0 auto; // Center horizontally within parent &.horizontal { flex-direction: row; align-items: flex-start; } &.vertical { flex-direction: column; } } .header { display: flex; flex-direction: column; } .content { display: flex; flex-direction: column; flex: 1; &.contentStart { align-items: flex-start; } &.contentCenter { align-items: center; } &.contentEnd { align-items: flex-end; } } .headingSection { display: flex; flex-direction: column; &.start { text-align: start; align-items: flex-start; } &.center { text-align: center; align-items: center; } &.end { text-align: end; align-items: flex-end; } } } .preamble { @include t.textVars($themeVars, $preamble); white-space: pre-wrap; display: inline-block; margin: 0; padding-bottom: createThemeVar("gap-#{$preamble}"); } .headline { @include t.textVars($themeVars, $headline); white-space: pre-wrap; display: inline-block; margin: 0; padding-bottom: createThemeVar("gap-#{$headline}"); } .subheadline { @include t.textVars($themeVars, $subheadline); white-space: pre-wrap; display: inline-block; margin: 0; padding-bottom: createThemeVar("gap-#{$subheadline}"); } .textWrapper { display: flex; flex-direction: column; white-space: pre-wrap; padding-bottom: createThemeVar("gap-#{$mainText}"); } .mainText { @include t.textVars($themeVars, $mainText); white-space: pre-wrap; display: inline-block; margin: 0; padding-bottom: createThemeVar("gap-#{$mainText}"); } .ctaButtonWrapper { margin-top: t.$space-16; } .ctaButton { font-size: 1.5em; } } :export { themeVars: t.json-stringify($themeVars); } ``` -------------------------------------------------------------------------------- /xmlui/src/components/DropdownMenu/DropdownMenu.md: -------------------------------------------------------------------------------- ```markdown %-DESC-START **Key features:** - **Hierarchical organization**: Supports nested submenus for complex menu structures - **Flexible triggers**: Customizable button trigger or use your own trigger component - **Progressive disclosure**: Reveals options on demand You can nest `MenuItem`, `MenuSeparator`, and `SubMenuItem` components into `DropdownMenu` to define a menu hierarchy. The component provides a trigger to display the menu items: ```xmlui-pg copy display name="Example: Using DropdownMenu" height="240px" ---app copy display <App> <DropdownMenu label="DropdownMenu"> <MenuItem>Item 1</MenuItem> <MenuItem>Item 2</MenuItem> <MenuSeparator /> <SubMenuItem label="Submenu"> <MenuItem>Submenu Item 1</MenuItem> <MenuItem>Submenu Item 2</MenuItem> </SubMenuItem> </DropdownMenu> </App> ---desc Try this dropdown menu: ``` %-DESC-END %-PROP-START alignment Available values are: - `start`: Menu items are aligned to the start of the trigger component (default). - `end`: Menu items are aligned to the end of the trigger component. ```xmlui-pg copy display name="Example: alignment" height="240px" <App> <HStack> <DropdownMenu label="Start-aligned menu (open it!)"> <MenuItem>Item 1</MenuItem> <MenuItem>Item 2</MenuItem> <MenuItem>Item 3</MenuItem> </DropdownMenu> <DropdownMenu label="End-aligned menu (open it!)" alignment="end"> <MenuItem>Item 1</MenuItem> <MenuItem>Item 2</MenuItem> <MenuItem>Item 3</MenuItem> </DropdownMenu> </HStack> </App> ``` %-PROP-END %-PROP-START triggerTemplate ```xmlui-pg copy {3-5} display name="Example: triggerTemplate" height="240px" <App> <DropdownMenu label="(ignored)"> <property name="triggerTemplate"> <Button label="Custom trigger" icon="chevrondown" iconPosition="end"/> </property> <MenuItem>Item 1</MenuItem> <MenuItem>Item 2</MenuItem> <MenuItem>Item 3</MenuItem> </DropdownMenu> </App> ``` %-PROP-END %-PROP-START enabled ```xmlui-pg copy {4, 11} display name="Example: enabled" height="240px" <App> <HStack> <DropdownMenu enabled="true" label="Enabled Dropdown"> <MenuItem>Item 1</MenuItem> <MenuItem>Item 2</MenuItem> <MenuItem>Item 3</MenuItem> </DropdownMenu> <DropdownMenu enabled="false" label="Disabled Dropdown"> <MenuItem>Item 1</MenuItem> <MenuItem>Item 2</MenuItem> <MenuItem>Item 3</MenuItem> </DropdownMenu> </HStack> </App> ``` %-PROP-END %-EVENT-START willOpen ```xmlui-pg copy {6} display name="Example: willOpen" height="240px" <App> <variable name="counter" value="{0}" /> <Text value="Number of times the dropdown was opened: {counter}" /> <DropdownMenu label="Dropdown" onWillOpen="counter += 1"> <MenuItem>Item 1</MenuItem> <MenuItem>Item 2</MenuItem> <MenuItem>Item 3</MenuItem> </DropdownMenu> </App> ``` %-EVENT-END %-API-START close ```xmlui-pg copy {4} display name="Example: close" height="240px" <App> <DropdownMenu id="emojiDropdown" label="Emoji Dropdown"> <EmojiSelector onSelect="(reaction) => { emojiDropdown.close(); }" autoFocus="true" /> </DropdownMenu> </App> ``` %-API-END %-API-START open ```xmlui-pg copy {2} display name="Example: open" height="300px" <App> <Button onClick="emojiDropdown.open()">Open the Dropdown</Button> <DropdownMenu id="emojiDropdown" label="Emoji Dropdown"> <EmojiSelector onSelect="(reaction) => { emojiDropdown.close(); }" autoFocus="true" /> </DropdownMenu> </App> ``` %-API-END ``` -------------------------------------------------------------------------------- /xmlui/src/components/RealTimeAdapter/RealTimeAdapterNative.tsx: -------------------------------------------------------------------------------- ```typescript import { useEffect } from "react"; import type { AppContextObject } from "../../abstractions/AppContextDefs"; import RestApiProxy from "../../components-core/RestApiProxy"; import { delay } from "../../components-core/utils/misc"; import { useAppContext } from "../../components-core/AppContext"; export const defaultProps = { url: "", }; type Props = { url: any; onEvent?: (...args: any[]) => void; }; type RealtimeEventHandler = (events: Array<RealTimeEvent>) => void; class PollClient { handlers: Array<RealtimeEventHandler> = []; lastEventId: any; tries: number = 0; polling: boolean = false; abortController: AbortController = new AbortController(); constructor( public url: string, public appContext: AppContextObject, ) {} refreshAppContext(appContext: AppContextObject) { this.appContext = appContext; } private async poll(abortSignal: AbortSignal) { if (!this.polling || abortSignal.aborted) { return; } try { const response = await new RestApiProxy(this.appContext).execute({ abortSignal, operation: { url: this.url, method: "get", headers: { "Cache-Control": "no-cache, no-store", }, queryParams: { lastEventId: this.lastEventId, }, }, resolveBindingExpressions: true, }); this.eventArrived(response); await this.poll(abortSignal); } catch (e) { if (this.tries < 100) { this.tries++; await delay(this.tries * 100); //TODO we should do some exponential fallback here await this.poll(abortSignal); } else { this.stopPoll(); } } } private startPoll() { if (this.polling) { return; } this.polling = true; this.tries = 0; this.abortController = new AbortController(); // console.log("poll client: start polling", this.handlers); void this.poll(this.abortController.signal); } private stopPoll() { this.polling = false; this.abortController.abort(); // console.log("poll client: stop polling"); } private eventArrived(response: any) { if (!response) { return; } let events = [response]; if (Array.isArray(response)) { events = response; } this.lastEventId = events[events.length - 1].id; this.handlers.forEach((handler) => { handler(events); }); } subscribe(handler: RealtimeEventHandler) { // console.log("subscribe", handler); this.handlers.push(handler); this.startPoll(); } unsubscribe(handler: RealtimeEventHandler) { // console.log("unsubscribe", handler); this.handlers = this.handlers.filter((existingHandler) => handler !== existingHandler); if (!this.handlers.length) { this.stopPoll(); } } } interface RealTimeEvent {} const clients: Record<string, PollClient> = {}; function createOrGetPollClient(url: string, appContext: AppContextObject) { if (!clients[url]) { clients[url] = new PollClient(url, appContext); } clients[url].refreshAppContext(appContext); return clients[url]; } export function RealTimeAdapter({ url, onEvent }: Props) { const appContext = useAppContext(); useEffect(() => { const pollClient = createOrGetPollClient(url, appContext); const handler: RealtimeEventHandler = (events) => { events.forEach((event) => { onEvent?.(event); }); }; pollClient.subscribe(handler); return () => { pollClient.unsubscribe(handler); }; }, [appContext, onEvent, url]); return null; } ``` -------------------------------------------------------------------------------- /xmlui/src/components/Icon/svg/admonition_info.svg: -------------------------------------------------------------------------------- ``` <svg width="37" height="36" viewBox="0 0 37 36" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M19.6797 30.6797H16.7538C16.4842 30.6797 16.2661 30.8978 16.2661 31.1673V31.655C16.2661 32.7308 17.141 33.6056 18.2168 33.6056C19.2925 33.6056 20.1674 32.7308 20.1674 31.655V31.1673C20.1674 30.8978 19.9493 30.6797 19.6797 30.6797Z" fill="#868491"/> <path d="M18.2163 30.6797H16.7538C16.4842 30.6797 16.2661 30.8978 16.2661 31.1673V31.655C16.2661 32.7307 17.1407 33.6053 18.2163 33.6056V30.6797Z" fill="#9A9A9A"/> <path d="M25.8957 5.43603C23.6164 3.29393 20.631 2.22101 17.4998 2.41742C11.7935 2.7725 7.18363 7.53242 7.00551 13.2541C6.87409 17.4935 9.10093 21.3967 12.8174 23.4411C13.7413 23.9497 14.3152 24.9265 14.3152 25.9908V29.704C14.3152 30.7798 15.19 31.6547 16.2658 31.6547H20.1671C21.2429 31.6547 22.1177 30.7798 22.1177 29.704V25.9908C22.1177 24.9274 22.6959 23.9483 23.6274 23.4349C27.2082 21.4595 29.4326 17.6954 29.4326 13.6112C29.4326 10.5281 28.1435 7.54833 25.8957 5.43603Z" fill="#FFD93C"/> <path d="M18.2172 22.9604C23.3799 22.9604 27.5651 18.7752 27.5651 13.6125C27.5651 8.44982 23.3799 4.26465 18.2172 4.26465C13.0546 4.26465 8.86938 8.44982 8.86938 13.6125C8.86938 18.7752 13.0546 22.9604 18.2172 22.9604Z" fill="#FFE980"/> <path d="M19.922 16.5376C19.2738 16.5376 18.9424 16.1594 18.701 15.8837C18.4881 15.6408 18.4033 15.5622 18.2133 15.5622C18.0242 15.5622 17.94 15.6408 17.728 15.8832C17.5004 16.1437 17.156 16.5376 16.5079 16.5376C15.8602 16.5376 15.5159 16.1437 15.2883 15.8832C15.0763 15.6408 14.9921 15.5622 14.803 15.5622C14.5334 15.5622 14.3153 15.3441 14.3153 15.0746C14.3153 14.805 14.5334 14.5869 14.803 14.5869C15.4506 14.5869 15.7949 14.9808 16.0226 15.2412C16.2346 15.4837 16.3188 15.5622 16.5079 15.5622C16.697 15.5622 16.7817 15.4837 16.9937 15.2412C17.2213 14.9808 17.5656 14.5869 18.2133 14.5869C18.8615 14.5869 19.1929 14.965 19.4343 15.2408C19.6472 15.4837 19.732 15.5622 19.922 15.5622C20.1115 15.5622 20.1963 15.4837 20.4092 15.2412C20.6373 14.9808 20.9816 14.5869 21.6302 14.5869C21.8998 14.5869 22.1179 14.805 22.1179 15.0746C22.1179 15.3441 21.8998 15.5622 21.6302 15.5622C21.4402 15.5622 21.3554 15.6408 21.1425 15.8837C20.9144 16.1442 20.5702 16.5376 19.922 16.5376Z" fill="#FFF7CE"/> <path d="M16.7533 25.8024C16.529 25.8024 16.3271 25.6466 16.277 25.4185L13.8387 14.2023C13.7815 13.939 13.9488 13.6794 14.2116 13.6218C14.4764 13.5637 14.7345 13.7319 14.7921 13.9947L17.2304 25.2109C17.2876 25.4742 17.1204 25.7338 16.8575 25.7914C16.8224 25.7991 16.7875 25.8024 16.7533 25.8024Z" fill="#FFF7CE"/> <path d="M19.6802 25.8022C19.6458 25.8022 19.6111 25.7989 19.5759 25.7913C19.313 25.7337 19.1458 25.4741 19.203 25.2108L21.6413 13.9946C21.6989 13.7317 21.9561 13.564 22.2218 13.6217C22.4847 13.6793 22.6518 13.9389 22.5947 14.2022L20.1564 25.4184C20.1064 25.6466 19.9045 25.8022 19.6802 25.8022Z" fill="#FFF7CE"/> <path d="M14.2314 25.3164C14.2833 25.5354 14.3152 25.761 14.3152 25.9922V29.7053C14.3152 30.7811 15.1901 31.656 16.2658 31.656H20.1671C21.2429 31.656 22.1178 30.7811 22.1178 29.7053V25.9922C22.1178 25.7612 22.1499 25.5355 22.2021 25.3164H14.2314Z" fill="#B4B6BC"/> <path d="M18.2162 25.3164H14.2314C14.2833 25.5354 14.3152 25.761 14.3152 25.9922V29.7053C14.3152 30.7811 15.1901 31.656 16.2658 31.656H18.2162V25.3164Z" fill="#B4B6BC"/> <path d="M22.1175 26.7773H18.2162V27.7527H22.1175V26.7773Z" fill="#9A9A9A"/> <path d="M22.1177 28.7285H18.2164V29.7038H22.1177V28.7285Z" fill="#9A9A9A"/> <path d="M18.2162 26.7773H14.3149V27.7527H18.2162V26.7773Z" fill="#9A9A9A"/> <path d="M18.2162 28.7285H14.3149V29.7038H18.2162V28.7285Z" fill="#9A9A9A"/> </svg> ``` -------------------------------------------------------------------------------- /docs/public/pages/howto/chain-a-refetch.md: -------------------------------------------------------------------------------- ```markdown # Chain a DataSource refetch from an APICall.execute `APICall.execute` returns a Promise, you can call `.then` to do something else. ```xmlui-pg copy display {54} name="Click the Like button" ---comp display <Component name="SocialButton"> <Button borderRadius="50%" icon="{$props.icon}" variant="outlined" themeColor="{$props.themeColor || 'secondary'}" size="xs" onClick="{emitEvent('click')}" /> </Component> ---app display <App> <APICall id="favoritePost" method="post" url="/api/posts/{$param}/favorite" /> <APICall id="unfavoritePost" method="post" url="/api/posts/{$param}/unfavorite" /> <DataSource id="timelineData" url="/api/timeline" method="GET" /> <VStack gap="$space-4" padding="$space-4"> <Text variant="h3">Social Media Timeline</Text> <Items data="{timelineData}"> <Card padding="$space-3" marginBottom="$space-2"> <VStack gap="$space-2"> <Text variant="h6">{$item.author}</Text> <Text>{$item.content}</Text> <HStack gap="$space-4" verticalAlignment="center"> <HStack gap="$space-1" verticalAlignment="center"> <SocialButton icon="reply" /> <Text variant="caption">{$item.replies_count}</Text> </HStack> <HStack gap="$space-1" verticalAlignment="center"> <SocialButton icon="trending-up" /> <Text variant="caption">{$item.reblogs_count}</Text> </HStack> <HStack gap="$space-1" verticalAlignment="center"> <SocialButton icon="like" themeColor="{$item.favourited ? 'attention' : 'secondary'}"> <event name="click"> if ($item.favourited) { // execute returns a Promise unfavoritePost.execute($item.id).then(() => timelineData.refetch()); } else { favoritePost.execute($item.id).then(() => timelineData.refetch()); } </event> </SocialButton> <Text variant="caption">{$item.favourites_count}</Text> </HStack> </HStack> </VStack> </Card> </Items> </VStack> </App> ---api { "apiUrl": "/api", "initialize": "$state.posts = [ { id: '1', content: 'This is a great post about XMLUI!', author: 'John Developer', favourited: false, favourites_count: 5, replies_count: 2, reblogs_count: 1 }, { id: '2', content: 'Learning how to chain API calls is so useful.', author: 'Jane Designer', favourited: true, favourites_count: 12, replies_count: 4, reblogs_count: 3 } ]", "operations": { "get-timeline": { "url": "/timeline", "method": "get", "handler": "return $state.posts" }, "favorite-post": { "url": "/posts/:id/favorite", "method": "post", "pathParamTypes": { "id": "string" }, "handler": " const post = $state.posts.find(p => p.id === $pathParams.id); if (post) { post.favourited = true; post.favourites_count += 1; } " }, "unfavorite-post": { "url": "/posts/:id/unfavorite", "method": "post", "pathParamTypes": { "id": "string" }, "handler": " const post = $state.posts.find(p => p.id === $pathParams.id); if (post) { post.favourited = false; post.favourites_count -= 1; } " } } } ``` ``` -------------------------------------------------------------------------------- /xmlui/tests/components-core/scripts-runner/eval-tree-arrow.test.ts: -------------------------------------------------------------------------------- ```typescript import { describe, expect, it } from "vitest"; import { evalBindingExpression } from "../../../src/components-core/script-runner/eval-tree-sync"; import {createEvalContext} from "./test-helpers"; describe("Evaluate arrow expressions (exp)", () => { it("Arrow #1", () => { // --- Arrange const source = "(x => 2 * x)(4)"; const context = createEvalContext({}); // --- Act const value = evalBindingExpression(source, context); // --- Arrange expect(value).equal(8); }); it("Arrow #2", () => { // --- Arrange const source = "((x, y) => x + y)(1, 2)"; const context = createEvalContext({}); // --- Act const value = evalBindingExpression(source, context); // --- Arrange expect(value).equal(3); }); it("Arrow #3", () => { // --- Arrange const source = "((x, y) => { return x + y })(1, 2)"; const context = createEvalContext({}); // --- Act const value = evalBindingExpression(source, context); // --- Arrange expect(value).equal(3); }); it("Arrow #4", () => { // --- Arrange const source = "(x => (++x.h))(count)"; const context = createEvalContext({ localContext: { count: { h: 3 } } }); // --- Act const value = evalBindingExpression(source, context); // --- Arrange expect(value).equal(4); }); it("Arrow #5", () => { // --- Arrange const source = "(x => x += 2)(count)"; const context = createEvalContext({ localContext: { count: 3 } }); // --- Act const value = evalBindingExpression(source, context); // --- Arrange expect(value).equal(5); }); it("Arrow #6", () => { // --- Arrange const source = "(x => x += 2)(count + 4)"; const context = createEvalContext({ localContext: { count: 3 } }); // --- Act const value = evalBindingExpression(source, context); // --- Arrange expect(value).equal(9); }); it("Arrow #7", () => { // --- Arrange const source = "[1,2,3,4,5].filter(x => x % 2 === 0)[1]"; const context = createEvalContext({ localContext: { count: 3 } }); // --- Act const value = evalBindingExpression(source, context); // --- Arrange expect(value).equal(4); }); it("Arrow #8", () => { // --- Arrange const source = "containsArray.array.filter(item => item % 2 === 0)[1]"; const context = createEvalContext({ localContext: { containsArray: { array: [5, 4, 3, 2, 1] } } }); // --- Act const value = evalBindingExpression(source, context); // --- Arrange expect(value).equal(2); }); it("Arrow #9", () => { // --- Arrange const source = "array.reduce((acc, item) => acc + item, 0)"; const context = createEvalContext({ localContext: { array: [5, 4, 3, 2, 1] } }); // --- Act const value = evalBindingExpression(source, context); // --- Arrange expect(value).equal(15); }); it("Arrow with rest #1", () => { // --- Arrange const source = "((...a) => a[0] + a[1])(1, 2)"; const context = createEvalContext({}); // --- Act const value = evalBindingExpression(source, context); // --- Arrange expect(value).equal(3); }); it("Arrow with rest #2", () => { // --- Arrange const source = "((x, ...a) => x + a[0] + a[1])(1, 2, 3)"; const context = createEvalContext({}); // --- Act const value = evalBindingExpression(source, context); // --- Arrange expect(value).equal(6); }); }); ``` -------------------------------------------------------------------------------- /xmlui/src/components/Markdown/Markdown.md: -------------------------------------------------------------------------------- ```markdown %-DESC-START **Key features:** - **Rich formatting**: Support for headings, bold, italic, lists, links, images, blockquotes, and code blocks - **Dynamic content**: Use @{} binding expressions to inject variables and function results - **File loading**: Load markdown content from external files using the `data` property ## Acquiring content You can specify Markdown content in these ways. ### The content property Render Markdown content that you calculate or get from other components. ### The data property Render Markdown content from an URL. ### Nested text Render Markdown content that you place directly in a Markdown component. ## Whitespace and special characters Whitespace is significant in Markdown, for example headers using the `#` syntax must begin in column 1. These special XML characters are significant too. ``` < (less than) - Must be escaped as < > (greater than) - Must be escaped as > & (ampersand) - Must be escaped as & " (double quote) - Must be escaped as " in attributes ' (single quote/apostrophe) - Must be escaped as ' in attributes ``` You can use a CDATA section to avoid having to escape these characters individually. ``` <Markdown> <![CDATA[ ]]> </Markdown> ``` Or, as we have done in this page, you can use a code fence (a block delimited by triple backtics) to preserve them. ## Supported elements The `Markdown` component supports these basic elements. - Heading - Bold - Italic - Strikethrough - Blockquote - Ordered List - Unordered List - Code - Horizontal Rule - Link - Image - Table See [this markdown guide](https://www.markdownguide.org/cheat-sheet/). ## Binding Expressions Our `Markdown` component is capable of evaluating binding expressions just as other XMLUI components. Use the @{} syntax to wrap expressions that need to be evaluated. Objects, functions and arrays will be stringified if you place them in `Markdown`. Function calls are executed and their return values inlined as strings into markdown. ```xmlui-pg copy {5-9} name="Example: binding expressions syntax" <App> <variable name="x" value="{() => { return 'testing' }}" /> <Markdown> <![CDATA[ Empty elements are removed: @{} Nested objects and functions are handled: @{ { a: 1, b: () => {} } } Function calls are executed: @{x()} ]]> </Markdown> </App> ``` %-DESC-END %-STYLE-START The component itself cannot be styled, but the components that render the final text have customizable style variables. [`Text`](/components/Text#styling) [`Heading`](/components/Heading#styling) [`Link`](/components/Link#styling) [`Image`](/components/Image#styling) [`Checkbox`](/components/Checkbox#styling) %-STYLE-END %-PROP-START content Use this property when the text you provide is not static but a result of calculations (you assemble the text or get it from other components). %-PROP-END %-PROP-START removeIndents ```xmlui-pg copy display name="Example: removeIndents property" <App layout="horizontal-sticky" padding="1rem"> <Markdown removeIndents="true"> <![CDATA[ # My Adventure in Markdown Land ## The Beginning In the bustling city of Markdownville, I embarked on a journey to discover the secrets of Markdown. My adventure started in the heart of the city, where the first rule of Markdown was inscribed in stone. ]]> </Markdown> </App> ``` %-PROP-END %-PROP-START showHeadingAnchors If this property is not set, the engine checks if `showHeadingAnchors` flag is turned on in the global configuration (in the `appGlobals` configuration object) and displays the heading anchor accordingly. %-PROP-END ``` -------------------------------------------------------------------------------- /docs/content/components/FileUploadDropZone.md: -------------------------------------------------------------------------------- ```markdown # FileUploadDropZone [#fileuploaddropzone] `FileUploadDropZone` enables users to upload files by dragging and dropping files from their local file system onto a designated area within the UI. ## Using `FileUploadDropZone` [#using-fileuploaddropzone] The component provides a surface on which you can drag files or paste files from the clipboard. The following example demonstrates how to use the component. ```xmlui-pg copy display name="Example: using FileUploadDropZone" height="200px" ---app copy display <App> <H3>The cyan area below is a FileUploadDropZone</H3> <FileUploadDropZone backgroundColor="cyan" height="100px" onUpload=" (files) => { console.log(files); files.map(file => toast('file ' + file.path + ' uploaded'))}" /> </App> ---desc You can try it by dragging one or more files to the cyan surface. When you drop the file(s), the app triggers the `upload` event and displays a dialog for each file. You can also paste files from the clipboard: click the drop zone (cyan area) and then use the keyboard shortcut set on your OS. ``` ## Properties [#properties] ### `acceptedFileTypes` [#acceptedfiletypes] Accepted file MIME types, separated by commas. For example: 'image/*,application/pdf'. ### `allowPaste` (default: true) [#allowpaste-default-true] This property indicates if the drop zone accepts files pasted from the clipboard (`true`) or only dragged files (`false`). This property indicates if the drop zone accepts files pasted from the clipboard (`true`) or only dragged files (`false`). The following example sets this property to `false` and, thus, it turns off pasting files: ```xmlui-pg copy display name="Example: allowPaste" height="200px" ---app copy display <App> <H3>You cannot paste files from the clipboard</H3> <FileUploadDropZone backgroundColor="cyan" height="100px" allowPaste="false" onUpload="(files) => files.map(file => toast('file ' + file.path + ' uploaded'))" /> </App> ---desc Try it! When you copy a file to a clipboard, you cannot paste it with the keyboard shortcut of your OS. ``` ### `enabled` (default: true) [#enabled-default-true] If set to `false`, the drop zone will be disabled and users will not be able to upload files. ### `maxFiles` [#maxfiles] The maximum number of files that can be selected. ### `text` (default: "Drop files here") [#text-default-drop-files-here] With this property, you can change the default text to display when files are dragged over the drop zone. ## Events [#events] ### `upload` [#upload] This component accepts files for upload but does not perform the actual operation. It fires the `upload` event and passes the list files to upload in the method's argument. You can use the passed file information to implement the upload (according to the protocol your backend supports). Each item passed in the event argument is an instance of [File](https://developer.mozilla.org/en-US/docs/Web/API/File). ## Exposed Methods [#exposed-methods] This component does not expose any methods. ## Styling [#styling] ### Theme Variables [#theme-variables] | Variable | Default Value (Light) | Default Value (Dark) | | --- | --- | --- | | [backgroundColor](../styles-and-themes/common-units/#color)-dropping-FileUploadDropZone | $backgroundColor--selected | $backgroundColor--selected | | [backgroundColor](../styles-and-themes/common-units/#color)-FileUploadDropZone | $backgroundColor | $backgroundColor | | [opacity](../styles-and-themes/common-units/#opacity)-dropping-FileUploadDropZone | 0.5 | 0.5 | | [textColor](../styles-and-themes/common-units/#color)-FileUploadDropZone | $textColor | $textColor | ``` -------------------------------------------------------------------------------- /xmlui/src/components-core/script-runner/BindingTreeEvaluationContext.ts: -------------------------------------------------------------------------------- ```typescript import type { LogicalThread } from "../../abstractions/scripting/LogicalThread"; import type { ActionExecutionContext } from "../../abstractions/ActionDefs"; import type { ArrowExpression, Statement } from "./ScriptingSourceTree"; import type { BlockScope } from "../../abstractions/scripting/BlockScope"; /** * A function that resolves a module name to the text of the module */ export type ModuleResolver = (sourceModule: string, moduleName: string) => string | null; // This type represents the context in which binding expressions and statements should be evaluated export type BindingTreeEvaluationContext = { // --- Container scope localContext?: any; // --- Function to obtain the current working copy of the local context getLocalContext?: () => any; // --- Application context scope appContext?: any; // --- The main execution thread; mainThread?: LogicalThread; // --- The cancellation token to signal the cancellation of an operation cancellationToken?: CancellationToken; // --- Execution timeout in milliseconds timeout?: number; // --- Evaluation options options?: EvalTreeOptions; // --- Start time of the synchronous statement processing startTick?: number; // --- The values of event arguments to process in an ArrowExpressionStatement eventArgs?: any[]; // --- Cached closure contexts for arrow expressions closureContexts?: Map<ArrowExpression, BlockScope[]> // --- Use this context wrapper with function that support implicit context implicitContextGetter?: ImplicitContextGetter; // --- Function to call on updating a localContext property (directly or indirectly) onUpdateHook?: (updateFn: () => any) => Promise<any>; // --- Call this method when a non-local variable is accessed onWillAccess?: (scope: any, index: string | number) => void | Promise<void>; // --- Call this method when a non-local variable is updated onWillUpdate?: (scope: any, index: string | number, updateType: UpdateType) => void | Promise<void>; // --- Call this method after a non-local variable has been updated onDidUpdate?: (scope: any, index: string | number, updateType: UpdateType) => void | Promise<void>; // --- Sign that a particular statement has started onStatementStarted?(evalContext: BindingTreeEvaluationContext, stmt: Statement): void | Promise<void>; // --- Sign that a particular statement has completed onStatementCompleted?(evalContext: BindingTreeEvaluationContext, stmt: Statement): void | Promise<void>; }; // --- The type of non-local variable update type UpdateType = "assignment" | "pre-post" | "function-call"; /** * A token that signals the cancellation of an operation */ class CancellationToken { private _cancelled = false; public get cancelled (): boolean { return this._cancelled; } public cancel (): void { this._cancelled = true; } } // Evaluation options to use with binding tree evaluation export type EvalTreeOptions = { defaultToOptionalMemberAccess?: boolean; }; // This function gets an object to pass as an implicit context when invoking a function on "objectWithFunction" type ImplicitContextGetter = (objectWithFunction: any) => ActionExecutionContext; /** * Creates an evaluation context with the given parts * @param parts Parts of the evaluation context * @returns New evaluation context */ export function createEvalContext (parts: Partial<BindingTreeEvaluationContext>): BindingTreeEvaluationContext { return { ...{ mainThread: { childThreads: [], blocks: [{ vars: {} }], loops: [], breakLabelValue: -1 }, localContext: {} }, ...parts }; } ``` -------------------------------------------------------------------------------- /xmlui/src/components/AppState/AppState.tsx: -------------------------------------------------------------------------------- ```typescript import { createComponentRenderer } from "../../components-core/renderers"; import { createMetadata, d } from "../metadata-helpers"; import { AppState, defaultProps } from "./AppStateNative"; const COMP = "AppState"; export const AppStateMd = createMetadata({ status: "stable", description: "`AppState` is an invisible component that provides global state management " + "across your entire application. Unlike component variables that are scoped " + "locally, AppState allows any component to access and update shared state " + "without prop drilling.", events: { didUpdate: d( "This event is fired when the AppState value is updated. The event provides " + "the new state value as its parameter.", ), }, props: { bucket: { description: `This property is the identifier of the bucket to which the \`${COMP}\` instance is bound. ` + `Multiple \`${COMP}\` instances with the same bucket will share the same state object: any ` + `of them updating the state will cause the other instances to view the new, updated state.`, valueType: "string", defaultValue: defaultProps.bucket, }, initialValue: { description: `This property contains the initial state value. Though you can use multiple \`${COMP}\`` + `component instances for the same bucket with their \`initialValue\` set, it may result ` + `in faulty app logic. When xmlui instantiates an \`${COMP}\` with an explicit initial ` + `value, that value is immediately merged with the existing state. ` + `The issue may come from the behavior that \`initialValue\` is set (merged) only when a component mounts. ` + `By default, the bucket's initial state is undefined.`, }, }, apis: { update: { signature: "update(newState: Record<string, any>)", description: "This method updates the application state object bound to the `AppState` instance.", parameters: { newState: "An object that specifies the new state value.", }, }, appendToList: { signature: "appendToList(key: string, id: any)", description: "This method appends an item to an array in the application state object bound to the" + " `AppState` instance.", parameters: { key: "The key of the array in the state object.", id: "The item to append to the array.", }, }, removeFromList: { signature: "removeFromList(key: string, id: any)", description: "This method removes an item from an array in the application state object bound to the" + " `AppState` instance.", parameters: { key: "The key of the array in the state object.", id: "The item to remove from the array.", }, }, listIncludes: { signature: "listIncludes(key: string, id: any)", description: "This method checks if an array in the application state object contains a specific item.", parameters: { key: "The key of the array in the state object.", id: "The item to check for in the array.", }, }, }, nonVisual: true, }); export const appStateComponentRenderer = createComponentRenderer( COMP, AppStateMd, ({ node, extractValue, updateState, registerComponentApi, lookupEventHandler }) => { return ( <AppState bucket={extractValue(node.props.bucket)} initialValue={extractValue(node.props.initialValue)} updateState={updateState} registerComponentApi={registerComponentApi} onDidUpdate={lookupEventHandler("didUpdate")} /> ); }, ); ``` -------------------------------------------------------------------------------- /xmlui/src/components/Form/Form.md: -------------------------------------------------------------------------------- ```markdown %-DESC-START **Key features:** - **Automatic data binding**: Form controls automatically sync with form data using `bindTo` properties - **Built-in validation**: Validates individual fields and overall form state before submission - **Context sharing**: Provides `$data` and other context values accessible to all nested components - **Submission handling**: Manages form submission workflow and prevents invalid submissions See [this guide](/forms) for details. %-DESC-END %-CONTEXT_VAR-START $data The following sample demonstrates enabling a field according to another's current value. ```xmlui-pg copy {5} display name="Example: referencing field values" <App> <Form data="{{ isEnabled: true, name: 'Joe' }}" paddingHorizontal="1rem"> <FormItem label="Enable name" bindTo="isEnabled" type="switch" /> <FormItem enabled="{$data.isEnabled}" label="Name" bindTo="name" /> </Form> </App> ``` %-CONTEXT_VAR-END %-PROP-START buttonRowTemplate The following example demonstrates using it: ```xmlui-pg copy display name="Example: buttonRowTemplate" ---app copy display {10-19} <App> <Form id="searchForm" padding="0.5rem" data="{{ search: 'Seattle', caseSensitive: false }}" onSubmit="() => {isSearching = true; delay(1000); isSearching = false; }" saveLabel="Search" var.isSearching="{false}"> <Text>Please specify the name to include in the search:</Text> <FormItem bindTo="search" width="280px" /> <FormItem type="checkbox" label="Case sensitive?" bindTo="caseSensitive" /> <property name="buttonRowTemplate"> <HStack gap="0.5rem" borderTop="1px solid #ddd" paddingVertical="1rem"> <Button label="Test Search Server" type="button" themeColor="secondary" variant="outlined" onClick="toast('Search server is ok.')"/> <SpaceFiller/> <Button type="submit" enabled="{!isSearching}" icon="search" label="{isSearching ? 'Searching...' : 'Search'}"/> </HStack> </property> </Form> </App> ---desc This example mimics a one-second search and turns off the submit button during the operation. Also, it adds a Test Search Server button: ``` %-PROP-END %-EVENT-START submit ```xmlui-pg copy {4} display name="Example: submit" <App> <Form padding="0.5rem" data="{{ name: 'Joe', age: 43 }}" onSubmit="(toSave) => toast(JSON.stringify(toSave))"> <FlowLayout columnGap="12px" paddingBottom="6px"> <FormItem bindTo="name" label="Customer name" width="50%" /> <FormItem bindTo="age" label="Age" type="integer" width="50%" zeroOrPositive="true" /> </FlowLayout> </Form> </App> ``` %-EVENT-END %-API-START update This method updates the form data with the change passed in its parameter. The parameter is a hash object, and this method updates the Form's properties accordingly. ```xmlui-pg copy display name="Example: update" <App> <Form id="myForm" padding="0.5rem" data="{{ name: 'Joe', age: 43, $update: 123 }}" onSubmit="(toSave) => toast(JSON.stringify(toSave))"> <FlowLayout columnGap="12px" paddingBottom="6px"> <FormItem bindTo="name" label="Customer name" width="50%" /> <FormItem bindTo="age" label="Age" type="integer" width="50%" zeroOrPositive="true" /> </FlowLayout> <Button onClick="() => $data.update({age: $data.age + 1})" > Increment age (1) </Button> <Button onClick="() => myForm.update({age: $data.age + 1})" > Increment age (2) </Button> <Button onClick="() => myForm.update({name: $data.name + '!', age: $data.age + 1})" > Update name and age </Button> </Form> </App> ``` %-API-END ``` -------------------------------------------------------------------------------- /xmlui/scripts/generate-docs/create-theme-files.mjs: -------------------------------------------------------------------------------- ``` import { join, dirname } from "path"; import { existsSync, mkdirSync } from "fs"; import { fileURLToPath } from "url"; import { collectedThemes, collectedComponentMetadata } from "../../dist/metadata/xmlui-metadata.mjs"; import { ERROR_HANDLING, ERROR_MESSAGES } from "./constants.mjs"; import { handleFatalError, validateDependencies } from "./error-handling.mjs"; import { createScopedLogger } from "./logging-standards.mjs"; import { pathResolver } from "./configuration-management.mjs"; import { processComponentThemeVars, iterateObjectEntries, writeFileWithLogging } from "./pattern-utilities.mjs"; const OUTPUT_DIR = pathResolver.getOutputPaths().themes; const logger = createScopedLogger("ThemeGenerator"); /** * Counts the number of theme variables, excluding those that start with "---" * @param {Object} themeVars - The theme variables object * @returns {number} - The count of non-separator theme variables */ function countThemeVars(themeVars) { if (!themeVars || typeof themeVars !== 'object') { return 0; } return Object.keys(themeVars).filter(key => !key.startsWith("---")).length; } async function generateThemeFiles() { logger.operationStart("theme file generation"); try { // --- Create the output folder if (!existsSync(OUTPUT_DIR)) { mkdirSync(OUTPUT_DIR, { recursive: true }); } // Validate required dependencies validateDependencies({ THEME_INFO: collectedThemes, COMPONENT_METADATA: collectedComponentMetadata }); const rootTheme = collectedThemes.root; // Extract theme variable information from components using utility const themeVarsData = processComponentThemeVars(collectedComponentMetadata, logger); // Write theme files with error handling let totalThemeVars = 0; let exportedThemeCount = 0; await iterateObjectEntries(collectedThemes, async (themeName, themeData) => { // Skip the abstract root theme if (themeName === "root") return; // Prepare the complete theme vars object const completeThemeVars = { "--- App-bound root theme variables": "", ...rootTheme.themeVars, "--- App-bound theme-specific variables": "", ...themeData.themeVars, "--- Component-bound theme variables": "", ...themeVarsData.base, light: { ...themeData.themeVars.light, ...themeVarsData.light }, dark: { ...themeData.themeVars.dark, ...themeVarsData.dark }, }; // Count theme vars (excluding separators) const themeVarCount = countThemeVars(completeThemeVars); totalThemeVars += themeVarCount; exportedThemeCount++; const themePath = join(OUTPUT_DIR, `${themeName}.json`); const themeContent = JSON.stringify( { ...themeData, themeVars: completeThemeVars, }, null, 2, ); await writeFileWithLogging(themePath, themeContent, logger); logger.info(`Theme '${themeName}' exported with ${themeVarCount} theme variables`); }, { async: true }); // Display summary of all exported themes if (exportedThemeCount > 0) { logger.info(`\n=== Theme Export Summary ===`); logger.info(`Exported ${exportedThemeCount} theme files with a total of ${totalThemeVars} theme variables`); logger.info(`Average of ${Math.round(totalThemeVars / exportedThemeCount)} theme variables per theme file`); } logger.operationComplete("theme file generation"); } catch (error) { handleFatalError(error, ERROR_HANDLING.EXIT_CODES.GENERAL_ERROR, "theme file generation"); } } // Execute the main function generateThemeFiles(); ```