This is page 28 of 141. Use http://codebase.md/xmlui-org/xmlui/mockApiDef.js?page={x} to view the full context. # Directory Structure ``` ├── .changeset │ └── config.json ├── .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 │ ├── layout-changes.md │ ├── 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 │ │ ├── resources │ │ │ ├── favicon.ico │ │ │ ├── files │ │ │ │ └── for-download │ │ │ │ └── xmlui │ │ │ │ └── xmlui-standalone.umd.js │ │ │ ├── github.svg │ │ │ ├── llms.txt │ │ │ ├── logo-dark.svg │ │ │ ├── logo.svg │ │ │ ├── pg-popout.svg │ │ │ ├── rss.svg │ │ │ └── xmlui-logo.svg │ │ ├── serve.json │ │ └── web.config │ ├── scripts │ │ ├── download-latest-xmlui.js │ │ ├── generate-rss.js │ │ ├── get-releases.js │ │ └── utils.js │ ├── src │ │ ├── components │ │ │ ├── BlogOverview.xmlui │ │ │ ├── BlogPage.xmlui │ │ │ └── PageNotFound.xmlui │ │ ├── config.ts │ │ ├── Main.xmlui │ │ └── themes │ │ └── blog-theme.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 │ │ │ │ ├── make-navpanel-width-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 │ ├── components-with-options.md │ ├── containers.md │ ├── data-operations.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 │ ├── ud-components.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 │ │ │ ├── OptionContext.ts │ │ │ ├── Select.md │ │ │ ├── Select.module.scss │ │ │ ├── Select.spec.ts │ │ │ ├── Select.tsx │ │ │ ├── SelectContext.tsx │ │ │ └── SelectNative.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 │ │ │ ├── base64-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 -------------------------------------------------------------------------------- /docs/public/pages/working-with-text.md: -------------------------------------------------------------------------------- ```markdown # Working with Text Text elements appear in UI components such as menu items, titles, headings, labels, and descriptions. There is also a [Markdown](/components/Markdown) component for complete text documents (like this page). We'll cover `Text` here and `Markdown` in the [next chapter](/working-with-markdown). You can nest text in any component that renders its children. ```xmlui-pg display name="Example: displaying text" <App> This is text! <Button label="This is text too." /> This is more text! </App> ``` To gain more control we can use the `Text` component. ```xmlui-pg display name="Example: Using the Text component" <App> <Text fontSize="1.5rem" color="purple">This is a text!</Text> <Button label="I'm just a button" /> <Text backgroundColor="lightgreen">This is another text!</Text> </App> ``` Components that render text support theme variables. You can use them, for example, to control the styling of the heading family of components ([Heading](/components/Heading), [H1](/components/H1), [H2](/components/H2), etc). ```xmlui-pg display name="Example: Text can be styled" <App> <Theme textColor-H1 = "red" textColor-H2 = "green"> <H1>My Main Title</H1> This document contains several sections. <H2>Section Title</H2> </Theme> </App> ``` ## Specifying text content Components like `Text`, `H1` and `Badge` can display text in two ways: - **Nesting text** - **Setting the `value` property** We've seen nesting, here's an example that uses the `value` property. ```xmlui-pg display name="Example: Text and the value property" <App> <H2 value="Text Content with Properties" /> <Text value="This text is set in the 'value' property of 'Text'." /> </App> ``` >[!INFO] > With nested text, multiple consecutive spaces or newlines collapse to a single space. That doesn't happen when you set text using the `value`. Whitespace collapsing enables you to maintain source text that's broken into multiple lines for easier editing. The collapsed view renders neatly. ```xmlui-pg display name="Example: whitespace collapsing" <App> This is a long text broken into multiple lines. The source markup would be challenging to read if the entire text were specified in a single line. </App> ``` ## Binding expressions Binding expressions are placeholders for computed values. ```xmlui-pg display name="Try the reset button!" <App> Seconds of the current minute: { getDate().getSeconds() } </App> ``` Results of binding expressions are displayed with collapsed whitespace. ## Inline and block rendering When you render text, it accommodates the current layout context. If that context uses inline rendering, the text is rendered inline; otherwise, it renders as a block. In an `HStack`, text segments render inline. ```xmlui-pg copy display name="Example: inline rendering" <App> <HStack> Show me a trash <Icon name="trash"/> icon! </HStack> </App> ``` In a `VStack` they render as blocks. ```xmlui-pg copy display name="Example: block rendering" <App> <VStack> Show me a trash <Icon name="trash"/> icon! </VStack> </App> ``` ## Non-breaking spaces Use ` ` to preserve spaces in a context where they would otherwise collapse. ```xmlui-pg copy display name="Example: non-breaking spaces" <App> A series of non-breaking segments: [1 2 3 4] </App> ``` ## Long text With long text you may need to control how that text is broken into new lines (if at all) and how to handle overflows. By default a long text breaks into multiple lines. ```xmlui-pg copy display name="Example: text breaks into multiple lines" <App> <Text width="200px"> This long text does not fit into a width constraint of 200 pixels. </Text> </App> ``` If necessary, breaks occur within a word. ```xmlui-pg copy display name="Example: break within a word" <App> <Text width="200px"> ThisLongTextDoesNotFitInTheGivenConstraint of 200 pixels wide. </Text> </App> ``` ## Preserving line breaks Sometimes you want to preserve line breaks, as when inspecting a JSON object. ```xmlui-pg copy display name="Example: preserving line breaks" /preserveLinebreaks="true"/ <App var.data = "{ { apples: 3, oranges: 4 } }" > <Text preserveLinebreaks="false"> { JSON.stringify(data, null, 2) } </Text> <Text preserveLinebreaks="true"> { JSON.stringify(data, null, 2) } </Text> </App> ``` ## Variants of the Text component In addition to the theme variables that govern the `Text` component, you can use the [`variant`](/components/Text#variant) property to control styles directly. ```xmlui-pg <App> <HStack> <Text width="150px">default:</Text> <Text>This is an example text</Text> </HStack> <HStack> <Text width="150px">paragraph:</Text> <Text variant="paragraph">This is an example paragraph</Text> </HStack> <HStack> <Text width="150px">placeholder:</Text> <Text variant="placeholder"> This is an example text </Text> </HStack> <HStack> <Text width="150px">secondary:</Text> <Text variant="secondary"> This is an example text </Text> </HStack> <HStack> <Text width="150px">code:</Text> <Text variant="code"> This is an example text </Text> </HStack> <HStack> <Text width="150px">codefence + code:</Text> <Text variant="codefence"> <Text variant="code"> This is an example text </Text> </Text> </HStack> <HStack> <Text width="150px">keyboard:</Text> <Text variant="keyboard"> This is an example text </Text> </HStack> <HStack> <Text width="150px">sup:</Text> <Text> This is an example text <Text variant="sup">(with some additional text)</Text> </Text> </HStack> <HStack> <Text width="150px">sub:</Text> <Text> This is an example text <Text variant="sub">(with some additional text)</Text> </Text> </HStack> <HStack> <Text width="150px">mono:</Text> <Text variant="mono"> This is an example text </Text> </HStack> <HStack> <Text width="150px">strong:</Text> <Text variant="strong"> This is an example text </Text> </HStack> <HStack> <Text width="150px">small:</Text> <Text variant="small"> This is an example text </Text> </HStack> <HStack> <Text width="150px">caption:</Text> <Text variant="caption"> This is an example text </Text> </HStack> </App> ``` ``` -------------------------------------------------------------------------------- /xmlui/dev-docs/next/project-build.md: -------------------------------------------------------------------------------- ```markdown # XMLUI Project Build System This document explains how the XMLUI monorepo is built using Turborepo, covering the build pipeline, task orchestration, and development workflows. ## Build System Overview XMLUI uses **Turborepo** to orchestrate builds across the entire monorepo containing 12+ buildable packages. Turborepo provides: - **Parallel execution** of build tasks across workspaces - **Intelligent caching** to avoid rebuilding unchanged packages - **Task dependency management** ensuring correct build order - **Incremental builds** for faster development cycles ## Turborepo Configuration ### Core Configuration (`turbo.json`) The build pipeline is defined in the root `turbo.json` file: ```json { "pipeline": { "build": { "dependsOn": ["^build"], "outputs": ["dist/**", "build/**"] }, "build:xmlui": { "dependsOn": ["^build:xmlui"], "outputs": ["dist/**"] }, "test": { "dependsOn": ["build"], "outputs": [] }, "lint": { "outputs": [] } } } ``` ### Key Pipeline Tasks - **`build`**: Generic build task for all packages - **`build:xmlui`**: Specific build for XMLUI framework and extensions - **`test`**: Run tests (depends on build completion) - **`lint`**: Code quality checks ## Workspace Build Scripts ### Root Package Scripts The root `package.json` defines orchestration scripts: ```json { "scripts": { "build-xmlui": "turbo run build:xmlui-all", "build-vscode-extension": "turbo run xmlui-vscode#build:vsix", "test-xmlui": "turbo run build:xmlui-all test:xmlui-all", "test-xmlui-smoke": "turbo run build:xmlui-all test:xmlui-smoke", "build-extensions": "turbo run build:extension", "build-docs": "turbo run build:xmlui-all build:docs", "publish-packages": "turbo run build:xmlui-all test:xmlui-all && npm run changeset:publish" } } ``` ## Build Targets by Workspace ### 1. Core Framework (`xmlui/`) **Build Scripts:** ```json { "scripts": { "build:xmlui": "vite build --mode lib", "build:xmlui-standalone": "vite build --mode standalone", "build:xmlui-metadata": "vite build --mode metadata", "build:bin": "tsc -p tsconfig.bin.json" } } ``` **Build Outputs:** - `dist/` - Library bundle for npm distribution - `bin/` - CLI executables - Metadata files for tooling support ### 2. Extension Packages (`packages/*/`) **Common Build Scripts:** ```json { "scripts": { "build:extension": "xmlui build-lib", "build:demo": "xmlui build", "build:meta": "xmlui build-lib --mode=metadata", "build-watch": "xmlui build-lib --watch" } } ``` **Build Outputs:** - `dist/` - Extension library bundle - Demo applications (when applicable) - Component metadata ### 3. Documentation Website (`docs/`) **Build Scripts:** ```json { "scripts": { "build:docs": "xmlui build --buildMode=INLINE_ALL --withMock && xmlui zip-dist --target=dist/ui.zip" } } ``` **Build Outputs:** - Static website with inlined dependencies - Zipped distribution package - Mock service worker integration ### 4. Development Tools (`tools/`) **CLI Tool (`tools/create-app/`):** ```json { "scripts": { "build": "ncc build ./index.ts -o dist/ --minify" } } ``` **VS Code Extension (`tools/vscode/`):** ```json { "scripts": { "build:vsix": "vsce package" } } ``` ## Build Execution Flows ### Development Build ```bash # Build everything for development npm run build-xmlui # This executes: # 1. Core framework build # 2. All extension packages (in parallel) # 3. CLI tools compilation ``` ### Production Build ```bash # Full production build with tests npm run publish-packages # This executes: # 1. Build all packages # 2. Run complete test suite # 3. Publish to npm (if tests pass) ``` ### Incremental Development ```bash # Watch mode for specific package cd packages/xmlui-animations npm run build-watch # Or build only changed packages turbo run build --filter=...@main ``` ## Build Dependencies ### Inter-Package Dependencies ```mermaid graph TD Core[xmlui] --> Ext1[xmlui-animations] Core --> Ext2[xmlui-devtools] Core --> Ext3[xmlui-playground] Core --> Docs[xmlui-docs] Ext3 --> Docs Core --> Tests[xmlui-e2e] Core --> CLI[create-xmlui-app] ``` ### Build Order 1. **Core Framework** (`xmlui`) - Must build first 2. **Extension Packages** - Can build in parallel after core 3. **Applications** - Documentation and test bed 4. **Tools** - CLI and VS Code extension ## Caching Strategy ### Turborepo Cache Turborepo automatically caches build outputs based on: - **Input files** - Source code, configuration files - **Dependencies** - package.json, lock files - **Environment** - Node version, environment variables ### Cache Locations - **Local cache**: `.turbo/cache/` - **Remote cache**: Configured via `TURBO_TOKEN` (optional) ### Cache Invalidation Cache is invalidated when: - Source files change - Dependencies are updated - Build configuration changes - Environment variables change ## Development Workflows ### First-Time Setup ```bash # 1. Install dependencies npm install # 2. Build documentation (includes framework) npm run build-docs # 3. Build complete framework npm run build-xmlui # 4. Run tests npm run test-xmlui-smoke ``` ### Daily Development ```bash # Build only changed packages turbo run build --filter=...@HEAD~1 # Start development server cd docs && npm start # Build specific package turbo run build --filter=xmlui-animations ``` ### Pre-Release ```bash # Complete build and test npm run test-xmlui # Build documentation npm run build-docs # Package VS Code extension npm run build-vscode-extension ``` ## Performance Optimization ### Parallel Execution Turborepo runs tasks in parallel when dependencies allow: ```bash # These run in parallel: - xmlui-animations build - xmlui-devtools build - xmlui-pdf build # (after xmlui core completes) ``` ### Build Filtering Target specific packages or changes: ```bash # Build only animation package and its dependencies turbo run build --filter=xmlui-animations # Build packages changed since main branch turbo run build --filter=...@main # Build specific scope turbo run build --filter=./packages/* ``` ### Watch Mode For active development: ```bash # Watch core framework cd xmlui && npm run build:xmlui-watch # Watch specific extension cd packages/xmlui-playground && npm run build-watch ``` This build system ensures efficient, reliable builds across the entire XMLUI ecosystem while supporting both development and production workflows. ``` -------------------------------------------------------------------------------- /docs/content/components/NavGroup.md: -------------------------------------------------------------------------------- ```markdown # NavGroup [#navgroup] `NavGroup` creates collapsible containers for organizing related navigation items into hierarchical menu structures. It groups `NavLink` components and other `NavGroup` components, providing expandable submenus with customizable icons and states. **Key features:** - **Hierarchical organization**: Creates nested menu structures by containing NavLinks and other NavGroups - **Expand/collapse behavior**: Users can toggle visibility of grouped navigation items - **Customizable icons**: Different icons for expanded/collapsed states and layout orientations - **Flexible placement**: Works within NavPanel for app navigation or standalone for custom menus - **Initial state control**: Configure whether groups start expanded or collapsed ## Using `NavGroup` [#using-navgroup] The primary use of a `NavGroup` is to create an application menu with submenus, as the following example shows: ```xmlui-pg copy display name="Example: NavGroup in App" height="280px" ---app <App layout="condensed"> <NavPanel> <NavLink label="Home" to="/" icon="home"/> <NavGroup label="Pages"> <NavLink label="Page 1" to="/page/1"/> <NavGroup label="Page 2-4"> <NavLink label="Page 2" to="/page/2"/> <NavLink label="Page 3" to="/page/3"/> <NavLink label="Page 4" to="/page/4"/> </NavGroup> <NavLink label="Page 5" to="/page/5"/> <NavLink label="Page Other" to="/page/Other"/> </NavGroup> </NavPanel> <Pages fallbackPath="/"> <Page url="/"> Home </Page> <Page url="/page/:id"> <Text value="Page {$routeParams.id}" /> </Page> </Pages> </App> ---desc Here, the highlighted `NavGroup` element nests other `NavLink` and `NavGroup` elements to create a hierarchical menu: ``` You do not have to use `NavGroup` within `NavPanel`; you can nest it into other components to represent a menu, like in the following example: ```xmlui-pg copy display name="Example: NavGroup in a Stack" height="280px" <App> <HStack verticalAlignment="center"> <Text>Use this menu:</Text> <NavGroup label="Pages"> <NavLink label="Page 1" /> <NavGroup label="Page 2-4"> <NavLink label="Page 2" /> <NavLink label="Page 3" /> <NavLink label="Page 4" /> </NavGroup> <NavLink label="Page 5" /> <NavLink label="Page Other" /> </NavGroup> </HStack> </App> ``` ### Custom Icons [#custom-icons] You can also provide custom icons for a specific NavGroup component via it's respective property: - [iconHorizontalCollapsed](#iconHorizontalCollapsed) - [iconHorizontalExpanded](#iconHorizontalExpanded) - [iconVerticalCollapsed](#iconVerticalCollapsed) - [iconVerticalExpanded](#iconVerticalExpanded) See the following for an example of all variants: ```xmlui-pg copy display name="Example: custom icons in horizontal layout" height="250px" <App layout="horizontal"> <NavGroup icon="email" label="Send To" iconVerticalExpanded="arrowup" iconVerticalCollapsed="arrowbottom"> <NavLink icon="arrowup" label="Boss" /> <NavGroup icon="users" label="Team" iconHorizontalExpanded="arrowleft" iconHorizontalCollapsed="arrowright"> <NavLink label="Jane" /> <NavLink label="Will" /> </NavGroup> <NavLink icon="cube" label="Support" /> </NavGroup> </App> ``` ## Properties [#properties] ### `enabled` (default: true) [#enabled-default-true] This boolean property value indicates whether the component responds to user events (`true`) or not (`false`). ### `icon` [#icon] This property defines an optional icon to display along with the `NavGroup` label. Look at this example: ```xmlui-pg copy {3, 5} display name="Example: label and icon" height="280px" <App> <HStack verticalAlignment="center"> <NavGroup icon="email" label="Send To" > <NavLink icon="arrowup" label="Boss" /> <NavGroup icon="users" label="Team"> <NavLink label="Jane" /> <NavLink label="Will" /> <NavLink label="Sandra" /> </NavGroup> <NavLink icon="cube" label="Support" /> </NavGroup> </HStack> </App> ``` ### `iconHorizontalCollapsed` (default: "chevronright") [#iconhorizontalcollapsed-default-chevronright] Set a custom icon to display when the navigation menu is collapsed, is in a **horizontal** app layout, and is in a navigation submenu. For an example, see the [Custom Icons section](#custom-icons). ### `iconHorizontalExpanded` (default: "chevronright") [#iconhorizontalexpanded-default-chevronright] Set a custom icon to display when the navigation menu is expanded, is in a **horizontal** app layout, and is in a navigation submenu. For an example, see the [Custom Icons section](#custom-icons). ### `iconVerticalCollapsed` (default: "chevronright") [#iconverticalcollapsed-default-chevronright] Set a custom icon to display when the navigation menu is collapsed, is in a **vertical** app layout, or is in a **horizontal** layout and is the top-level navigation item in the menu. For an example, see the [Custom Icons section](#custom-icons). ### `iconVerticalExpanded` (default: "chevrondown") [#iconverticalexpanded-default-chevrondown] Set a custom icon to display when the navigation menu is expanded, is in a **vertical** app layout, or is in a **horizontal** layout and is the top-level navigation item in the menu. For an example, see the [Custom Icons section](#custom-icons). ### `initiallyExpanded` [#initiallyexpanded] This property defines whether the group is initially expanded or collapsed. If not defined, the group is collapsed by default. ### `label` [#label] This property sets the label of the component. If not set, the component will not display a label. This property sets the text displayed as the name of the `NavGroup`. For an example, see the [section on the icon property](#icon). ### `to` [#to] This property defines an optional navigation link. ## Events [#events] This component does not have any events. ## 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)-dropdown-NavGroup | $backgroundColor-primary | $backgroundColor-primary | | [borderRadius](../styles-and-themes/common-units/#border-rounding)-dropdown-NavGroup | $borderRadius | $borderRadius | | [boxShadow](../styles-and-themes/common-units/#boxShadow)-dropdown-NavGroup | $boxShadow-spread | $boxShadow-spread | ``` -------------------------------------------------------------------------------- /xmlui/src/components/Slider/Slider.tsx: -------------------------------------------------------------------------------- ```typescript import { Slider } from "./SliderNative"; import styles from "./Slider.module.scss"; import { createComponentRenderer } from "../../components-core/renderers"; import { parseScssVar } from "../../components-core/theming/themeVars"; import { createMetadata, d, dAutoFocus, dDidChange, dEnabled, dGotFocus, dInitialValue, dLostFocus, dReadonly, dRequired, dValidationStatus, } from "../metadata-helpers"; const COMP = "Slider"; export const SliderMd = createMetadata({ status: "stable", description: "`Slider` provides an interactive control for selecting numeric values within " + "a defined range, supporting both single value selection and range selection with " + "multiple thumbs. It offers precise control through customizable steps and visual " + "feedback with formatted value display." + "\n\n" + "Hover over the component to see the tooltip with the current value. On mobile, tap the thumb to see the tooltip.", props: { initialValue: dInitialValue(), minValue: { description: `This property specifies the minimum value of the allowed input range.`, valueType: "number", defaultValue: 0, }, maxValue: { description: `This property specifies the maximum value of the allowed input range.`, valueType: "number", defaultValue: 10, }, step: { description: `This property defines the increment value for the slider, determining the allowed intervals between selectable values.`, defaultValue: 1, }, minStepsBetweenThumbs: d( `This property sets the minimum number of steps required between multiple thumbs on the slider, ensuring they maintain a specified distance.`, undefined, "number", 1, ), enabled: dEnabled(), autoFocus: dAutoFocus(), required: dRequired(), readOnly: dReadonly(), validationStatus: { ...dValidationStatus(), defaultValue: "none", }, rangeStyle: d( `This optional property allows you to apply custom styles to the range element of the slider.`, ), thumbStyle: d( `This optional property allows you to apply custom styles to the thumb elements of the slider.`, ), showValues: { description: `This property controls whether the slider shows the current values of the thumbs.`, valueType: "boolean", defaultValue: true, }, valueFormat: { description: `This property allows you to customize how the values are displayed.`, valueType: "any", defaultValue: "(value) => value.toString()", }, }, events: { didChange: dDidChange(COMP), gotFocus: dGotFocus(COMP), lostFocus: dLostFocus(COMP), }, apis: { focus: { description: `This method sets the focus on the slider component.`, signature: "focus(): void", }, value: { description: `This API retrieves the current value of the \`${COMP}\`. You can use it to get the value programmatically.`, signature: "get value(): number | [number, number] | undefined", }, setValue: { description: `This API sets the value of the \`${COMP}\`. You can use it to programmatically change the value.`, signature: "setValue(value: number | [number, number] | undefined): void", parameters: { value: "The new value to set. Can be a single value or an array of values for range sliders.", }, }, }, themeVars: parseScssVar(styles.themeVars), defaultThemeVars: { [`backgroundColor-track-${COMP}`]: "$color-surface-200", [`backgroundColor-range-${COMP}`]: "$color-primary", [`borderWidth-thumb-${COMP}`]: "2px", [`borderStyle-thumb-${COMP}`]: "solid", [`borderColor-thumb-${COMP}`]: "$color-surface-50", [`backgroundColor-thumb-${COMP}`]: "$color-primary", [`backgroundColor-thumb-${COMP}--focus`]: "$color-primary", [`boxShadow-thumb-${COMP}--focus`]: "0 0 0 6px rgb(from $color-primary r g b / 0.4)", [`backgroundColor-thumb-${COMP}--hover`]: "$color-primary", [`boxShadow-thumb-${COMP}--hover`]: "0 0 0 6px rgb(from $color-primary r g b / 0.4)", [`backgroundColor-thumb-${COMP}--active`]: "$color-primary-400", [`boxShadow-thumb-${COMP}--active`]: "0 0 0 6px rgb(from $color-primary r g b / 0.4)", [`borderRadius-${COMP}-default`]: "$borderRadius", [`borderColor-${COMP}-default`]: "transparent", [`borderWidth-${COMP}-default`]: "0", [`borderStyle-${COMP}-default`]: "solid", [`boxShadow-${COMP}-default`]: "none", light: { [`backgroundColor-track-${COMP}--disabled`]: "$color-surface-300", [`backgroundColor-range-${COMP}--disabled`]: "$color-surface-400", [`backgroundColor-thumb-${COMP}`]: "$color-primary-500", [`borderColor-thumb-${COMP}`]: "$color-surface-50", }, dark: { [`backgroundColor-track-${COMP}--disabled`]: "$color-surface-600", [`backgroundColor-range-${COMP}--disabled`]: "$color-surface-800", [`backgroundColor-thumb-${COMP}`]: "$color-primary-400", [`borderColor-thumb-${COMP}`]: "$color-surface-950", }, }, }); export const sliderComponentRenderer = createComponentRenderer( COMP, SliderMd, ({ node, extractValue, lookupEventHandler, lookupSyncCallback, className, updateState, state, registerComponentApi, }) => { return ( <Slider validationStatus={extractValue(node.props.validationStatus)} minStepsBetweenThumbs={extractValue(node.props?.minStepsBetweenThumbs)} value={state.value} initialValue={extractValue(node.props.initialValue)} updateState={updateState} onDidChange={lookupEventHandler("didChange")} onFocus={lookupEventHandler("gotFocus")} onBlur={lookupEventHandler("lostFocus")} registerComponentApi={registerComponentApi} className={className} step={extractValue(node.props?.step)} min={extractValue(node.props?.minValue)} max={extractValue(node.props?.maxValue)} enabled={extractValue.asOptionalBoolean(node.props?.enabled)} autoFocus={extractValue.asOptionalBoolean(node.props.autoFocus)} readOnly={extractValue.asOptionalBoolean(node.props.readOnly)} required={extractValue.asOptionalBoolean(node.props.required)} rangeStyle={extractValue(node.props?.rangeStyle)} thumbStyle={extractValue(node.props?.thumbStyle)} showValues={extractValue.asOptionalBoolean(node.props?.showValues)} valueFormat={lookupSyncCallback(node.props?.valueFormat)} /> ); }, ); ``` -------------------------------------------------------------------------------- /xmlui/src/components-core/theming/component-layout-resolver.ts: -------------------------------------------------------------------------------- ```typescript import type { CSSProperties } from "react"; import type { LayoutContext } from "../../abstractions/RendererDefs"; import { EMPTY_OBJECT } from "../constants"; import { parseLayoutProperty } from "./parse-layout-props"; export type ResolvedComponentLayout = Record<string, ResolvedPartLayout>; export type ResolvedPartLayout = { baseStyles?: CssPropsWithStates; responsiveStyles?: Record<string, CssPropsWithStates>; }; export type CssPropsWithStates = CSSProperties & { states?: Record<string, CSSProperties>; }; export const THEME_VAR_PREFIX = "xmlui"; export const BASE_COMPONENT_PART = "_base_"; const themeVarCapturesRegex = /(\$[a-zA-Z][a-zA-Z0-9-_]*)/g; const starSizeRegex = /^\d*\*$/; export function resolveComponentLayoutProps( layoutProps: Record<string, any> = EMPTY_OBJECT, layoutContext?: LayoutContext, ): ResolvedComponentLayout { const result: ResolvedComponentLayout = {}; for (const [key, value] of Object.entries(layoutProps)) { const parsed = parseLayoutProperty(key, false); if (typeof parsed === "string") { // --- Ignore failed properties continue; } // --- Process the properties, resolve theme variables to CSS variables const cssPropName = parsed.property; const appliedValue = value ?.toString() ?.replace(themeVarCapturesRegex, (match: string) => toCssVar(match.trim())); // --- Some properties may need transformation const cssProps: CSSProperties = cssPropName in specialResolvers ? specialResolvers[cssPropName](appliedValue, layoutContext) : { [cssPropName]: appliedValue }; // --- Check if the property belongs to one or more states const stateName = parsed.states && parsed.states.length > 0 ? parsed.states.join("&") : null; // --- Prepare the place to store the styles. It belongs to the specified part. const partName = parsed.part ? parsed.part : BASE_COMPONENT_PART; let propertyTarget: any = (result[partName] ??= {}); if (parsed.screenSizes && parsed.screenSizes.length > 0) { const screenSizeKey = parsed.screenSizes.join("&"); propertyTarget.responsiveStyles ??= {}; propertyTarget.responsiveStyles[screenSizeKey] ??= {}; if (stateName) { propertyTarget.responsiveStyles[screenSizeKey].states ??= {}; propertyTarget.responsiveStyles[screenSizeKey].states = { ...propertyTarget.responsiveStyles[screenSizeKey].states, [stateName]: { ...propertyTarget.responsiveStyles[screenSizeKey].states[stateName], ...cssProps, }, }; } else { propertyTarget.responsiveStyles[screenSizeKey] = { ...propertyTarget.responsiveStyles[screenSizeKey], ...cssProps, }; } } else { // --- No screen sizes specified, the property belongs to the base styles propertyTarget.baseStyles ??= {}; if (stateName) { propertyTarget.baseStyles.states ??= {}; propertyTarget.baseStyles.states = { ...propertyTarget.baseStyles.states, [stateName]: { ...propertyTarget.baseStyles.states[stateName], ...cssProps }, }; } else { propertyTarget.baseStyles = { ...propertyTarget.baseStyles, ...cssProps }; } } } // --- Done return result; } // --- Checks if the specified size is a star size and the orientation is horizontal function getHorizontalStarSize( size: string | number, layoutContext?: LayoutContext, ): number | null { if (!size) return null; const orientation = getOrientation(layoutContext); return orientation === "horizontal" && starSizeRegex.test(size.toString()) ? getStarSizeNumber(size.toString()) : null; } // --- Checks if the specified size is a star size and the orientation is vertical function getVerticalStarSize(size: string | number, layoutContext?: LayoutContext): number | null { if (!size) return null; const orientation = getOrientation(layoutContext); return orientation === "vertical" && starSizeRegex.test(size.toString()) ? getStarSizeNumber(size.toString()) : null; } // --- Obtains the integer number from a string that matches the starSizeRegex. function getStarSizeNumber(input: string): number | null { if (starSizeRegex.test(input)) { const numberPart = input.slice(0, -1); // Remove the trailing '*' return numberPart === "" ? 1 : parseInt(numberPart, 10); // Default to 1 if no number is present } return null; } // --- Gets the current orientation from the layout context function getOrientation(layoutContext?: LayoutContext): string | undefined { if (!layoutContext) return; let orientation = layoutContext?.type === "Stack" && layoutContext?.orientation; return orientation?.toString(); } function toCssVar(c: string): string { return `var(--${THEME_VAR_PREFIX}-${c.substring(1)})`; } type SpecialResolver = (propValue: any, layoutContext?: LayoutContext) => CSSProperties; export const specialResolvers: Record<string, SpecialResolver> = { paddingHorizontal: (propValue) => ({ paddingLeft: propValue, paddingRight: propValue, }), paddingVertical: (propValue) => ({ paddingTop: propValue, paddingBottom: propValue, }), marginHorizontal: (propValue) => ({ marginLeft: propValue, marginRight: propValue, }), marginVertical: (propValue) => ({ marginTop: propValue, marginBottom: propValue, }), borderHorizontal: (propValue) => ({ borderLeft: propValue, borderRight: propValue, }), borderVertical: (propValue) => ({ borderTop: propValue, borderBottom: propValue, }), wrapContent: (propValue) => ({ flexWrap: propValue === "true" ? "wrap" : "nowrap", }), canShrink: (propValue) => ({ flexShrink: propValue === "true" ? 1 : 0, }), width: (propValue, layoutContext) => { const horizontalStarSize = getHorizontalStarSize(propValue, layoutContext); const result: CSSProperties = {}; if (horizontalStarSize !== null) { // --- We use "flex" when width is in start-size and allow shrinking result.flex = horizontalStarSize; result.flexShrink = 1; } else { result.width = propValue; } return result; }, height: (propValue, layoutContext) => { const verticalStarSize = getVerticalStarSize(propValue, layoutContext); const result: CSSProperties = {}; if (verticalStarSize !== null) { // --- We use "flex" when height is in start-size and allow shrinking result.flex = verticalStarSize; result.flexShrink = 1; } else { result.height = propValue; } return result; }, }; ``` -------------------------------------------------------------------------------- /docs/content/components/xmlui-website-blocks/Carousel.md: -------------------------------------------------------------------------------- ```markdown # Carousel [#carousel] This component displays a slideshow by cycling through elements (images, text, or custom slides) like a carousel. ## Properties ### `autoplay` (default: false) Start scrolling the carousel automatically (`true`) or not (`false`). ### `autoplayInterval` (default: 5000) Specifies the interval between autoplay transitions. ### `controls` (default: true) Display the previous/next controls (`true`) or not (`false`). ### `indicators` (default: true) Display the individual slides as buttons (`true`) or not (`false`). ### `loop` (default: false) Sets whether the carousel should loop back to the start/end when it reaches the last/first slide. ### `nextIcon` The icon to display for the next control. ### `orientation` (default: "horizontal") This property indicates the orientation of the carousel. The `horizontal` value indicates that the carousel moves horizontally, and the `vertical` value indicates that the carousel moves vertically. Available values: `horizontal` **(default)**, `vertical` ### `prevIcon` The icon to display for the previous control. ### `startIndex` (default: 0) The index of the first slide to display. ### `stopAutoplayOnInteraction` (default: true) This property indicates whether autoplay stops on user interaction. ### `transitionDuration` (default: 25) The duration of the transition between slides. ## Events ### `displayDidChange` This event is fired when the displayed content of the CarouselNew changes. ## Exposed Methods ### `canScrollNext` This method returns `true` if the carousel can scroll to the next slide. **Signature**: `canScrollNext(): boolean` ### `canScrollPrev` This method returns `true` if the carousel can scroll to the previous slide. **Signature**: `canScrollPrev(): boolean` ### `scrollNext` This method scrolls the carousel to the next slide. **Signature**: `scrollNext(): void` ### `scrollPrev` This method scrolls the carousel to the previous slide. **Signature**: `scrollPrev(): void` ### `scrollTo` This method scrolls the carousel to the specified slide index. **Signature**: `scrollTo(index: number): void` - `index`: The index of the slide to scroll to. ## Styling ### Theme Variables | Variable | Default Value (Light) | Default Value (Dark) | | --- | --- | --- | | [backgroundColor](../styles-and-themes/common-units/#color)-control-active-Carousel | *none* | *none* | | [backgroundColor](../styles-and-themes/common-units/#color)-control-active-CarouselNew | $color-primary | $color-primary | | [backgroundColor](../styles-and-themes/common-units/#color)-control-Carousel | *none* | *none* | | [backgroundColor](../styles-and-themes/common-units/#color)-control-CarouselNew | $color-primary | $color-primary | | [backgroundColor](../styles-and-themes/common-units/#color)-control-disabled-Carousel | *none* | *none* | | [backgroundColor](../styles-and-themes/common-units/#color)-control-disabled-CarouselNew | $color-surface-200 | $color-surface-200 | | [backgroundColor](../styles-and-themes/common-units/#color)-control-hover-Carousel | *none* | *none* | | [backgroundColor](../styles-and-themes/common-units/#color)-control-hover-CarouselNew | $color-primary | $color-primary | | [backgroundColor](../styles-and-themes/common-units/#color)-indicator-active-Carousel | *none* | *none* | | [backgroundColor](../styles-and-themes/common-units/#color)-indicator-active-CarouselNew | $color-primary | $color-primary | | [backgroundColor](../styles-and-themes/common-units/#color)-indicator-Carousel | *none* | *none* | | [backgroundColor](../styles-and-themes/common-units/#color)-indicator-CarouselNew | $color-surface-200 | $color-surface-200 | | [backgroundColor](../styles-and-themes/common-units/#color)-indicator-hover-Carousel | *none* | *none* | | [backgroundColor](../styles-and-themes/common-units/#color)-indicator-hover-CarouselNew | $color-surface-200 | $color-surface-200 | | [borderRadius](../styles-and-themes/common-units/#border-rounding)-control-Carousel | *none* | *none* | | [borderRadius](../styles-and-themes/common-units/#border-rounding)-control-CarouselNew | 50% | 50% | | [height](../styles-and-themes/common-units/#size)-Carousel | *none* | *none* | | [height](../styles-and-themes/common-units/#size)-CarouselNew | 100% | 100% | | [height](../styles-and-themes/common-units/#size)-control-Carousel | *none* | *none* | | [height](../styles-and-themes/common-units/#size)-control-CarouselNew | 36px | 36px | | [height](../styles-and-themes/common-units/#size)-indicator-Carousel | *none* | *none* | | [height](../styles-and-themes/common-units/#size)-indicator-CarouselNew | 6px | 6px | | [textColor](../styles-and-themes/common-units/#color)-control-active-Carousel | *none* | *none* | | [textColor](../styles-and-themes/common-units/#color)-control-active-CarouselNew | $color-primary | $color-primary | | [textColor](../styles-and-themes/common-units/#color)-control-Carousel | *none* | *none* | | [textColor](../styles-and-themes/common-units/#color)-control-CarouselNew | $textColor | $textColor | | [textColor](../styles-and-themes/common-units/#color)-control-disabled-Carousel | *none* | *none* | | [textColor](../styles-and-themes/common-units/#color)-control-disabled-CarouselNew | $textColor-disabled | $textColor-disabled | | [textColor](../styles-and-themes/common-units/#color)-control-hover-Carousel | *none* | *none* | | [textColor](../styles-and-themes/common-units/#color)-control-hover-CarouselNew | $textColor | $textColor | | [textColor](../styles-and-themes/common-units/#color)-indicator-active-Carousel | *none* | *none* | | [textColor](../styles-and-themes/common-units/#color)-indicator-active-CarouselNew | $color-primary | $color-primary | | [textColor](../styles-and-themes/common-units/#color)-indicator-Carousel | *none* | *none* | | [textColor](../styles-and-themes/common-units/#color)-indicator-CarouselNew | $color-primary | $color-primary | | [textColor](../styles-and-themes/common-units/#color)-indicator-hover-Carousel | *none* | *none* | | [textColor](../styles-and-themes/common-units/#color)-indicator-hover-CarouselNew | $color-primary | $color-primary | | [width](../styles-and-themes/common-units/#size)-Carousel | *none* | *none* | | [width](../styles-and-themes/common-units/#size)-CarouselNew | 100% | 100% | | [width](../styles-and-themes/common-units/#size)-control-Carousel | *none* | *none* | | [width](../styles-and-themes/common-units/#size)-control-CarouselNew | 36px | 36px | | [width](../styles-and-themes/common-units/#size)-indicator-Carousel | *none* | *none* | | [width](../styles-and-themes/common-units/#size)-indicator-CarouselNew | 25px | 25px | ``` -------------------------------------------------------------------------------- /docs/content/extensions/xmlui-website-blocks/Carousel.md: -------------------------------------------------------------------------------- ```markdown # Carousel [#carousel] This component displays a slideshow by cycling through elements (images, text, or custom slides) like a carousel. ## Properties ### `autoplay` (default: false) Start scrolling the carousel automatically (`true`) or not (`false`). ### `autoplayInterval` (default: 5000) Specifies the interval between autoplay transitions. ### `controls` (default: true) Display the previous/next controls (`true`) or not (`false`). ### `indicators` (default: true) Display the individual slides as buttons (`true`) or not (`false`). ### `loop` (default: false) Sets whether the carousel should loop back to the start/end when it reaches the last/first slide. ### `nextIcon` The icon to display for the next control. ### `orientation` (default: "horizontal") This property indicates the orientation of the carousel. The `horizontal` value indicates that the carousel moves horizontally, and the `vertical` value indicates that the carousel moves vertically. Available values: `horizontal` **(default)**, `vertical` ### `prevIcon` The icon to display for the previous control. ### `startIndex` (default: 0) The index of the first slide to display. ### `stopAutoplayOnInteraction` (default: true) This property indicates whether autoplay stops on user interaction. ### `transitionDuration` (default: 25) The duration of the transition between slides. ## Events ### `displayDidChange` This event is fired when the displayed content of the CarouselNew changes. ## Exposed Methods ### `canScrollNext` This method returns `true` if the carousel can scroll to the next slide. **Signature**: `canScrollNext(): boolean` ### `canScrollPrev` This method returns `true` if the carousel can scroll to the previous slide. **Signature**: `canScrollPrev(): boolean` ### `scrollNext` This method scrolls the carousel to the next slide. **Signature**: `scrollNext(): void` ### `scrollPrev` This method scrolls the carousel to the previous slide. **Signature**: `scrollPrev(): void` ### `scrollTo` This method scrolls the carousel to the specified slide index. **Signature**: `scrollTo(index: number): void` - `index`: The index of the slide to scroll to. ## Styling ### Theme Variables | Variable | Default Value (Light) | Default Value (Dark) | | --- | --- | --- | | [backgroundColor](../styles-and-themes/common-units/#color)-control-active-Carousel | *none* | *none* | | [backgroundColor](../styles-and-themes/common-units/#color)-control-active-CarouselNew | $color-primary | $color-primary | | [backgroundColor](../styles-and-themes/common-units/#color)-control-Carousel | *none* | *none* | | [backgroundColor](../styles-and-themes/common-units/#color)-control-CarouselNew | $color-primary | $color-primary | | [backgroundColor](../styles-and-themes/common-units/#color)-control-disabled-Carousel | *none* | *none* | | [backgroundColor](../styles-and-themes/common-units/#color)-control-disabled-CarouselNew | $color-surface-200 | $color-surface-200 | | [backgroundColor](../styles-and-themes/common-units/#color)-control-hover-Carousel | *none* | *none* | | [backgroundColor](../styles-and-themes/common-units/#color)-control-hover-CarouselNew | $color-primary | $color-primary | | [backgroundColor](../styles-and-themes/common-units/#color)-indicator-active-Carousel | *none* | *none* | | [backgroundColor](../styles-and-themes/common-units/#color)-indicator-active-CarouselNew | $color-primary | $color-primary | | [backgroundColor](../styles-and-themes/common-units/#color)-indicator-Carousel | *none* | *none* | | [backgroundColor](../styles-and-themes/common-units/#color)-indicator-CarouselNew | $color-surface-200 | $color-surface-200 | | [backgroundColor](../styles-and-themes/common-units/#color)-indicator-hover-Carousel | *none* | *none* | | [backgroundColor](../styles-and-themes/common-units/#color)-indicator-hover-CarouselNew | $color-surface-200 | $color-surface-200 | | [borderRadius](../styles-and-themes/common-units/#border-rounding)-control-Carousel | *none* | *none* | | [borderRadius](../styles-and-themes/common-units/#border-rounding)-control-CarouselNew | 50% | 50% | | [height](../styles-and-themes/common-units/#size)-Carousel | *none* | *none* | | [height](../styles-and-themes/common-units/#size)-CarouselNew | 100% | 100% | | [height](../styles-and-themes/common-units/#size)-control-Carousel | *none* | *none* | | [height](../styles-and-themes/common-units/#size)-control-CarouselNew | 36px | 36px | | [height](../styles-and-themes/common-units/#size)-indicator-Carousel | *none* | *none* | | [height](../styles-and-themes/common-units/#size)-indicator-CarouselNew | 6px | 6px | | [textColor](../styles-and-themes/common-units/#color)-control-active-Carousel | *none* | *none* | | [textColor](../styles-and-themes/common-units/#color)-control-active-CarouselNew | $color-primary | $color-primary | | [textColor](../styles-and-themes/common-units/#color)-control-Carousel | *none* | *none* | | [textColor](../styles-and-themes/common-units/#color)-control-CarouselNew | $textColor | $textColor | | [textColor](../styles-and-themes/common-units/#color)-control-disabled-Carousel | *none* | *none* | | [textColor](../styles-and-themes/common-units/#color)-control-disabled-CarouselNew | $textColor-disabled | $textColor-disabled | | [textColor](../styles-and-themes/common-units/#color)-control-hover-Carousel | *none* | *none* | | [textColor](../styles-and-themes/common-units/#color)-control-hover-CarouselNew | $textColor | $textColor | | [textColor](../styles-and-themes/common-units/#color)-indicator-active-Carousel | *none* | *none* | | [textColor](../styles-and-themes/common-units/#color)-indicator-active-CarouselNew | $color-primary | $color-primary | | [textColor](../styles-and-themes/common-units/#color)-indicator-Carousel | *none* | *none* | | [textColor](../styles-and-themes/common-units/#color)-indicator-CarouselNew | $color-primary | $color-primary | | [textColor](../styles-and-themes/common-units/#color)-indicator-hover-Carousel | *none* | *none* | | [textColor](../styles-and-themes/common-units/#color)-indicator-hover-CarouselNew | $color-primary | $color-primary | | [width](../styles-and-themes/common-units/#size)-Carousel | *none* | *none* | | [width](../styles-and-themes/common-units/#size)-CarouselNew | 100% | 100% | | [width](../styles-and-themes/common-units/#size)-control-Carousel | *none* | *none* | | [width](../styles-and-themes/common-units/#size)-control-CarouselNew | 36px | 36px | | [width](../styles-and-themes/common-units/#size)-indicator-Carousel | *none* | *none* | | [width](../styles-and-themes/common-units/#size)-indicator-CarouselNew | 25px | 25px | ``` -------------------------------------------------------------------------------- /xmlui/src/components/Timer/TimerNative.tsx: -------------------------------------------------------------------------------- ```typescript import type { CSSProperties, ForwardedRef } from "react"; import { forwardRef, useEffect, useRef, useState, useCallback, useMemo } from "react"; import type { RegisterComponentApiFn } from "../../abstractions/RendererDefs"; export interface TimerApi { pause(): void; resume(): void; isPaused(): boolean; isRunning(): boolean; } export const defaultProps = { enabled: true, interval: 1000, once: false, initialDelay: 0, }; type TimerProps = { enabled?: boolean; interval?: number; once?: boolean; initialDelay?: number; onTick?: () => void | Promise<void>; registerComponentApi?: RegisterComponentApiFn; style?: CSSProperties; className?: string; } & React.HTMLAttributes<HTMLDivElement>; export const Timer = forwardRef(function Timer( { enabled = defaultProps.enabled, interval = defaultProps.interval, once = defaultProps.once, initialDelay = defaultProps.initialDelay, onTick, registerComponentApi, style, className, ...rest }: TimerProps, forwardedRef: ForwardedRef<HTMLDivElement>, ) { const [isPaused, setIsPaused] = useState(false); const [hasExecutedOnce, setHasExecutedOnce] = useState(false); const [hasEverStarted, setHasEverStarted] = useState(false); const intervalRef = useRef<NodeJS.Timeout | null>(null); const initialDelayRef = useRef<NodeJS.Timeout | null>(null); const handlerRunningRef = useRef(false); // Refs for current values to ensure handleTick has stable dependencies const enabledRef = useRef(enabled); const isPausedRef = useRef(isPaused); const intervalRef2 = useRef(interval); const onTickRef = useRef(onTick); const onceRef = useRef(once); const hasExecutedOnceRef = useRef(hasExecutedOnce); const hasEverStartedRef = useRef(hasEverStarted); // Update refs when values change enabledRef.current = enabled; isPausedRef.current = isPaused; intervalRef2.current = interval; onTickRef.current = onTick; onceRef.current = once; hasExecutedOnceRef.current = hasExecutedOnce; hasEverStartedRef.current = hasEverStarted; // Derived state const isRunning = enabled && !isPaused && (intervalRef.current !== null || initialDelayRef.current !== null); const isInInitialDelay = initialDelayRef.current !== null; // Timer API methods const pause = useCallback(() => { // Pause if timer is enabled and currently running (not already paused) if (enabled && !isPaused) { setIsPaused(true); if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } if (initialDelayRef.current) { clearTimeout(initialDelayRef.current); initialDelayRef.current = null; } } }, [enabled, isPaused]); const resume = useCallback(() => { // Resume if timer is enabled and currently paused if (enabled && isPaused) { setIsPaused(false); // The useEffect will handle restarting the timer } }, [enabled, isPaused]); // Create API object once const timerApi = useMemo(() => ({ pause, resume, isPaused: () => isPaused, isRunning: () => isRunning && !isPaused, }), [pause, resume, isPaused, isRunning]); // Register both APIs together useEffect(() => { if (registerComponentApi) { registerComponentApi(timerApi); } }, [registerComponentApi, timerApi]); const handleTick = useCallback(async () => { // Check if timer should still be running (enabled, not paused, valid interval) if (!enabledRef.current || isPausedRef.current || intervalRef2.current <= 0) { return; } // Prevent re-firing if the previous event hasn't completed yet if (handlerRunningRef.current) { return; } if (onTickRef.current) { handlerRunningRef.current = true; try { await onTickRef.current(); // Mark that the timer has actually started executing (for initial delay logic) if (!hasEverStartedRef.current) { setHasEverStarted(true); } // If this is a "once" timer and it's the very first execution, mark it as executed // After the first execution, the timer becomes a regular timer that can be paused/resumed if (onceRef.current && !hasExecutedOnceRef.current) { setHasExecutedOnce(true); } } finally { handlerRunningRef.current = false; } } }, []); useEffect(() => { // Clear any existing timers first if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } if (initialDelayRef.current) { clearTimeout(initialDelayRef.current); initialDelayRef.current = null; } // If "once" is true and the timer has already executed, don't start the timer if (once && hasExecutedOnce) { return; } if (enabled && !isPaused && interval > 0) { // Helper to start the actual timer const startTicking = () => { intervalRef.current = (once && !hasExecutedOnce) ? setTimeout(handleTick, interval) as any : setInterval(handleTick, interval); }; // Only apply initial delay if timer has never been started before if (initialDelay > 0 && !hasEverStarted) { initialDelayRef.current = setTimeout(() => { initialDelayRef.current = null; startTicking(); }, initialDelay); } else { startTicking(); } } return () => { if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } if (initialDelayRef.current) { clearTimeout(initialDelayRef.current); initialDelayRef.current = null; } }; }, [enabled, interval, once, hasExecutedOnce, isPaused, initialDelay, hasEverStarted]); // Reset state when enabled changes useEffect(() => { if (enabled && once) { // Reset hasExecutedOnce when enabled changes from false to true for "once" timers setHasExecutedOnce(false); } if (!enabled) { // Reset pause state when timer is disabled setIsPaused(false); } }, [enabled, once]); // Timer is a non-visual component return ( <div ref={forwardedRef} style={{ display: "none", ...style }} className={className} data-timer-enabled={enabled} data-timer-interval={interval} data-timer-initial-delay={initialDelay} data-timer-once={once} data-timer-running={isRunning} data-timer-paused={isPaused} data-timer-in-initial-delay={isInInitialDelay} data-timer-has-executed={hasExecutedOnce} {...rest} /> ); }); ``` -------------------------------------------------------------------------------- /xmlui/src/components/TableOfContents/TableOfContents.module.scss: -------------------------------------------------------------------------------- ```scss @use "../../components-core/theming/themes" as t; $themeVars: (); @function createThemeVar($componentVariable) { $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global; @return t.getThemeVar($themeVars, $componentVariable); } $component: "TableOfContents"; $backgroundColor-TableOfContents: createThemeVar("backgroundColor-#{$component}"); $width-TableOfContents: createThemeVar("width-#{$component}"); $height-TableOfContents: createThemeVar("height-#{$component}"); $themeVars: t.composeBorderVars($themeVars, $component); $themeVars: t.composePaddingVars($themeVars, $component); $componentItem: "TableOfContentsItem"; $themeVars: t.composeTextVars($themeVars, $componentItem); $themeVars: t.composeBorderVars($themeVars, $componentItem); $themeVars: t.composeBorderVars($themeVars, "#{$componentItem}--hover"); $themeVars: t.composeBorderVars($themeVars, "#{$componentItem}--active"); $themeVars: t.composePaddingVars($themeVars, $componentItem); $themeVars: t.composePaddingVars($themeVars, "#{$componentItem}-level-1"); $themeVars: t.composeTextVars($themeVars, "#{$componentItem}-level-1"); $themeVars: t.composePaddingVars($themeVars, "#{$componentItem}-level-2"); $themeVars: t.composeTextVars($themeVars, "#{$componentItem}-level-2"); $themeVars: t.composePaddingVars($themeVars, "#{$componentItem}-level-3"); $themeVars: t.composeTextVars($themeVars, "#{$componentItem}-level-3"); $themeVars: t.composePaddingVars($themeVars, "#{$componentItem}-level-4"); $themeVars: t.composeTextVars($themeVars, "#{$componentItem}-level-4"); $themeVars: t.composePaddingVars($themeVars, "#{$componentItem}-level-5"); $themeVars: t.composeTextVars($themeVars, "#{$componentItem}-level-5"); $themeVars: t.composePaddingVars($themeVars, "#{$componentItem}-level-6"); $themeVars: t.composeTextVars($themeVars, "#{$componentItem}-level-6"); @layer components { .nav { border-width: createThemeVar("borderWidth-#{$component}"); border-color: createThemeVar("borderColor-#{$component}"); border-style: createThemeVar("borderStyle-#{$component}"); @include t.borderVars($themeVars, $component); @include t.paddingVars($themeVars, $component); background-color: $backgroundColor-TableOfContents; min-width: 250px; align-self: flex-start; width: $width-TableOfContents; height: $height-TableOfContents; max-height: calc(100vh - var(--header-abs-height) - var(--footer-abs-height)); overflow-y: auto; overflow-x: hidden; z-index: 99; top: var(--header-height); position: sticky; scrollbar-width: thin; // --- Do not collapse these padding properties; they are intentionally expanded margin-top: createThemeVar("marginTop-#{$component}"); margin-bottom: createThemeVar("marginBottom-#{$component}"); .list { margin: 0; padding: 0; list-style: none; .listItem { list-style-type: none; @include t.borderVars($themeVars, $componentItem); .link { display: block; overflow-wrap: break-word; color: createThemeVar("textColor-#{$componentItem}"); @include t.paddingVars($themeVars, $componentItem); @include t.textVars($themeVars, $componentItem); &.head_1 { @include t.paddingVars($themeVars, "#{$componentItem}-level-1"); @include t.textVars($themeVars, "#{$componentItem}-level-1"); } &.head_2 { @include t.paddingVars($themeVars, "#{$componentItem}-level-2"); @include t.textVars($themeVars, "#{$componentItem}-level-2"); } &.head_3 { @include t.paddingVars($themeVars, "#{$componentItem}-level-3"); @include t.textVars($themeVars, "#{$componentItem}-level-3"); } &.head_4 { @include t.paddingVars($themeVars, "#{$componentItem}-level-4"); @include t.textVars($themeVars, "#{$componentItem}-level-4"); } &.head_5 { @include t.paddingVars($themeVars, "#{$componentItem}-level-5"); @include t.textVars($themeVars, "#{$componentItem}-level-5"); } &.head_6 { @include t.paddingVars($themeVars, "#{$componentItem}-level-6"); @include t.textVars($themeVars, "#{$componentItem}-level-6"); } } &:hover { @include t.borderVars($themeVars, "#{$componentItem}--hover"); background-color: createThemeVar("backgroundColor-#{$componentItem}--hover"); .link { font-weight: createThemeVar("fontWeight-#{$componentItem}--hover"); color: createThemeVar("textColor-#{$componentItem}--hover"); &.head_1 { font-weight: createThemeVar("fontWeight-#{$componentItem}-level-1--hover"); } &.head_2 { font-weight: createThemeVar("fontWeight-#{$componentItem}-level-2--hover"); } &.head_3 { font-weight: createThemeVar("fontWeight-#{$componentItem}-level-3--hover"); } &.head_4 { font-weight: createThemeVar("fontWeight-#{$componentItem}-level-4--hover"); } &.head_5 { font-weight: createThemeVar("fontWeight-#{$componentItem}-level-5--hover"); } &.head_6 { font-weight: createThemeVar("fontWeight-#{$componentItem}-level-6--hover"); } } } &.active { @include t.borderVars($themeVars, "#{$componentItem}--active"); background-color: createThemeVar("backgroundColor-#{$componentItem}--active"); .link { color: createThemeVar("textColor-#{$componentItem}--active"); font-weight: createThemeVar("fontWeight-#{$componentItem}--active"); &.head_1 { font-weight: createThemeVar("fontWeight-#{$componentItem}-level-1--active"); } &.head_2 { font-weight: createThemeVar("fontWeight-#{$componentItem}-level-2--active"); } &.head_3 { font-weight: createThemeVar("fontWeight-#{$componentItem}-level-3--active"); } &.head_4 { font-weight: createThemeVar("fontWeight-#{$componentItem}-level-4--active"); } &.head_5 { font-weight: createThemeVar("fontWeight-#{$componentItem}-level-5--active"); } &.head_6 { font-weight: createThemeVar("fontWeight-#{$componentItem}-level-6--active"); } } } } } } } :export { themeVars: t.json-stringify($themeVars); } ``` -------------------------------------------------------------------------------- /xmlui/src/components-core/theming/StyleRegistry.ts: -------------------------------------------------------------------------------- ```typescript // app/utils/styleUtils.ts import type { CSSProperties } from 'react'; // --- Type Definitions --- // This type definition is now more powerful. It allows a value to be another // style object, enabling true nesting. export type StyleObjectType = CSSProperties & { [selectorOrAtRule: string]: StyleObjectType | CSSProperties[keyof CSSProperties]; }; interface StyleCacheEntry { className: string; styleHash: string; css: string; } // --- Helper Functions --- function hashString(str: string): string { let hash = 5381; let i = str.length; while (i) { hash = (hash * 33) ^ str.charCodeAt(--i); } let s = (hash >>> 0).toString(36); // console.log("hashString", str, "->", s); return s; } /** * Converts a camelCase string to kebab-case, but ignores CSS Custom Properties. * @param {string} str The string to convert. * @returns {string} The formatted string. */ function toKebabCase(str: string): string { // NEW: If the string is a CSS Custom Property (starts with '--'), // return it as-is without any changes. if (str.startsWith('--')) { return str; } // Otherwise, convert from camelCase to kebab-case as before. return str.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`); } /** * Creates a stable, canonical JSON string from an object by sorting keys recursively. * This ensures that objects with the same content produce the same string regardless of key order. * @param obj The object to stringify. * @returns A stable string representation. */ function stableJSONStringify(obj: any): string { return JSON.stringify(obj); // if (obj === null || typeof obj !== 'object') { // return JSON.stringify(obj); // } // // if (Array.isArray(obj)) { // const arrayStr = obj.map(item => stableJSONStringify(item)).join(','); // return `[${arrayStr}]`; // } // // const keys = Object.keys(obj).sort(); // const props = keys.map(key => { // const value = stableJSONStringify(obj[key]); // return `"${key}":${value}`; // }); // // return `{${props.join(',')}}`; } // --- The StyleRegistry Class (with updated logic) --- export class StyleRegistry { public cache: Map<string, StyleCacheEntry> = new Map(); public rootClasses: Set<string> = new Set(); public injected: Set<string> = new Set(); // NEW: A map to track how many components are using a style. public refCounts: Map<string, number> = new Map(); // NEW: A set to specifically track hashes injected by SSR. public ssrHashes: Set<string> = new Set(); public register(styles: StyleObjectType): StyleCacheEntry { const key = stableJSONStringify(styles); const styleHash = hashString(key); const cachedEntry = this.cache.get(styleHash); if (cachedEntry) { return cachedEntry; } // The entry point for our new recursive generator. const className = `css-${styleHash}`; const css = this._generateCss(`.${className}`, styles); const entry: StyleCacheEntry = { className, styleHash, css }; this.cache.set(styleHash, entry); return entry; } /** * [PRIVATE] Recursively generates CSS rules from a style object. * This is the new, more powerful engine. * @param selector - The CSS selector for the current context (e.g., '.css-123' or '&:hover'). * @param styles - The style object to process. * @returns A string of CSS rules. */ private _generateCss(selector: string, styles: StyleObjectType): string { const directProps: string[] = []; const nestedRules: string[] = []; // 1. Separate direct CSS properties from nested rules. for (const key in styles) { const value = styles[key]; if (typeof value === 'object' && value !== null) { // It's a nested rule (e.g., '&:hover', '@media'). nestedRules.push(this._processNestedRule(selector, key, value as StyleObjectType)); } else { // It's a direct CSS property (e.g., 'backgroundColor'). directProps.push(`${toKebabCase(key)}:${value};`); } } let finalCss = ''; // 2. Generate the CSS for the direct properties at the current selector level. if (directProps.length > 0) { finalCss += `${selector} {${directProps.join('')}}`; } // 3. Append the CSS from all the processed nested rules. finalCss += nestedRules.join(''); return finalCss; } private _processNestedRule(parentSelector: string, nestedKey: string, nestedStyles: StyleObjectType): string { // If the key is an at-rule (@media, @container, @keyframes), wrap the recursive call. if (nestedKey.startsWith('@')) { // The inner content is generated relative to the original parent selector. return `${nestedKey}{${this._generateCss(parentSelector, nestedStyles)}}`; } // If the key is a nested selector, resolve the '&' placeholder. // e.g., parent='.css-123', nestedKey='&:hover' -> '.css-123:hover' const newSelector = nestedKey.replace(/&/g, parentSelector); return this._generateCss(newSelector, nestedStyles); } public getSsrStyles(): string { const allCss = Array.from(this.cache.values()).map(entry => entry.css).join(''); // Wrap the entire output in our top-most layer. return `@layer dynamic {${allCss}}`; } /** * Adds a class name to be applied to the <html> tag. */ public addRootClasses(classNames: Array<string>): void { classNames.forEach((className)=>{ this.rootClasses.add(className); }); } /** * Returns a space-separated string of all collected html classes. */ public getRootClasses(): string { return Array.from(this.rootClasses).join(' '); } // NEW: A helper to safely get the current reference count. public getRefCount(styleHash: string): number { return this.refCounts.get(styleHash) || 0; } /** * Increments the reference count for a given style hash. */ public incrementRef(styleHash: string): void { const newCount = (this.refCounts.get(styleHash) || 0) + 1; this.refCounts.set(styleHash, newCount); // console.log("incrementing ref count for styleHash:", styleHash, "to", newCount); } /** * Decrements the reference count for a given style hash. * @returns {number} The new reference count. */ public decrementRef(styleHash: string): number { const currentCount = this.refCounts.get(styleHash) || 0; const newCount = Math.max(0, currentCount - 1); // console.log("decrementing ref count for styleHash:", styleHash, "from", currentCount, "to", newCount); if (newCount > 0) { this.refCounts.set(styleHash, newCount); } else { // If the count is zero, remove it from the map. this.refCounts.delete(styleHash); } return newCount; } } ``` -------------------------------------------------------------------------------- /xmlui/scripts/generate-docs/logging-standards.mjs: -------------------------------------------------------------------------------- ``` import { logger } from "./logger.mjs"; /** * Standardized logging utilities for documentation generation scripts * Provides consistent logging patterns and message formatting */ /** * Log levels in order of severity (lowest to highest) */ export const LOG_LEVELS = { INFO: "info", WARN: "warn", ERROR: "error" }; /** * Standard logging patterns for common operations */ export const LOGGING_PATTERNS = { // Operation start/completion OPERATION_START: (operation) => `Starting ${operation}...`, OPERATION_COMPLETE: (operation) => `Completed ${operation}`, OPERATION_FAILED: (operation, error) => `Failed ${operation}: ${error}`, // File operations FILE_PROCESSING: (filePath) => `Processing file: ${filePath}`, FILE_WRITTEN: (filePath) => `Written file: ${filePath}`, FILE_DELETED: (filePath) => `Deleted file: ${filePath}`, FILE_NOT_FOUND: (filePath) => `File not found: ${filePath}`, // Directory operations DIRECTORY_CREATED: (dirPath) => `Created directory: ${dirPath}`, DIRECTORY_CLEANED: (dirPath) => `Cleaned directory: ${dirPath}`, // Component processing COMPONENT_PROCESSING: (componentName) => `Processing component: ${componentName}`, COMPONENT_SKIPPED: (componentName, reason) => `Skipped component ${componentName}: ${reason}`, // Package/Extension operations PACKAGE_LOADING: (packageName) => `Loading package: ${packageName}`, PACKAGE_LOADED: (packageName) => `Loaded package: ${packageName}`, PACKAGE_SKIPPED: (packageName, reason) => `Skipped package ${packageName}: ${reason}`, // Configuration CONFIG_LOADING: (configPath) => `Loading configuration from: ${configPath}`, CONFIG_LOADED: (configPath) => `Configuration loaded from: ${configPath}`, // Progress indicators PROGRESS: (current, total, operation) => `Progress: ${current}/${total} ${operation}`, // Validation VALIDATION_PASSED: (item) => `Validation passed: ${item}`, VALIDATION_FAILED: (item, reason) => `Validation failed for ${item}: ${reason}`, // Generic warnings and errors DEPRECATION_WARNING: (feature) => `DEPRECATED: ${feature} is deprecated and will be removed in a future version`, FEATURE_NOT_SUPPORTED: (feature) => `Feature not supported: ${feature}`, UNEXPECTED_CONDITION: (condition) => `Unexpected condition encountered: ${condition}` }; /** * Standardized logging functions with consistent formatting */ export const standardLogger = { /** * Log an informational message */ info: (message, ...additionalArgs) => { if (additionalArgs.length > 0) { logger.info(message, ...additionalArgs); } else { logger.info(message); } }, /** * Log a warning message */ warn: (message, ...additionalArgs) => { if (additionalArgs.length > 0) { logger.warn(message, ...additionalArgs); } else { logger.warn(message); } }, /** * Log an error message */ error: (message, ...additionalArgs) => { if (additionalArgs.length > 0) { logger.error(message, ...additionalArgs); } else { logger.error(message); } }, /** * Log the start of an operation */ operationStart: (operation) => { logger.info(LOGGING_PATTERNS.OPERATION_START(operation)); }, /** * Log the completion of an operation */ operationComplete: (operation) => { logger.info(LOGGING_PATTERNS.OPERATION_COMPLETE(operation)); }, /** * Log a failed operation */ operationFailed: (operation, error) => { logger.error(LOGGING_PATTERNS.OPERATION_FAILED(operation, error)); }, /** * Log file processing */ fileProcessing: (filePath) => { logger.info(LOGGING_PATTERNS.FILE_PROCESSING(filePath)); }, /** * Log successful file write */ fileWritten: (filePath) => { logger.info(LOGGING_PATTERNS.FILE_WRITTEN(filePath)); }, /** * Log component processing */ componentProcessing: (componentName) => { logger.info(LOGGING_PATTERNS.COMPONENT_PROCESSING(componentName)); }, /** * Log skipped component */ componentSkipped: (componentName, reason) => { logger.warn(LOGGING_PATTERNS.COMPONENT_SKIPPED(componentName, reason)); }, /** * Log package loading */ packageLoading: (packageName) => { logger.info(LOGGING_PATTERNS.PACKAGE_LOADING(packageName)); }, /** * Log package loaded */ packageLoaded: (packageName) => { logger.info(LOGGING_PATTERNS.PACKAGE_LOADED(packageName)); }, /** * Log skipped package */ packageSkipped: (packageName, reason) => { logger.warn(LOGGING_PATTERNS.PACKAGE_SKIPPED(packageName, reason)); }, /** * Log configuration loading */ configLoading: (configPath) => { logger.info(LOGGING_PATTERNS.CONFIG_LOADING(configPath)); }, /** * Log progress */ progress: (current, total, operation) => { logger.info(LOGGING_PATTERNS.PROGRESS(current, total, operation)); }, /** * Log deprecation warning */ deprecationWarning: (feature) => { logger.warn(LOGGING_PATTERNS.DEPRECATION_WARNING(feature)); } }; /** * Creates a scoped logger for a specific module/operation */ export function createScopedLogger(scope) { return { info: (message, ...args) => standardLogger.info(`[${scope}] ${message}`, ...args), warn: (message, ...args) => standardLogger.warn(`[${scope}] ${message}`, ...args), error: (message, ...args) => standardLogger.error(`[${scope}] ${message}`, ...args), operationStart: (operation) => standardLogger.operationStart(`${scope}: ${operation}`), operationComplete: (operation) => standardLogger.operationComplete(`${scope}: ${operation}`), operationFailed: (operation, error) => standardLogger.operationFailed(`${scope}: ${operation}`, error), // File operations fileProcessing: (filePath) => standardLogger.fileProcessing(filePath), fileWritten: (filePath) => standardLogger.fileWritten(filePath), // Component operations componentProcessing: (componentName) => standardLogger.componentProcessing(componentName), componentSkipped: (componentName, reason) => standardLogger.componentSkipped(componentName, reason), // Package operations packageLoading: (packageName) => standardLogger.packageLoading(packageName), packageLoaded: (packageName) => standardLogger.packageLoaded(packageName), packageSkipped: (packageName, reason) => standardLogger.packageSkipped(packageName, reason), // Configuration configLoading: (configPath) => standardLogger.configLoading(configPath), // Progress progress: (current, total, operation) => standardLogger.progress(current, total, operation), // Warnings deprecationWarning: (feature) => standardLogger.deprecationWarning(feature) }; } ``` -------------------------------------------------------------------------------- /docs/public/pages/tutorial-09.md: -------------------------------------------------------------------------------- ```markdown # Invoice Details We've seen that the `Invoices` [Table](/components/Table) includes a `Details` [Column](/components/Column) with an icon. Clicking the icon opens a [ModalDialog](/components/ModalDialog) and sends it the current row of the table as `$item`. ```xmlui /detailsDialog/ <Table data="{invoices}"> <Column bindTo="invoice_number" /> <Column bindTo="client" /> <Column bindTo="issue_date" /> <Column bindTo="due_date" /> <Column bindTo="paid_date" /> <Column header="total"> ${$item.total} </Column> <Column header="Status"> <StatusBadge status="{$item.status}" /> </Column> <Column canSort="{false}" header="Details"> <Icon name="doc-outline" onClick="detailsDialog.open($item)" /> </Column> </Table> ``` ```xmlui /detailsDialog/ <ModalDialog id="detailsDialog"> <InvoiceDetails details="{$params[0]}" /> </ModalDialog> ``` The `ModalDialog` wraps an `InvoiceDetails` component which displays an invoice and enables editing. Click the `Details` icon to open the viewer/editor. ```xmlui-pg noHeader height="500px" ---app <App> <Table gap="0" data="{[window.sampleInvoice]}"> <Column canSort="{false}" bindTo="invoice_number" /> <Column canSort="{false}" bindTo="client" /> <Column canSort="{false}" bindTo="issue_date" /> <Column canSort="{false}" bindTo="due_date" /> <Column canSort="{false}" bindTo="paid_date" /> <Column canSort="{false}" header="total"> ${$item.total} </Column> <Column canSort="{false}" header="Status"> <StatusBadge status="{$item.status}" /> </Column> <Column canSort="{false}" header="Details"> <Icon name="doc-outline" onClick="detailsDialog.open($item)" /> </Column> </Table> <Theme maxWidth-ModalDialog="50%" backgroundColor-overlay-ModalDialog="rgba(0,0,0,0.5)"> <ModalDialog id="detailsDialog"> <InvoiceDetails details="{$params[0]}" /> </ModalDialog> </Theme> </App> ---comp <Component name="StatusBadge" var.statusColors="{{ draft: { background: '#f59e0b', label: 'white' }, sent: { background: '#3b82f6', label: 'white' }, paid: { background: '#10b981', label: 'white' } }}" > <Badge value="{$props.status}" colorMap="{statusColors}" variant="pill" /> </Component> ---comp <Component name="InvoiceDetails"> <variable name="details" value="{$props.details}" /> <Form> <Table width="100%" data="{[details]}"> <Column canSort="{false}" bindTo="invoice_number" /> <Column canSort="{false}" bindTo="client" /> <Column canSort="{false}" bindTo="issue_date" /> <Column canSort="{false}" bindTo="due_date" /> <Column canSort="{false}" header="Status"> <StatusBadge status="{$item.status}" /> </Column> </Table> <VStack gap="0"> <Text>Notes:</Text> <FormItem bindTo="notes" initialValue="{window.coalesce(details.notes)}" /> </VStack> <VStack gap="0"> <Text>Status:</Text> <FormItem type="select" bindTo="status" initialValue="{details.status}" enabled="{details.status !== 'paid'}"> <Option label="sent" value="sent" /> <Option label="paid" value="paid" /> <Option label="draft" value="draft" /> <Option value="{$item.name}" label="{$item.name}" /> </FormItem> </VStack> <Table width="100%" data="{JSON.parse(details.items)}"> <Column bindTo="name" /> <Column bindTo="quantity" /> <Column bindTo="total"> ${$item.total} </Column> <Column header="price"> ${$item.price} </Column> </Table> <event name="submit"> <APICall url="https://httpbin.org/post" method="POST" inProgressNotificationMessage="Updating invoice..." completedNotificationMessage="Invoice updated successfully" body="{ { number: details.invoice_number, status: $param.status, notes: $param.notes } }" onSuccess="Actions.navigate('/invoices')" /> </event> </Form> </Component> ``` Here's the `InvoiceDetails` component. ```xmlui <Component name="InvoiceDetails"> <Table width="100%" data="{[$props.details]}"> <Column canSort="{false}" bindTo="invoice_number" /> <Column canSort="{false}" bindTo="client" /> <Column canSort="{false}" bindTo="issue_date" /> <Column canSort="{false}" bindTo="due_date" /> <Column canSort="{false}" header="Status"> <StatusBadge status="{$item.status}" /> </Column> </Table> <Form submitUrl="/api/invoices/{$props.details.invoice_number}" submitMethod="PUT" > <FormItem label="Notes" bindTo="notes" initialValue="{window.coalesce($props.details.notes)}" /> <FormItem label="Status" bindTo="status" initialValue="{$props.details.status}" type="select" enabled="{$props.details.status !== 'paid'}" > <Option label="sent" value="sent" /> <Option label="paid" value="paid" /> <Option label="draft" value="draft" /> </FormItem> <Table data="{JSON.parse($props.details.items)}"> <Column bindTo="name" /> <Column bindTo="quantity" /> <Column bindTo="total"> ${$item.total} </Column> <Column header="price"> ${$item.price} </Column> </Table> </Form> </Component> ``` There are two `Table`s. The first has only one row to report the top-level details. Since `$props.details` is an object, not an array, we wrap it in square brackets (`[ ]`) to provide the array that `Table` expects. The second `Table` reports one row per lineitem. The Invoices app chooses not to make lineitems editable. The editable fields are `notes` and `status`. The `status` field uses `FormItem type="select"`. It's disabled for statuses other than `paid`, so the operator can mark a `sent` invoice as `paid` but not vice versa. The [APICall](/component/APICall) is triggered by the submit event. Its `onSuccess` handler uses [Actions.navigate](/globals#navigate) to return to the `Invoices` page. In other contexts you use `ModalDialog`'s `close()` method, but when a `Form` is wrapped in a `ModalDialog`, the form's submit and cancel buttons both close the dialog. ``` -------------------------------------------------------------------------------- /xmlui/src/components/DropdownMenu/DropdownMenuNative.tsx: -------------------------------------------------------------------------------- ```typescript import { type CSSProperties, forwardRef, type ReactNode } from "react"; import { useEffect, useState, useRef } from "react"; import * as ReactDropdownMenu from "@radix-ui/react-dropdown-menu"; import classnames from "classnames"; import styles from "./DropdownMenu.module.scss"; import type { RegisterComponentApiFn } from "../../abstractions/RendererDefs"; import { useTheme } from "../../components-core/theming/ThemeContext"; import { noop } from "../../components-core/constants"; import type { IconPosition, ButtonVariant, ButtonThemeColor, AlignmentOptions, } from "../abstractions"; import { Button } from "../Button/ButtonNative"; import { Icon } from "../Icon/IconNative"; type DropdownMenuProps = { triggerTemplate?: ReactNode; children?: ReactNode; label?: string; registerComponentApi?: RegisterComponentApiFn; style?: CSSProperties; className?: string; alignment?: AlignmentOptions; onWillOpen?: () => Promise<boolean | undefined>; disabled?: boolean; triggerButtonVariant?: string; triggerButtonThemeColor?: string; triggerButtonIcon?: string; triggerButtonIconPosition?: IconPosition; }; export const defaultDropdownMenuProps: Pick< DropdownMenuProps, | "alignment" | "triggerButtonVariant" | "triggerButtonThemeColor" | "triggerButtonIcon" | "triggerButtonIconPosition" > = { alignment: "start", triggerButtonVariant: "ghost", triggerButtonThemeColor: "primary", triggerButtonIcon: "triggerButton:DropdownMenu", // Use component-specific icon resource pattern triggerButtonIconPosition: "end", }; export const DropdownMenu = forwardRef(function DropdownMenu( { triggerTemplate, children, label, registerComponentApi, style, className, onWillOpen, alignment = defaultDropdownMenuProps.alignment, disabled = false, triggerButtonVariant = defaultDropdownMenuProps.triggerButtonVariant, triggerButtonThemeColor = defaultDropdownMenuProps.triggerButtonThemeColor, triggerButtonIcon = defaultDropdownMenuProps.triggerButtonIcon, triggerButtonIconPosition = defaultDropdownMenuProps.triggerButtonIconPosition, ...rest }: DropdownMenuProps, ref, ) { const { root } = useTheme(); const [open, setOpen] = useState(false); const closeTimeoutRef = useRef<NodeJS.Timeout>(); useEffect(() => { registerComponentApi?.({ open: () => setOpen(true), close: () => setOpen(false), }); }, [registerComponentApi]); // Cleanup timeout on unmount useEffect(() => { return () => { if (closeTimeoutRef.current) { clearTimeout(closeTimeoutRef.current); } }; }, []); return ( <ReactDropdownMenu.Root open={open} onOpenChange={async (isOpen) => { if (isOpen) { // Clear any pending close timeout when opening if (closeTimeoutRef.current) { clearTimeout(closeTimeoutRef.current); closeTimeoutRef.current = undefined; } const willOpenResult = await onWillOpen?.(); if (willOpenResult === false) { return; } setOpen(isOpen); } else { // When closing, add a small delay to allow child components (like Select) // to handle their click-outside events first before the DropdownMenu closes closeTimeoutRef.current = setTimeout(() => { setOpen(false); closeTimeoutRef.current = undefined; }, 0); } }} > <ReactDropdownMenu.Trigger {...rest} asChild disabled={disabled} ref={ref as any}> {triggerTemplate ? ( triggerTemplate ) : ( <Button icon={<Icon name={triggerButtonIcon} fallback="chevrondown" />} iconPosition={triggerButtonIconPosition} type="button" variant={triggerButtonVariant as ButtonVariant} themeColor={triggerButtonThemeColor as ButtonThemeColor} disabled={disabled} > {label} </Button> )} </ReactDropdownMenu.Trigger> <ReactDropdownMenu.Portal container={root}> <ReactDropdownMenu.Content align={alignment} style={style} className={classnames(styles.DropdownMenuContent, className)} > {children} </ReactDropdownMenu.Content> </ReactDropdownMenu.Portal> </ReactDropdownMenu.Root> ); }); type MenuItemProps = { icon?: ReactNode; iconPosition?: IconPosition; onClick?: (event: any) => void; children?: ReactNode; label?: string; style?: CSSProperties; className?: string; to?: string; active?: boolean; enabled?: boolean; }; export const defaultMenuItemProps: Pick<MenuItemProps, "iconPosition" | "active"> = { iconPosition: "start", active: false, }; export const MenuItem = forwardRef(function MenuItem( { children, onClick = noop, label, style, className, icon, iconPosition = defaultMenuItemProps.iconPosition, active = defaultMenuItemProps.active, enabled = true, }: MenuItemProps, ref, ) { const iconToStart = iconPosition === "start"; return ( <ReactDropdownMenu.Item style={style} className={classnames(className, styles.DropdownMenuItem, { [styles.active]: active, [styles.disabled]: !enabled, })} onClick={(event) => { if (!enabled) { event.preventDefault(); event.stopPropagation(); return; } event.stopPropagation(); if (enabled) { onClick(event); } }} ref={ref as any} > {iconToStart && icon} <div className={styles.wrapper}>{label ?? children}</div> {!iconToStart && icon} </ReactDropdownMenu.Item> ); }); type SubMenuItemProps = { label?: string; children?: ReactNode; triggerTemplate?: ReactNode; }; export const SubMenuItem = forwardRef<HTMLDivElement, SubMenuItemProps>( function SubMenuItem({ children, label, triggerTemplate }, ref) { const { root } = useTheme(); return ( <ReactDropdownMenu.Sub> <ReactDropdownMenu.SubTrigger className={styles.DropdownMenuSubTrigger} asChild ref={ref}> {triggerTemplate ? triggerTemplate : <div>{label}</div>} </ReactDropdownMenu.SubTrigger> <ReactDropdownMenu.Portal container={root}> <ReactDropdownMenu.SubContent className={styles.DropdownMenuSubContent}> {children} </ReactDropdownMenu.SubContent> </ReactDropdownMenu.Portal> </ReactDropdownMenu.Sub> ); }, ); export const MenuSeparator = forwardRef<HTMLDivElement>(function MenuSeparator(props, ref) { return <ReactDropdownMenu.Separator ref={ref} className={styles.DropdownMenuSeparator} {...props} />; }); ``` -------------------------------------------------------------------------------- /xmlui/src/components/App/App.module.scss: -------------------------------------------------------------------------------- ```scss @use "../../components-core/theming/themes" as t; // --- This code snippet is required to collect the theme variables used in this module $themeVars: (); @function createThemeVar($componentVariable) { $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global; @return t.getThemeVar($themeVars, $componentVariable); } $width-navPanel-App: createThemeVar("width-navPanel-App"); $backgroundColor-navPanel-App: createThemeVar("backgroundColor-navPanel-App"); $boxShadow-header-App: createThemeVar("boxShadow-header-App"); $boxShadow-navPanel-App: createThemeVar("boxShadow-navPanel-App"); $backgroundColor-content-App: createThemeVar("backgroundColor-content-App"); $borderLeft-content-App: createThemeVar("borderLeft-content-App"); $maxWidth-content-App: createThemeVar("maxWidth-content-App"); $maxWidth-App: createThemeVar("maxWidth-App"); $backgroundColor-AppHeader: createThemeVar("backgroundColor-AppHeader"); $borderBottom-NavPanel: createThemeVar("borderBottom-AppHeader"); $scrollPaddingBlockPage: createThemeVar("scroll-padding-block-Pages"); @layer components { @include t.withMaxScreenSize(2) { .wrapper.verticalFullHeader{ .navPanelWrapper{ display: none; } } } .wrapper { --footer-height: 0px; --header-height: 0px; width: 100%; height: 100%; position: relative; //leave it here, otherwise there could be double scrollbars because of the absolute positionings (typically radix's visuallyHidden) display: flex; flex-direction: column; isolation: isolate; &.vertical { flex-direction: row; overflow: initial; .contentWrapper { overflow: auto; scroll-padding-block: $scrollPaddingBlockPage; position: relative; scrollbar-gutter: stable both-edges; } &.noScrollbarGutters{ .contentWrapper { scrollbar-gutter: auto; } } .navPanelWrapper { width: $width-navPanel-App; flex-shrink: 0; } .PagesWrapper { min-height: initial; flex: 1; } .footerWrapper { position: static; } &.sticky { .footerWrapper { position: sticky; bottom: 0; } } } &.horizontal { overflow: auto; scroll-padding-block: $scrollPaddingBlockPage; .PagesWrapper { min-height: initial; } .footerWrapper { position: static; } &.sticky { min-height: 100%; .footerWrapper { position: sticky; bottom: 0; } } .navPanelWrapper { border-bottom: $borderBottom-NavPanel; justify-content: end; background-color: $backgroundColor-navPanel-App; } } &.verticalFullHeader { min-height: 100%; height: 100%; overflow: auto; scroll-padding-block: $scrollPaddingBlockPage; .navPanelWrapper { width: $width-navPanel-App; position: sticky; height: calc(var(--containerHeight, 100vh) - var(--footer-height) - var(--header-height)); top: var(--header-height); &::before { content: ""; position: absolute; top: 0; right: 0; bottom: 0; width: 50vw; z-index: -1; background-color: $backgroundColor-navPanel-App; } } .PagesWrapper { overflow: initial; min-height: calc(var(--containerHeight, 100vh) - var(--header-height) - var(--footer-height)); height: 100%; } .PagesWrapperInner { height: 100%; & > :global(.xmlui-page-root) { height: 100%; } } .footerWrapper { position: sticky; left: 0; right: 0; bottom: 0; } } &.scrollWholePage { scrollbar-gutter: stable both-edges; .headerWrapper { & > div { padding-inline: var(--scrollbar-width); } margin-inline: calc(-1 * var(--scrollbar-width)); } .footerWrapper { margin-inline: calc(-1 * var(--scrollbar-width)); & > div { padding-inline: var(--scrollbar-width); } } &.verticalFullHeader { .content { margin-inline: calc(-1 * var(--scrollbar-width)); width: calc(100% + (2 * var(--scrollbar-width))); } .contentWrapper { padding-inline: var(--scrollbar-width); } } } &:not(.scrollWholePage) { overflow: hidden; .content { min-height: 0; height: 100%; } .contentWrapper { overflow: initial; } .PagesWrapper { overflow: auto; scroll-padding-block: $scrollPaddingBlockPage; min-height: 0; height: 100%; scrollbar-gutter: stable both-edges; } .PagesWrapperInner { min-height: 100%; height: 0; } } &.noScrollbarGutters { scrollbar-gutter: auto; .PagesWrapper{ scrollbar-gutter: auto; } } } .headerWrapper { z-index: 1; //position: relative; min-height: 0; flex-shrink: 0; overflow-x: clip; top: 0; box-shadow: $boxShadow-header-App; background-color: $backgroundColor-AppHeader; &.sticky { position: sticky; } } .content { display: flex; flex-direction: row; isolation: isolate; align-self: center; width: 100%; max-width: createThemeVar("maxWidth-App"); } .contentWrapper { position: relative; min-width: 0; flex: 1; display: flex; flex-direction: column; box-shadow: $boxShadow-navPanel-App; background-color: $backgroundColor-content-App; border-left: $borderLeft-content-App; } .navPanelWrapper { display: flex; position: sticky; top: 0; &:empty { display: none; } } .PagesWrapper { flex: 1; //display: flex; //flex-direction: column; //flex: 1; //min-height: 0; //width: 100%; isolation: isolate; //height: 100%; //overflow: auto; } .PagesWrapperInner { max-width: $maxWidth-content-App; width: 100%; margin: 0 auto; //flex: 1; min-height: 100%; display: flex; flex-direction: column; &.withDefaultContentPadding{ padding-left: t.$space-4; padding-right: t.$space-4; padding-top: t.$space-5; padding-bottom: t.$space-5; gap: t.$space-5; } } .footerWrapper { flex-shrink: 0; //position: sticky; //bottom: calc(-1 * var(--footer-height)); //margin-bottom: calc(-1 * var(--footer-height)); } } // --- We export the theme variables to add them to the component renderer :export { themeVars: t.json-stringify($themeVars); } ```