This is page 29 of 140. Use http://codebase.md/xmlui-org/xmlui/mockApiDef.js?lines=false&page={x} to view the full context. # Directory Structure ``` ├── .changeset │ └── config.json ├── .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 │ ├── containers.md │ ├── glossary.md │ ├── index.md │ ├── next │ │ ├── component-dev-guide.md │ │ ├── configuration-management-enhancement-summary.md │ │ ├── documentation-scripts-refactoring-complete-summary.md │ │ ├── documentation-scripts-refactoring-plan.md │ │ ├── duplicate-pattern-extraction-summary.md │ │ ├── error-handling-standardization-summary.md │ │ ├── generating-component-reference.md │ │ ├── index.md │ │ ├── logging-consistency-implementation-summary.md │ │ ├── project-build.md │ │ ├── project-structure.md │ │ ├── theme-context.md │ │ ├── tiptap-design-considerations.md │ │ ├── working-with-code.md │ │ ├── xmlui-runtime-architecture │ │ └── xmlui-wcag-accessibility-report.md │ ├── react-fundamentals.md │ ├── release-method.md │ ├── standalone-app.md │ ├── state-management.md │ ├── 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 │ │ │ ├── MultiSelectOption.tsx │ │ │ ├── OptionContext.ts │ │ │ ├── Select.md │ │ │ ├── Select.module.scss │ │ │ ├── Select.spec.ts │ │ │ ├── Select.tsx │ │ │ ├── SelectContext.tsx │ │ │ ├── SelectNative.tsx │ │ │ ├── SelectOption.tsx │ │ │ └── SimpleSelect.tsx │ │ ├── SelectionStore │ │ │ ├── SelectionStore.md │ │ │ ├── SelectionStore.tsx │ │ │ └── SelectionStoreNative.tsx │ │ ├── Slider │ │ │ ├── Slider.md │ │ │ ├── Slider.module.scss │ │ │ ├── Slider.spec.ts │ │ │ ├── Slider.tsx │ │ │ └── SliderNative.tsx │ │ ├── Slot │ │ │ ├── Slot.md │ │ │ ├── Slot.spec.ts │ │ │ └── Slot.ts │ │ ├── SlotItem.tsx │ │ ├── SpaceFiller │ │ │ ├── SpaceFiller.md │ │ │ ├── SpaceFiller.module.scss │ │ │ ├── SpaceFiller.spec.ts │ │ │ ├── SpaceFiller.tsx │ │ │ └── SpaceFillerNative.tsx │ │ ├── Spinner │ │ │ ├── Spinner.md │ │ │ ├── Spinner.module.scss │ │ │ ├── Spinner.spec.ts │ │ │ ├── Spinner.tsx │ │ │ └── SpinnerNative.tsx │ │ ├── Splitter │ │ │ ├── HSplitter.md │ │ │ ├── HSplitter.spec.ts │ │ │ ├── Splitter.md │ │ │ ├── Splitter.module.scss │ │ │ ├── Splitter.spec.ts │ │ │ ├── Splitter.tsx │ │ │ ├── SplitterNative.tsx │ │ │ ├── utils.ts │ │ │ ├── VSplitter.md │ │ │ └── VSplitter.spec.ts │ │ ├── Stack │ │ │ ├── CHStack.md │ │ │ ├── CHStack.spec.ts │ │ │ ├── CVStack.md │ │ │ ├── CVStack.spec.ts │ │ │ ├── HStack.md │ │ │ ├── HStack.spec.ts │ │ │ ├── Stack.md │ │ │ ├── Stack.module.scss │ │ │ ├── Stack.spec.ts │ │ │ ├── Stack.tsx │ │ │ ├── StackNative.tsx │ │ │ ├── VStack.md │ │ │ └── VStack.spec.ts │ │ ├── StickyBox │ │ │ ├── StickyBox.md │ │ │ ├── StickyBox.module.scss │ │ │ ├── StickyBox.tsx │ │ │ └── StickyBoxNative.tsx │ │ ├── Switch │ │ │ ├── Switch.md │ │ │ ├── Switch.spec.ts │ │ │ └── Switch.tsx │ │ ├── Table │ │ │ ├── doc-resources │ │ │ │ └── list-component-data.js │ │ │ ├── react-table-config.d.ts │ │ │ ├── Table.md │ │ │ ├── Table.module.scss │ │ │ ├── Table.spec.ts │ │ │ ├── Table.tsx │ │ │ ├── TableNative.tsx │ │ │ └── useRowSelection.tsx │ │ ├── TableOfContents │ │ │ ├── TableOfContents.module.scss │ │ │ ├── TableOfContents.spec.ts │ │ │ ├── TableOfContents.tsx │ │ │ └── TableOfContentsNative.tsx │ │ ├── Tabs │ │ │ ├── TabContext.tsx │ │ │ ├── TabItem.md │ │ │ ├── TabItem.tsx │ │ │ ├── TabItemNative.tsx │ │ │ ├── Tabs.md │ │ │ ├── Tabs.module.scss │ │ │ ├── Tabs.spec.ts │ │ │ ├── Tabs.tsx │ │ │ └── TabsNative.tsx │ │ ├── Text │ │ │ ├── Text.md │ │ │ ├── Text.module.scss │ │ │ ├── Text.spec.ts │ │ │ ├── Text.tsx │ │ │ └── TextNative.tsx │ │ ├── TextArea │ │ │ ├── TextArea.md │ │ │ ├── TextArea.module.scss │ │ │ ├── TextArea.spec.ts │ │ │ ├── TextArea.tsx │ │ │ ├── TextAreaNative.tsx │ │ │ ├── TextAreaResizable.tsx │ │ │ └── useComposedRef.ts │ │ ├── TextBox │ │ │ ├── TextBox.md │ │ │ ├── TextBox.module.scss │ │ │ ├── TextBox.spec.ts │ │ │ ├── TextBox.tsx │ │ │ └── TextBoxNative.tsx │ │ ├── Theme │ │ │ ├── NotificationToast.tsx │ │ │ ├── Theme.md │ │ │ ├── Theme.module.scss │ │ │ ├── Theme.spec.ts │ │ │ ├── Theme.tsx │ │ │ └── ThemeNative.tsx │ │ ├── TimeInput │ │ │ ├── TimeInput.md │ │ │ ├── TimeInput.module.scss │ │ │ ├── TimeInput.spec.ts │ │ │ ├── TimeInput.tsx │ │ │ ├── TimeInputNative.tsx │ │ │ └── utils.ts │ │ ├── Timer │ │ │ ├── Timer.md │ │ │ ├── Timer.spec.ts │ │ │ ├── Timer.tsx │ │ │ └── TimerNative.tsx │ │ ├── Toggle │ │ │ ├── Toggle.module.scss │ │ │ └── Toggle.tsx │ │ ├── ToneChangerButton │ │ │ ├── ToneChangerButton.md │ │ │ ├── ToneChangerButton.spec.ts │ │ │ └── ToneChangerButton.tsx │ │ ├── ToneSwitch │ │ │ ├── ToneSwitch.md │ │ │ ├── ToneSwitch.module.scss │ │ │ ├── ToneSwitch.spec.ts │ │ │ ├── ToneSwitch.tsx │ │ │ └── ToneSwitchNative.tsx │ │ ├── Tooltip │ │ │ ├── Tooltip.md │ │ │ ├── Tooltip.module.scss │ │ │ ├── Tooltip.spec.ts │ │ │ ├── Tooltip.tsx │ │ │ └── TooltipNative.tsx │ │ ├── Tree │ │ │ ├── testData.ts │ │ │ ├── Tree-dynamic.spec.ts │ │ │ ├── Tree-icons.spec.ts │ │ │ ├── Tree.md │ │ │ ├── Tree.spec.ts │ │ │ ├── TreeComponent.module.scss │ │ │ ├── TreeComponent.tsx │ │ │ └── TreeNative.tsx │ │ ├── TreeDisplay │ │ │ ├── TreeDisplay.md │ │ │ ├── TreeDisplay.module.scss │ │ │ ├── TreeDisplay.tsx │ │ │ └── TreeDisplayNative.tsx │ │ ├── ValidationSummary │ │ │ ├── ValidationSummary.module.scss │ │ │ └── ValidationSummary.tsx │ │ └── VisuallyHidden.tsx │ ├── components-core │ │ ├── abstractions │ │ │ ├── ComponentRenderer.ts │ │ │ ├── LoaderRenderer.ts │ │ │ ├── standalone.ts │ │ │ └── treeAbstractions.ts │ │ ├── action │ │ │ ├── actions.ts │ │ │ ├── APICall.tsx │ │ │ ├── FileDownloadAction.tsx │ │ │ ├── FileUploadAction.tsx │ │ │ ├── NavigateAction.tsx │ │ │ └── TimedAction.tsx │ │ ├── ApiBoundComponent.tsx │ │ ├── appContext │ │ │ ├── date-functions.ts │ │ │ ├── math-function.ts │ │ │ └── misc-utils.ts │ │ ├── AppContext.tsx │ │ ├── behaviors │ │ │ ├── Behavior.tsx │ │ │ └── CoreBehaviors.tsx │ │ ├── component-hooks.ts │ │ ├── ComponentDecorator.tsx │ │ ├── ComponentViewer.tsx │ │ ├── CompoundComponent.tsx │ │ ├── constants.ts │ │ ├── DebugViewProvider.tsx │ │ ├── descriptorHelper.ts │ │ ├── devtools │ │ │ ├── InspectorDialog.module.scss │ │ │ ├── InspectorDialog.tsx │ │ │ └── InspectorDialogVisibilityContext.tsx │ │ ├── EngineError.ts │ │ ├── event-handlers.ts │ │ ├── InspectorButton.module.scss │ │ ├── InspectorContext.tsx │ │ ├── interception │ │ │ ├── abstractions.ts │ │ │ ├── ApiInterceptor.ts │ │ │ ├── ApiInterceptorProvider.tsx │ │ │ ├── apiInterceptorWorker.ts │ │ │ ├── Backend.ts │ │ │ ├── Errors.ts │ │ │ ├── IndexedDb.ts │ │ │ ├── initMock.ts │ │ │ ├── InMemoryDb.ts │ │ │ ├── ReadonlyCollection.ts │ │ │ └── useApiInterceptorContext.tsx │ │ ├── loader │ │ │ ├── ApiLoader.tsx │ │ │ ├── DataLoader.tsx │ │ │ ├── ExternalDataLoader.tsx │ │ │ ├── Loader.tsx │ │ │ ├── MockLoaderRenderer.tsx │ │ │ └── PageableLoader.tsx │ │ ├── LoaderComponent.tsx │ │ ├── markup-check.ts │ │ ├── parts.ts │ │ ├── renderers.ts │ │ ├── rendering │ │ │ ├── AppContent.tsx │ │ │ ├── AppRoot.tsx │ │ │ ├── AppWrapper.tsx │ │ │ ├── buildProxy.ts │ │ │ ├── collectFnVarDeps.ts │ │ │ ├── ComponentAdapter.tsx │ │ │ ├── ComponentWrapper.tsx │ │ │ ├── Container.tsx │ │ │ ├── containers.ts │ │ │ ├── ContainerWrapper.tsx │ │ │ ├── ErrorBoundary.module.scss │ │ │ ├── ErrorBoundary.tsx │ │ │ ├── InvalidComponent.module.scss │ │ │ ├── InvalidComponent.tsx │ │ │ ├── nodeUtils.ts │ │ │ ├── reducer.ts │ │ │ ├── renderChild.tsx │ │ │ ├── StandaloneComponent.tsx │ │ │ ├── StateContainer.tsx │ │ │ ├── UnknownComponent.module.scss │ │ │ ├── UnknownComponent.tsx │ │ │ └── valueExtractor.ts │ │ ├── reportEngineError.ts │ │ ├── RestApiProxy.ts │ │ ├── script-runner │ │ │ ├── asyncProxy.ts │ │ │ ├── AttributeValueParser.ts │ │ │ ├── bannedFunctions.ts │ │ │ ├── BindingTreeEvaluationContext.ts │ │ │ ├── eval-tree-async.ts │ │ │ ├── eval-tree-common.ts │ │ │ ├── eval-tree-sync.ts │ │ │ ├── ParameterParser.ts │ │ │ ├── process-statement-async.ts │ │ │ ├── process-statement-common.ts │ │ │ ├── process-statement-sync.ts │ │ │ ├── ScriptingSourceTree.ts │ │ │ ├── simplify-expression.ts │ │ │ ├── statement-queue.ts │ │ │ └── visitors.ts │ │ ├── StandaloneApp.tsx │ │ ├── StandaloneExtensionManager.ts │ │ ├── TableOfContentsContext.tsx │ │ ├── theming │ │ │ ├── _themes.scss │ │ │ ├── component-layout-resolver.ts │ │ │ ├── extendThemeUtils.ts │ │ │ ├── hvar.ts │ │ │ ├── layout-resolver.ts │ │ │ ├── parse-layout-props.ts │ │ │ ├── StyleContext.tsx │ │ │ ├── StyleRegistry.ts │ │ │ ├── ThemeContext.tsx │ │ │ ├── ThemeProvider.tsx │ │ │ ├── themes │ │ │ │ ├── base-utils.ts │ │ │ │ ├── palette.ts │ │ │ │ ├── root.ts │ │ │ │ ├── solid.ts │ │ │ │ ├── theme-colors.ts │ │ │ │ └── xmlui.ts │ │ │ ├── themeVars.module.scss │ │ │ ├── themeVars.ts │ │ │ ├── transformThemeVars.ts │ │ │ └── utils.ts │ │ ├── utils │ │ │ ├── actionUtils.ts │ │ │ ├── audio-utils.ts │ │ │ ├── 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 -------------------------------------------------------------------------------- /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); } ``` -------------------------------------------------------------------------------- /xmlui/scripts/generate-docs/utils.mjs: -------------------------------------------------------------------------------- ``` import { existsSync, readdirSync, statSync, unlinkSync } from "fs"; import { posix } from "path"; import { TABLE_CONFIG } from "./constants.mjs"; /** * Creates a markdown table. * The number of headers can be more or less than the number of rows. * @param {Record<string, string[] | boolean>} data * @param {string[]} data.headers A list of headers for the table * @param {string[][]} data.rows A list of rows of the table content, * each row is an array of strings * @param {boolean} data.rowNums Toggle to generate row numbers or not * @returns {string} Generated markdown table */ export function createTable({ headers = [], rows = [], rowNums = false }) { let table = ""; if (headers.length === 0 && rows.length === 0) { return table; } if (rowNums) { headers.unshift(TABLE_CONFIG.DEFAULT_ROW_NUM_HEADER); } table += "| " + headers .map((h) => { if (typeof h === "string") return h; if (typeof h === "object") return h.value; }) .join(" | ") + " |\n"; table += "| " + headers .map((h) => { if (typeof h === "object" && h.style === TABLE_CONFIG.STYLES.LEFT) return TABLE_CONFIG.MARKDOWN_ALIGNMENTS.LEFT; if (typeof h === "object" && h.style === TABLE_CONFIG.STYLES.CENTER) return TABLE_CONFIG.MARKDOWN_ALIGNMENTS.CENTER; if (typeof h === "object" && h.style === TABLE_CONFIG.STYLES.RIGHT) return TABLE_CONFIG.MARKDOWN_ALIGNMENTS.RIGHT; return TABLE_CONFIG.MARKDOWN_ALIGNMENTS.DEFAULT; }) .join(" | ") + " |\n"; rows.forEach((row) => { table += "| " + (rowNums ? rows.indexOf(row) + 1 + " | " : "") + row.join(" | ") + " |\n"; }); return table; } /** * Multi-liner (commented and compatible with really old javascript versions) * Source: https://stackoverflow.com/a/62732509 */ export function winPathToPosix(windowsPath) { // handle the edge-case of Window's long file names // See: https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#short-vs-long-names windowsPath = windowsPath.replace(/^\\\\\?\\/, ""); // convert the separators, valid since both \ and / can't be in a windows filename windowsPath = windowsPath.replace(/\\/g, "/"); // compress any // or /// to be just /, which is a safe operation under POSIX // and prevents accidental errors caused by manually doing path1+path2 windowsPath = windowsPath.replace(/\/\/+/g, "/"); return windowsPath; } /** * Simple but slow * @param {string} buffer */ export function strBufferToLines(buffer) { if (typeof buffer !== "string") { throw new Error("Only string buffers are supported."); } return buffer.split(/\r?\n/); } /** * Recursive function that traverses a given folder and applies an optional function on * each of the folders/files found inside. */ export function traverseDirectory(node, visitor, level = 0) { level++; const dirContents = readdirSync(node.path); if (!node.children) node.children = dirContents; for (const itemName of dirContents) { const itemPath = [winPathToPosix(node.path), itemName].join(posix.sep); const itemIsDir = statSync(itemPath).isDirectory(); const childNode = { name: itemName, path: itemPath, parent: node, }; visitor && visitor(childNode, level); if (itemIsDir) { traverseDirectory(childNode, visitor, level); } } } /** * Removes duplicate entries from the input array. */ export function gatherAndRemoveDuplicates(container, byAttribute = "id") { const idSet = new Set(); const duplicates = []; container.forEach((item) => { if (idSet.has(item[byAttribute])) { duplicates.push(item); } idSet.add(item[byAttribute]); }); return { filtered: container.filter((item) => !duplicates.includes(item)), duplicates, }; } export function toNormalizedUpperCase(rawStr) { return rawStr .trim() .toLocaleUpperCase() .replaceAll(/[^A-Za-z0-9_]/g, "_") .replaceAll(/__+/g, "_"); // <- remove duplicate underscores } export function toHeadingPath(rawStr) { return rawStr .trim() .toLocaleLowerCase() .replaceAll(/[^A-Za-z0-9-]/g, "-") .replaceAll(/--+/g, "-") .replace(/^-|-$/, ""); } /** * Converts string from kebab case to space separated string with uppercase starting character * @param {string} rawStr input in kebab case (e.g. "hello-there-friend") * @returns {string} camel cased string (e.g. "Hello There Friend") */ export function fromKebabtoReadable(rawStr) { return rawStr .trim() .split("-") .map((n) => n[0].toUpperCase() + n.slice(1)) .join(" "); } /** * Deletes a file if it exists * @param {string} filePath the full path of the file to delete */ export function deleteFileIfExists(filePath) { if (existsSync(filePath)) { unlinkSync(filePath); } } /** * Removes duplicate new line characters adjecent to one another in a list of strings * @param {string[]} buffer the list of strings * @returns {string[]} the list of strings without duplicate new line characters */ export function removeAdjacentNewlines(buffer) { const result = []; let prevWasNewline = false; for (const item of buffer) { // Check if the current item is only newline characters const isNewline = /^\s*$/.test(item); if (!isNewline || !prevWasNewline) { result.push(item); } prevWasNewline = isNewline; } return result; } /** * The summary file may contain further sections other than the summary table. * Thus, we only (re)generate the section that contains the summary table. * This is done by finding the heading for the start of the summary table section * and either the end of file or the next section heading. * @param {string} buffer the string containing the file contents * @param {string} sectionHeading The section to look for, has to have heading level as well */ export function getSectionBeforeAndAfter(buffer, sectionHeading) { if (!sectionHeading) { return { beforeSection: buffer, afterSection: "" }; } const lines = strBufferToLines(buffer); const sectionStartIdx = lines.findIndex((line) => line.includes(sectionHeading)); // If sectionHeading isn't found, return the buffer unchanged if (sectionStartIdx === -1) { return { beforeSection: buffer, afterSection: "" }; } // Find the next heading after the section heading let nextHeadingIdx = -1; for (let i = sectionStartIdx + 1; i < lines.length; i++) { if (/^#+\s/.test(lines[i])) { nextHeadingIdx = i; break; } } // Remove lines after the section heading and before the next heading (or end of file) const beforeSection = lines.slice(0, sectionStartIdx).join("\n"); const afterSection = (nextHeadingIdx !== -1 ? lines.slice(nextHeadingIdx) : []).join("\n"); return { beforeSection, afterSection }; } ``` -------------------------------------------------------------------------------- /xmlui/src/components/DatePicker/DatePicker.md: -------------------------------------------------------------------------------- ```markdown %-DESC-START **Key features:** - **Flexible modes**: Single date selection (default) or date range selection - **Format customization**: Support for various date formats (MM/dd/yyyy, yyyy-MM-dd, etc.) - **Date restrictions**: Set minimum/maximum dates and disable specific dates - **Localization options**: Configure first day of week and show week numbers %-DESC-END %-API-START setValue ```xmlui-pg copy {3, 9, 12} display name="Example: setValue" height="500px" <App> <HStack> <Button label="Set Date to 05/25/2024" onClick="picker.setValue('05/25/2024')" /> <Button label="Remove Date" onClick="picker.setValue('')" /> </HStack> <DatePicker inline id="picker" /> </App> ``` %-API-END %-PROP-START initialValue ```xmlui-pg copy display name="Example: initialValue" height="440px" <App> <DatePicker inline initialValue="05/25/2024" /> </App> ``` %-PROP-END %-PROP-START placeholder ```xmlui-pg copy display name="Example: placeholder" height="500px" <App> <DatePicker placeholder="This is a placeholder" /> </App> ``` %-PROP-END %-PROP-START enabled ```xmlui-pg copy display name="Example: enabled" height="120px" <App> <DatePicker enabled="false" /> </App> ``` %-PROP-END %-PROP-START validationStatus | Value | Description | | :-------- | :---------------------------------------------------- | | `valid` | Visual indicator for an input that is accepted | | `warning` | Visual indicator for an input that produced a warning | | `error` | Visual indicator for an input that produced an error | ```xmlui-pg copy display name="Example: validationStatus" height="500px" <App> <DatePicker /> <DatePicker validationStatus="valid" /> <DatePicker validationStatus="warning" /> <DatePicker validationStatus="error" /> </App> ``` %-PROP-END %-PROP-START mode ```xmlui-pg copy {2-3} display name="Example: mode" height="560px" <App> <DatePicker mode="single" /> <DatePicker mode="range" /> </App> ``` %-PROP-END %-PROP-START dateFormat Formats handle years (`y`), months (`m` or `M`), days of the month (`d`). Providing multiple placeholder letters changes the display of the date. The table below shows the available date formats: | Format | Example | | :--------- | :--------- | | MM/dd/yyyy | 05/25/2024 | | MM-dd-yyyy | 05-25-2024 | | yyyy/MM/dd | 2024/05/25 | | yyyy-MM-dd | 2024-05-25 | | dd/MM/yyyy | 25/05/2024 | | dd-MM-yyyy | 25-05-2024 | | yyyyMMdd | 20240525 | | MMddyyyy | 05252024 | ```xmlui-pg copy display name="Example: dateFormat" height="440px" <App> <DatePicker inline dateFormat="dd-MM-yyyy" initialValue="05/25/2024" /> </App> ``` %-PROP-END %-PROP-START showWeekNumber ```xmlui-pg copy display name="Example: showWeekNumber" height="500px" <App> <DatePicker showWeekNumber="true" /> </App> ``` %-PROP-END %-PROP-START weekStartsOn | Day | Number | | :-------- | :----- | | Sunday | 0 | | Monday | 1 | | Tuesday | 2 | | Wednesday | 3 | | Thursday | 4 | | Friday | 5 | | Saturday | 6 | ```xmlui-pg copy display name="Example: weekStartsOn" height="440px" <App> <DatePicker inline weekStartsOn="1" /> </App> ``` %-PROP-END %-PROP-START minValue ```xmlui-pg copy display name="Example: minValue" height="440px" <App> <DatePicker inline minValue="05/24/2024" /> </App> ``` %-PROP-END %-PROP-START maxValue ```xmlui-pg copy display name="Example: maxValue" height="440px" <App> <DatePicker inline maxValue="05/26/2024" /> </App> ``` %-PROP-END %-PROP-START disabledDates The `disabledDates` prop supports multiple patterns for disabling specific dates in the calendar. You can use Date objects, strings (parsed using the `dateFormat`), or complex matcher objects. **Basic patterns:** | Pattern | Description | Example | | :------ | :---------- | :------ | | Single string | Disable one specific date | `"05/25/2024"` | | Array of strings | Disable multiple specific dates | `["05/25/2024", "05/26/2024"]` | | Boolean | Disable all dates | `true` | > [!INFO] You can use the [getDate()](/globals#getdate) function to query the current date. ```xmlui-pg copy display name="Example: Disable specific dates" height="440px" <App> <DatePicker inline disabledDates="{['05/26/2024', '05/27/2024']}" initialValue="05/25/2024" /> </App> ``` **Advanced patterns:** | Pattern | Description | Example | | :------ | :---------- | :------ | | Date range | Disable a range of dates | `{from: "05/20/2024", to: "05/25/2024"}` | | Day of week | Disable specific weekdays (0=Sunday, 6=Saturday) | `{dayOfWeek: [0, 6]}` | | Before date | Disable all dates before a specific date | `{before: "05/25/2024"}` | | After date | Disable all dates after a specific date | `{after: "05/25/2024"}` | | Date interval | Disable dates between two dates (exclusive) | `{before: "05/30/2024", after: "05/20/2024"}` | ```xmlui-pg copy display name="Example: Disable weekends" height="440px" <App> <DatePicker inline disabledDates="{{dayOfWeek: [0, 6]}}" /> </App> ``` ```xmlui-pg copy display name="Example: Disable date range" height="440px" <App> <DatePicker inline disabledDates="{{from: '05/20/2024', to: '05/25/2024'}}" initialValue="05/18/2024" /> </App> ``` ```xmlui-pg copy display name="Example: Disable dates before today" height="440px" <App> <DatePicker inline disabledDates="{{before: getDate()}}" intialValue="{getDate()}"/> </App> ``` ```xmlui-pg copy display name="Example: Disable dates today and after" height="440px" <App> <DatePicker inline disabledDates="{[getDate(), {after: getDate()}]}" intialValue="{getDate()}"/> </App> ``` ```xmlui-pg copy display name="Example: Complex combination" height="440px" <App> <DatePicker inline disabledDates="{[ {dayOfWeek: [0, 6]}, {from: '12/24/2024', to: '12/26/2024'}, '01/01/2025']}" initialValue="12/20/2024" /> </App> ``` %-PROP-END %-EVENT-START didChange Write in the input field and see how the `Text` underneath it is updated in parallel. ```xmlui-pg copy {2} display name="Example: didChange" height="520px" <App var.field="(none)"> <Text value="{field}" /> <DatePicker inline initialValue="{field}" onDidChange="(val) => field = val" /> </App> ``` %-EVENT-END %-EVENT-START gotFocus Clicking on the `DatePicker` in the example demo changes the label text. Note how clicking elsewhere resets the text to the original. ```xmlui-pg copy {4-5} display name="Example: gotFocus/lostFocus" height="540px" <App var.isFocused="false"> <Text value="{isFocused === true ? 'DatePicker focused' : 'DatePicker lost focus'}" /> <DatePicker onGotFocus="isFocused = true" onLostFocus="isFocused = false" /> </App> ``` %-EVENT-END ``` -------------------------------------------------------------------------------- /packages/xmlui-playground/src/playground/StandalonePlaygroundNative.tsx: -------------------------------------------------------------------------------- ```typescript import { useEffect, useId, useMemo, useReducer, useRef, useState } from "react"; import { appDescriptionInitialized, contentChanged, optionsInitialized, PlaygroundContext, playgroundReducer, } from "../state/store"; import { decompressData, INITIAL_PLAYGROUND_STATE } from "../utils/helpers"; import { ToastProvider } from "../providers/ToastProvider"; import styles from "./StandalonePlaygroundNative.module.scss"; import { useToast } from "../hooks/useToast"; import { ErrorBoundary, useThemes } from "xmlui"; import { Header } from "./Header"; import { PlaygroundContent } from "./PlaygroundContent"; import { Theme } from "../themes/theme"; export const StandalonePlayground = () => { const { showToast } = useToast(); const id = useId(); const [loading, setLoading] = useState(true); const { setActiveThemeTone } = useThemes(); const [playgroundState, dispatch] = useReducer(playgroundReducer, INITIAL_PLAYGROUND_STATE); const initialized = useRef(false); const queryParams = useMemo(() => { if (typeof window !== "undefined") { return Object.fromEntries(new URLSearchParams("?app=" + window.location.hash.split("#")[2])); } return {}; }, []); useEffect(() => { const getApp = async () => { try { const data = JSON.parse(await decompressData(queryParams.app as string)); dispatch(appDescriptionInitialized(data.standalone)); dispatch( optionsInitialized({ ...playgroundState.options, ...data.options, content: "app", orientation: "horizontal", }), ); setActiveThemeTone(data.options.activeTone || "light"); dispatch(contentChanged(data.options.content)); } catch (e) { showToast({ type: "error", title: "Error", description: "The app could not be loaded", }); } setLoading(false); initialized.current = true; }; if (initialized.current) { return; } else { if (queryParams.app && queryParams.app !== "undefined") { getApp(); } else { dispatch( appDescriptionInitialized({ config: { name: "Hello World", description: "", appGlobals: {}, resources: {}, themes: [Theme], }, components: [], app: "<App>Hello World!</App>", }), ); dispatch( optionsInitialized({ activeTheme: "theme", activeTone: "light", content: "app", orientation: "horizontal", id: 0, allowStandalone: true, language: "xmlui", emulatedApi: undefined, fixedTheme: false, }), ); setLoading(false); initialized.current = true; } } }, [queryParams.app, showToast, setActiveThemeTone, playgroundState.options]); const playgroundContextValue = useMemo(() => { return { editorStatus: playgroundState.editorStatus, status: playgroundState.status, options: playgroundState.options, text: playgroundState.text, originalAppDescription: playgroundState.originalAppDescription, appDescription: playgroundState.appDescription, dispatch, playgroundId: id, error: playgroundState.error, }; }, [ playgroundState.editorStatus, playgroundState.status, playgroundState.options, playgroundState.text, playgroundState.originalAppDescription, playgroundState.appDescription, id, playgroundState.error, ]); return ( <ToastProvider> <PlaygroundContext.Provider value={playgroundContextValue}> <ErrorBoundary> {loading && <AnimatedLogo />} {!loading && ( <div className={styles.standalonePlayground}> {!playgroundState.options.previewMode && <Header standalone={true} />} <div style={{ flexGrow: 1, overflow: "auto" }}> <PlaygroundContent standalone={true} /> </div> </div> )} </ErrorBoundary> </PlaygroundContext.Provider> </ToastProvider> ); }; function AnimatedLogo() { return ( <div className={styles.loadingContainer}> <div className={styles.loadingText}>Loading XMLUI App...</div> <div className={styles.logoWrapper}> <svg viewBox="0 0 26 26" xmlns="http://www.w3.org/2000/svg"> {/* Unchanged inner paths */} <path d="M9.04674 19.3954C8.2739 19.3954 7.60226 19.2265 7.03199 18.8887C6.47443 18.5384 6.0435 18.0505 5.73938 17.425C5.43526 16.7869 5.2832 16.0362 5.2832 15.173V9.89961C5.2832 9.7745 5.32771 9.66815 5.41637 9.58059C5.50502 9.493 5.61275 9.44922 5.73938 9.44922H7.41222C7.55157 9.44922 7.6593 9.493 7.73524 9.58059C7.8239 9.66815 7.86841 9.7745 7.86841 9.89961V15.0604C7.86841 16.6117 8.55895 17.3874 9.94021 17.3874C10.5991 17.3874 11.1187 17.181 11.4988 16.7681C11.8917 16.3553 12.0881 15.786 12.0881 15.0604V9.89961C12.0881 9.7745 12.1325 9.66815 12.2211 9.58059C12.3098 9.493 12.4175 9.44922 12.5443 9.44922H14.217C14.3436 9.44922 14.4513 9.493 14.54 9.58059C14.6288 9.66815 14.6732 9.7745 14.6732 9.89961V18.7574C14.6732 18.8825 14.6288 18.9888 14.54 19.0764C14.4513 19.164 14.3436 19.2078 14.217 19.2078H12.6773C12.538 19.2078 12.4239 19.164 12.3352 19.0764C12.2591 18.9888 12.2211 18.8825 12.2211 18.7574V17.988C11.879 18.4258 11.4545 18.7699 10.9476 19.0201C10.4407 19.2703 9.80704 19.3954 9.04674 19.3954Z" fill="#3367CC" /> <path d="M17.6397 19.2104C17.5129 19.2104 17.4052 19.1666 17.3165 19.079C17.2279 18.9914 17.1835 18.8851 17.1835 18.76V9.90221C17.1835 9.7771 17.2279 9.67075 17.3165 9.58319C17.4052 9.4956 17.5129 9.45182 17.6397 9.45182H19.2174C19.3567 9.45182 19.4644 9.4956 19.5404 9.58319C19.6292 9.67075 19.6736 9.7771 19.6736 9.90221V18.76C19.6736 18.8851 19.6292 18.9914 19.5404 19.079C19.4644 19.1666 19.3567 19.2104 19.2174 19.2104H17.6397ZM17.5636 7.8379C17.4243 7.8379 17.3102 7.80038 17.2215 7.72531C17.1454 7.63773 17.1074 7.52514 17.1074 7.38751V6.03633C17.1074 5.91122 17.1454 5.80487 17.2215 5.71731C17.3102 5.62972 17.4243 5.58594 17.5636 5.58594H19.2933C19.4327 5.58594 19.5467 5.62972 19.6354 5.71731C19.7242 5.80487 19.7686 5.91122 19.7686 6.03633V7.38751C19.7686 7.52514 19.7242 7.63773 19.6354 7.72531C19.5467 7.80038 19.4327 7.8379 19.2933 7.8379H17.5636Z" fill="#3367CC" /> {/* ✨ MODIFIED outer path for animation */} <path className={styles.animatedLogoPath} d="M23.0215 2.81748H2.53486V23.044H23.0215V2.81748Z" fill="none" stroke="#3367CC" strokeWidth="0.75" /> </svg> </div> </div> ); } ``` -------------------------------------------------------------------------------- /docs/content/components/NavPanel.md: -------------------------------------------------------------------------------- ```markdown # NavPanel [#navpanel] `NavPanel` defines the navigation structure within an App, serving as a container for NavLink and NavGroup components that create your application's primary navigation menu. Its appearance and behavior automatically adapt based on the App's layout configuration. **Key features:** - **Layout adaptation**: Automatically positions navigation horizontally or vertically based on App layout - **Navigation organization**: Contains NavLink and NavGroup components to build structured menus - **Logo integration**: Supports custom logo templates in vertical layouts via logoTemplate property - **Drawer mode**: Can optionally display navigation in a collapsible drawer interface - **Theme integration**: Inherits styling from the app's theme system for consistent appearance ## Properties [#properties] ### `inDrawer` (default: false) [#indrawer-default-false] This property determines if the navigation panel is displayed in a drawer. ### `logoTemplate` [#logotemplate] This property defines the logo template to display in the navigation panel with the `vertical` and `vertical-sticky` layout. ```xmlui-pg copy {3-8} display name="Example: logoTemplate" height={250} <App layout="vertical"> <NavPanel> <property name="logoTemplate"> <H3> <Icon name="drive" /> DriveDiag (Nav) </H3> </property> <NavLink label="Home" to="/" icon="home"/> <NavLink label="Page 1" to="/page1"/> </NavPanel> <Pages fallbackPath="/"> <Page url="/"> <Text value="Home" /> </Page> <Page url="/page1"> <Text value="Page 1" /> </Page> </Pages> </App> ``` ## 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)-NavPanel | $backgroundColor | $backgroundColor | | [backgroundColor](../styles-and-themes/common-units/#color)-NavPanel-horizontal | $backgroundColor-AppHeader | $backgroundColor-AppHeader | | [border](../styles-and-themes/common-units/#border)-NavPanel | 0px solid $borderColor | 0px solid $borderColor | | [borderBottom](../styles-and-themes/common-units/#border)-NavPanel | *none* | *none* | | [borderBottomColor](../styles-and-themes/common-units/#color)-NavPanel | *none* | *none* | | [borderBottomStyle](../styles-and-themes/common-units/#border-style)-NavPanel | *none* | *none* | | [borderBottomWidth](../styles-and-themes/common-units/#size)-NavPanel | *none* | *none* | | [borderColor](../styles-and-themes/common-units/#color)-NavPanel | *none* | *none* | | [borderEndEndRadius](../styles-and-themes/common-units/#border-rounding)-NavPanel | *none* | *none* | | [borderEndStartRadius](../styles-and-themes/common-units/#border-rounding)-NavPanel | *none* | *none* | | [borderHorizontal](../styles-and-themes/common-units/#border)-NavPanel | *none* | *none* | | [borderHorizontalColor](../styles-and-themes/common-units/#color)-NavPanel | *none* | *none* | | [borderHorizontalStyle](../styles-and-themes/common-units/#border-style)-NavPanel | *none* | *none* | | [borderHorizontalWidth](../styles-and-themes/common-units/#size)-NavPanel | *none* | *none* | | [borderLeft](../styles-and-themes/common-units/#border)-NavPanel | *none* | *none* | | [color](../styles-and-themes/common-units/#color)-NavPanel | *none* | *none* | | [borderLeftStyle](../styles-and-themes/common-units/#border-style)-NavPanel | *none* | *none* | | [borderLeftWidth](../styles-and-themes/common-units/#size)-NavPanel | *none* | *none* | | [borderRight](../styles-and-themes/common-units/#border)-NavPanel | *none* | *none* | | [color](../styles-and-themes/common-units/#color)-NavPanel | *none* | *none* | | [borderRightStyle](../styles-and-themes/common-units/#border-style)-NavPanel | *none* | *none* | | [borderRightWidth](../styles-and-themes/common-units/#size)-NavPanel | *none* | *none* | | [borderStartEndRadius](../styles-and-themes/common-units/#border-rounding)-NavPanel | *none* | *none* | | [borderStartStartRadius](../styles-and-themes/common-units/#border-rounding)-NavPanel | *none* | *none* | | [borderStyle](../styles-and-themes/common-units/#border-style)-NavPanel | *none* | *none* | | [borderTop](../styles-and-themes/common-units/#border)-NavPanel | *none* | *none* | | [borderTopColor](../styles-and-themes/common-units/#color)-NavPanel | *none* | *none* | | [borderTopStyle](../styles-and-themes/common-units/#border-style)-NavPanel | *none* | *none* | | [borderTopWidth](../styles-and-themes/common-units/#size)-NavPanel | *none* | *none* | | [borderHorizontal](../styles-and-themes/common-units/#border)-NavPanel | *none* | *none* | | [borderVerticalColor](../styles-and-themes/common-units/#color)-NavPanel | *none* | *none* | | [borderVerticalStyle](../styles-and-themes/common-units/#border-style)-NavPanel | *none* | *none* | | [borderVerticalWidth](../styles-and-themes/common-units/#size)-NavPanel | *none* | *none* | | [borderWidth](../styles-and-themes/common-units/#size)-NavPanel | *none* | *none* | | [boxShadow](../styles-and-themes/common-units/#boxShadow)-NavPanel | *none* | *none* | | [boxShadow](../styles-and-themes/common-units/#boxShadow)-NavPanel-vertical | 4px 0 4px 0 rgb(0 0 0 / 10%) | 4px 0 4px 0 rgb(0 0 0 / 10%) | | [horizontalAlignment](../styles-and-themes/common-units/#alignment)-logo-NavPanel | center | center | | [marginBottom](../styles-and-themes/common-units/#size)-logo-NavPanel | $space-4 | $space-4 | | [padding](../styles-and-themes/common-units/#size)-logo-NavPanel | *none* | *none* | | [padding](../styles-and-themes/common-units/#size)-NavPanel | *none* | *none* | | [paddingBottom](../styles-and-themes/common-units/#size)-logo-NavPanel | *none* | *none* | | [paddingBottom](../styles-and-themes/common-units/#size)-NavPanel | *none* | *none* | | [paddingHorizontal](../styles-and-themes/common-units/#size)-logo-NavPanel | $space-4 | $space-4 | | [paddingHorizontal](../styles-and-themes/common-units/#size)-NavPanel | 0 | 0 | | [paddingLeft](../styles-and-themes/common-units/#size)-logo-NavPanel | *none* | *none* | | [paddingLeft](../styles-and-themes/common-units/#size)-NavPanel | *none* | *none* | | [paddingRight](../styles-and-themes/common-units/#size)-logo-NavPanel | *none* | *none* | | [paddingRight](../styles-and-themes/common-units/#size)-NavPanel | *none* | *none* | | [paddingTop](../styles-and-themes/common-units/#size)-logo-NavPanel | *none* | *none* | | [paddingTop](../styles-and-themes/common-units/#size)-NavPanel | *none* | *none* | | [paddingVertical](../styles-and-themes/common-units/#size)-logo-NavPanel | $space-4 | $space-4 | | [paddingVertical](../styles-and-themes/common-units/#size)-NavPanel | *none* | *none* | ``` -------------------------------------------------------------------------------- /packages/xmlui-playground/src/utils/helpers.ts: -------------------------------------------------------------------------------- ```typescript import type { PlaygroundState } from "../state/store"; import JSZip from "jszip"; import { saveAs } from "file-saver"; import { type ComponentDef, type CompoundComponentDef, type ThemeDefinition, XmlUiHelper, type XmlUiNode, } from "xmlui"; import { decompress } from "../playground/utils"; import { decodeFromBase64 } from "../../../../xmlui/src/components-core/utils/base64-utils"; export function normalizePath(url?: string): string | undefined { if (!url) { return undefined; } if (url.startsWith("http://") || url.startsWith("https://")) { return url; } if (typeof window === "undefined") { return url; } // @ts-ignore const prefix = window.__PUBLIC_PATH || ""; if (!prefix) { return url; } const prefixWithoutTrailingSlash = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix; const urlWithoutLeadingSlash = url.startsWith("/") ? url.slice(1) : url; return `${prefixWithoutTrailingSlash}/${urlWithoutLeadingSlash}`; } async function fetchWithoutCache(url: string) { return fetch(normalizePath(url) as any, { headers: { "Cache-Control": "no-cache, no-store", }, }); } export function serialize(component: ComponentDef | CompoundComponentDef): string { if (component) { const xh = new XmlUiHelper(); try { const node = xh.transformComponentDefinition(component) as XmlUiNode; return xh.serialize(node, { prettify: true }); } catch (e) { console.log(e); return ""; } } return ""; } export async function decompressData(source: string) { const base64 = decodeURIComponent(source); const decoded = decodeFromBase64(base64); if (!decoded) { throw new Error("Failed to decode base64 data"); } const compressed = new TextEncoder().encode(decoded); return await decompress(compressed); } export const INITIAL_PLAYGROUND_STATE: PlaygroundState = { editorStatus: "idle", status: "idle", options: { orientation: "horizontal", swapped: false, content: "app", previewMode: false, id: 0, language: "xmlui", }, text: "", appDescription: { config: { name: "", appGlobals: {}, resources: {}, themes: [], }, components: [], app: "", }, originalAppDescription: { config: { name: "", appGlobals: {}, resources: {}, themes: [], }, components: [], app: "", }, error: null, }; function removeWhitespace(obj: any) { if (typeof obj === "string") { return obj.replace(/\s+/g, " ").trim(); // Remove extra whitespaces and newlines } else if (obj !== null && typeof obj === "object") { const newObj: any = Array.isArray(obj) ? [] : {}; for (const key in obj) { newObj[key] = removeWhitespace(obj[key]); } return newObj; } return obj; // Return the value as is if not a string or object } export const handleDownloadZip = async (appDescription: any) => { const operatingSystem = getOperatingSystem(); const zip = new JSZip(); const xmluiFolder = zip.folder("xmlui"); const xmluiStandalone = await fetchWithoutCache( "/resources/files/for-download/xmlui/xmlui-standalone.umd.js", ).then((res) => res.blob()); xmluiFolder?.file("xmlui-standalone.umd.js", xmluiStandalone); zip.file("Main.xmlui", appDescription.app); zip.file("config.json", JSON.stringify(appDescription.config, null, 2)); if (appDescription.components.length > 0) { const components = zip.folder("components"); appDescription.components.forEach((component: { name: string; component: string }) => { components?.file(`${component.name}.xmlui`, component.component); }); } if (appDescription.config.themes.length > 0) { const themes = zip.folder("themes"); appDescription.config.themes.forEach((theme: ThemeDefinition) => { themes?.file(`${theme.id}.json`, JSON.stringify(theme, null, 2)); }); } const emulatedApi = appDescription.api; if (emulatedApi) { const indexWithApiHtml = await fetchWithoutCache( "/resources/files/for-download/index-with-api.html", ).then((res) => res.blob()); zip.file("index.html", indexWithApiHtml); xmluiFolder?.file( "mockApiDef.js", `window.XMLUI_MOCK_API = ${JSON.stringify(removeWhitespace(emulatedApi), null, 2)};`, ); const emulatedApiWorker = await fetchWithoutCache( "/resources/files/for-download/mockApi.js", ).then((res) => res.blob()); zip.file("mockApi.js", emulatedApiWorker); } else { const indexHtml = await fetchWithoutCache("/resources/files/for-download/index.html").then( (res) => res.blob(), ); zip.file("index.html", indexHtml); } const startBat = await fetchWithoutCache("/resources/files/for-download/start.bat").then((res) => res.blob(), ); if (operatingSystem === "Windows") { zip.file("start.bat", startBat); } else { let fileName = operatingSystem === "Linux" ? "start-linux.sh" : "start-darwin.sh"; const startSh = await fetchWithoutCache(`/resources/files/for-download/${fileName}`).then( (res) => res.blob(), ); zip.file("start.sh", startSh, { unixPermissions: "777", }); } try { const content = await zip.generateAsync({ type: "blob", platform: operatingSystem === "Windows" ? "DOS" : "UNIX", }); saveAs(content, `${(appDescription.config.name || "xmlui-playground-app").trim()}.zip`); } catch (error) { console.error("An error occurred while generating the ZIP:", error); } }; export function preprocessCode(code: string): string { // Split code by newlines const lines = code.split("\n"); // Remove whitespace-only lines from the beginning and end let start = 0; while (start < lines.length && lines[start].trim() === "") { start++; } let end = lines.length - 1; while (end >= 0 && lines[end].trim() === "") { end--; } const trimmedLines = lines.slice(start, end + 1); // Calculate the minimum indentation const minIndent = Math.min( ...trimmedLines .filter((line) => line.trim() !== "") // Ignore empty lines for indentation .map((line) => line.match(/^\s*/)?.[0].length || 0), // Count leading spaces ); // Remove minIndent spaces from the beginning of each line const result = trimmedLines.map((line) => line.startsWith(" ".repeat(minIndent)) ? line.slice(minIndent) : line, ); // Join lines back into a single string return result.join("\n"); } function getOperatingSystem() { const userAgent = window.navigator.userAgent; const platform = window.navigator.platform; if (/Win/.test(platform)) { return "Windows"; } if (/Mac/.test(platform)) { return "MacOS"; } if (/Linux/.test(platform)) { return "Linux"; } if (/Android/.test(userAgent)) { return "Android"; } if (/iPhone|iPad|iPod/.test(userAgent)) { return "iOS"; } return "Unknown OS"; } ``` -------------------------------------------------------------------------------- /xmlui/src/components/Toggle/Toggle.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); } // --- CSS properties of a particular Checkbox variant @mixin checkboxVariant($variantName) { border-radius: createThemeVar("Input:borderRadius-Checkbox-#{$variantName}"); border-color: createThemeVar("Input:borderColor-Checkbox-#{$variantName}"); background-color: createThemeVar("Input:backgroundColor-Checkbox-#{$variantName}"); &:focus-visible { outline-width: createThemeVar("Input:outlineWidth-Checkbox-#{$variantName}--focus"); outline-color: createThemeVar("Input:outlineColor-Checkbox-#{$variantName}--focus"); outline-style: createThemeVar("Input:outlineStyle-Checkbox-#{$variantName}--focus"); outline-offset: createThemeVar("Input:outlineOffset-Checkbox-#{$variantName}--focus"); } } @mixin hoverAndDisabledState($componentName) { &:hover { border-color: createThemeVar("Input:borderColor-#{$componentName}-default--hover"); } &:disabled { cursor: not-allowed; background-color: createThemeVar("Input:backgroundColor-#{$componentName}--disabled"); border-color: createThemeVar("Input:borderColor-#{$componentName}--disabled"); } } @mixin checkedState($componentName) { &:checked { border-color: createThemeVar("Input:borderColor-checked-#{$componentName}"); background-color: createThemeVar("Input:backgroundColor-checked-#{$componentName}"); } &:checked:disabled { background-color: createThemeVar("Input:backgroundColor-#{$componentName}--disabled"); border-color: createThemeVar("Input:borderColor-#{$componentName}--disabled"); } &:checked.error { border-color: createThemeVar("Input:borderColor-checked-#{$componentName}-error"); background-color: createThemeVar("Input:backgroundColor-checked-#{$componentName}-error"); } &:checked.warning { border-color: createThemeVar("Input:borderColor-checked-#{$componentName}-warning"); background-color: createThemeVar("Input:backgroundColor-checked-#{$componentName}-warning"); } &:checked.valid { border-color: createThemeVar("Input:borderColor-checked-#{$componentName}-success"); background-color: createThemeVar("Input:backgroundColor-checked-#{$componentName}-success"); } } @layer components { .resetAppearance { /* Add if not using autoprefixer */ -webkit-appearance: none; appearance: none; /* Not removed via appearance */ margin: 0; } .label { width: 100%; } .inputContainer { z-index: -1; position: relative; opacity: 0; width: 0; height: 0; } // --------------- Checkbox --------------- .checkbox { display: grid; place-content: center; min-width: 1em; min-height: 1em; width: 1em; height: 1em; border: 2px solid transparent; &:not([readonly]) { cursor: pointer; } @include checkboxVariant("default"); @include hoverAndDisabledState("Checkbox"); .forceHover { border-color: createThemeVar("Input:borderColor-Checkbox-default--hover"); // Don't override background-color - let the existing background rules apply } &.error { @include checkboxVariant("error"); } &.warning { @include checkboxVariant("warning"); } &.valid { @include checkboxVariant("success"); } &::before { content: ""; width: 0.5em; height: 0.5em; transform: scale(0); transition: 0.1s transform ease-out; box-shadow: inset 1em 1em createThemeVar("backgroundColor-indicator-Checkbox"); transform-origin: center; clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%); } &:checked::before { transform: scale(1); } @include checkedState("Checkbox"); &:indeterminate { background-color: createThemeVar("Input:backgroundColor-checked-Checkbox"); border-color: createThemeVar("Input:borderColor-checked-Checkbox"); &[readonly] { pointer-events: none; } } &:indeterminate:disabled { background-color: createThemeVar("Input:backgroundColor-Checkbox--disabled"); border-color: createThemeVar("Input:borderColor-Checkbox--disabled"); } &:indeterminate::before { clip-path: circle(30% at 50% 50%); transform: scale(1); } } // --------------- Switch --------------- .switch { --thumb-size: 1rem; --thumb-position: 0%; --track-size: calc(var(--thumb-size) * 3); --padding-size: 4px; cursor: pointer; flex-shrink: 0; display: grid; align-items: center; grid: [track] 1fr / [track] 1fr; background-color: createThemeVar("Input:backgroundColor-Switch"); width: var(--track-size); min-height: var(--thumb-size); padding: var(--padding-size); border: 1px solid createThemeVar("Input:borderColor-Switch"); border-radius: 1rem; &::before { content: ""; grid-area: track; height: var(--thumb-size); width: var(--thumb-size); background: createThemeVar("backgroundColor-indicator-Switch"); border-radius: 50%; transform: translateX(var(--thumb-position)); transition: 0.3s transform; } &:checked { background: createThemeVar("backgroundColor-checked-Switch"); &::before { background: createThemeVar("backgroundColor-indicator-checked-Switch"); --thumb-position: calc( var(--track-size) - var(--thumb-size) - 2 * var(--padding-size) - 2px ); } } @include hoverAndDisabledState("Switch"); @include checkedState("Switch"); .forceHover { border-color: createThemeVar("Input:borderColor-Switch-default--hover"); // Don't override background-color - let the existing background rules apply } &:focus-visible { outline-width: createThemeVar("Input:outlineWidth-Switch--focus"); outline-color: createThemeVar("Input:outlineColor-Switch--focus"); outline-style: createThemeVar("Input:outlineStyle-Switch--focus"); outline-offset: createThemeVar("Input:outlineOffset-Switch--focus"); } &:disabled { &::before { background-color: createThemeVar("backgroundColor-Switch-indicator--disabled"); } } &.error { border-color: createThemeVar("Input:borderColor-Switch-error"); } &.warning { border-color: createThemeVar("Input:borderColor-Switch-warning"); } &.valid { border-color: createThemeVar("Input:borderColor-Switch-success"); } } } // --- We export the theme variables to add them to the component renderer :export { themeVars: t.json-stringify($themeVars); } ``` -------------------------------------------------------------------------------- /xmlui/src/components/FileInput/FileInputNative.tsx: -------------------------------------------------------------------------------- ```typescript import type React from "react"; import { type CSSProperties, useCallback, useEffect, useId, useRef, useState } from "react"; import type { DropzoneRootProps } from "react-dropzone"; import * as dropzone from "react-dropzone"; import * as VisuallyHidden from "@radix-ui/react-visually-hidden"; import classnames from "classnames"; import styles from "./FileInput.module.scss"; import type { RegisterComponentApiFn, UpdateStateFn } from "../../abstractions/RendererDefs"; import { noop } from "../../components-core/constants"; import { useEvent } from "../../components-core/utils/misc"; import type { ValidationStatus } from "../abstractions"; import type { ButtonThemeColor, ButtonVariant, SizeType, IconPosition } from "../abstractions"; import { Button } from "../Button/ButtonNative"; import { TextBox } from "../TextBox/TextBoxNative"; // https://github.com/react-dropzone/react-dropzone/issues/1259 const { useDropzone } = dropzone; // ============================================================================ // React FileInput component implementation type Props = { // General id?: string; enabled?: boolean; style?: CSSProperties; className?: string; // Button styles buttonLabel?: string; variant?: ButtonVariant; buttonThemeColor?: ButtonThemeColor; buttonSize?: SizeType; buttonIcon?: React.ReactNode; buttonIconPosition?: IconPosition; // Input props updateState?: UpdateStateFn; onDidChange?: (newValue: File[]) => void; onFocus?: () => void; onBlur?: () => void; registerComponentApi?: RegisterComponentApiFn; validationStatus?: ValidationStatus; autoFocus?: boolean; // Component-specific props value?: any; initialValue?: any; acceptsFileType?: string | string[]; multiple?: boolean; directory?: boolean; required?: boolean; placeholder?: string; buttonPosition?: "start" | "end"; }; export const defaultProps: Pick< Props, | "enabled" | "buttonPosition" | "buttonLabel" | "multiple" | "directory" | "updateState" | "onDidChange" | "onFocus" | "onBlur" | "buttonThemeColor" > = { enabled: true, buttonPosition: "end", buttonLabel: "Browse", multiple: false, directory: false, buttonThemeColor: "primary", updateState: noop, onDidChange: noop, onFocus: noop, onBlur: noop, }; export const FileInput = ({ id, enabled = defaultProps.enabled, style, className, placeholder, buttonPosition = defaultProps.buttonPosition, buttonLabel = defaultProps.buttonLabel, buttonIcon, buttonIconPosition, variant, buttonThemeColor, buttonSize, autoFocus, validationStatus, updateState = defaultProps.updateState, onDidChange = defaultProps.onDidChange, onFocus = defaultProps.onFocus, onBlur = defaultProps.onBlur, registerComponentApi, value, initialValue, acceptsFileType, multiple = defaultProps.multiple, directory = defaultProps.directory, required, ...rest }: Props) => { const _id = useId(); id = id || _id; // Don't accept any (initial) value if it is not a File array explicitly const _initialValue: File[] | undefined = isFileArray(initialValue) ? initialValue : undefined; const _value: File[] | undefined = isFileArray(value) ? value : undefined; const buttonRef = useRef<HTMLButtonElement>(null); const _acceptsFileType = typeof acceptsFileType === "string" ? acceptsFileType : acceptsFileType?.join(","); useEffect(() => { if (autoFocus) { setTimeout(() => { buttonRef.current?.focus(); }, 0); } }, [autoFocus]); // --- Initialize the related field with the input's initial value useEffect(() => { updateState({ value: _initialValue }, { initial: true }); }, [_initialValue, updateState]); // --- Manage obtaining and losing the focus const handleOnFocus = useCallback((e: React.FocusEvent) => { // Only fire onFocus if focus is coming from outside the component if (!e.currentTarget.contains(e.relatedTarget as Node)) { onFocus?.(); } }, [onFocus]); const handleOnBlur = useCallback((e: React.FocusEvent) => { // Only fire onBlur if focus is leaving the component entirely if (!e.currentTarget.contains(e.relatedTarget as Node)) { onBlur?.(); } }, [onBlur]); const focus = useCallback(() => { buttonRef.current?.focus(); }, []); // --- Handle the value change events for this input const onDrop = useCallback( (acceptedFiles: File[]) => { if (!acceptedFiles.length) return; updateState({ value: acceptedFiles }); onDidChange(acceptedFiles); }, [updateState, onDidChange], ); const { getRootProps, getInputProps, open } = useDropzone({ disabled: !enabled, multiple: multiple || directory, onDrop, noClick: true, noKeyboard: true, noDragEventsBubbling: true, useFsAccessApi: directory === false, }); const doOpen = useEvent(() => { open(); }); useEffect(() => { registerComponentApi?.({ focus, open: doOpen, }); }, [focus, doOpen, registerComponentApi]); // Solution source: https://stackoverflow.com/questions/1084925/input-type-file-show-only-button return ( <div className={classnames(styles.container, className, { [styles.buttonStart]: buttonPosition === "start", [styles.buttonEnd]: buttonPosition === "end", })} style={style} onFocus={handleOnFocus} onBlur={handleOnBlur} tabIndex={-1} {...rest} > <button id={id} {...getRootProps({ tabIndex: 0, disabled: !enabled, className: styles.textBoxWrapper, onClick: open, ref: buttonRef, type: "button", })} > <VisuallyHidden.Root> <input {...getInputProps({ webkitdirectory: directory ? "true" : undefined, } as DropzoneRootProps)} accept={_acceptsFileType} /> </VisuallyHidden.Root> <TextBox placeholder={placeholder} enabled={enabled} value={_value?.map((v) => v.name).join(", ") || ""} validationStatus={validationStatus} readOnly tabIndex={-1} /> </button> <Button disabled={!enabled} type="button" onClick={open} icon={buttonIcon} iconPosition={buttonIconPosition} variant={variant} themeColor={buttonThemeColor} size={buttonSize} className={styles.button} autoFocus={autoFocus} > {buttonLabel} </Button> </div> ); }; export function isFile(value: any): value is File { return value instanceof File; } export function isFileArray(value: any): value is File[] { return Array.isArray(value) && value.every(isFile); } ``` -------------------------------------------------------------------------------- /xmlui/src/abstractions/RendererDefs.ts: -------------------------------------------------------------------------------- ```typescript import type { CSSProperties, ForwardedRef, ReactNode, RefObject } from "react"; import type { AppContextObject } from "./AppContextDefs"; import type { ComponentDef, ComponentMetadata, CompoundComponentDef, DynamicChildComponentDef, ParentRenderContext, } from "./ComponentDefs"; import type { ContainerState } from "./ContainerDefs"; import type { LookupActionOptions, LookupAsyncFn, LookupSyncFn } from "./ActionDefs"; import type { AsyncFunction } from "./FunctionDefs"; import type {ComponentApi} from "../components-core/rendering/ContainerWrapper"; // This interface defines the renderer context for the exposed components of the // XMLUI framework. export interface RendererContext<TMd extends ComponentMetadata = ComponentMetadata> extends ComponentRendererContextBase<TMd> { uid: symbol; // The unique identifier of the component instance updateState: UpdateStateFn; // A component invokes this function to change its internal state // When a component wants to access a property value (which may contain a binding // expression to evaluate), it must use this property to get the current value. extractValue: ValueExtractor; // This function gets a physical resource URL according to the provided logical URL. extractResourceUrl: (url?: string) => string | undefined; // This function gets an async executable function that handles an event. lookupEventHandler: LookupEventHandlerFn<TMd>; registerComponentApi: RegisterComponentApiFn; // A component can register its APIs with this function lookupAction: LookupAsyncFn; // This function obtains an action by its name with the specified options // This function retrieves a sync function the component can use as a callback lookupSyncCallback: LookupSyncFn; className?: string; } export type UpdateStateFn = (componentState: any, options?: any) => void; // This function updates the state of a component. // This type represent the function that extracts the value from a component property export type ValueExtractor = { (expression?: any, strict?: boolean): any; // Get a value (any) from a component property asString(expression?: any): string; // Get a string value from an expression // Get an optional string value from an expression asOptionalString<T extends string>(expression?: any, defValue?: string): T | undefined; // Get an optional string value from an expression asOptionalStringArray(expression?: any): (string | undefined)[]; asDisplayText(expression?: any): string; // Get a display string value from an expression asNumber(expression?: any): number; // Get a number value from an expression // Get an optional number value from an expression asOptionalNumber(expression?: any, defValue?: number): number | undefined; // Get a boolean value (JavaScript semantics) from an expression asBoolean(expression?: any): boolean; // Get an optional Boolean value from an expression asOptionalBoolean(expression?: any, defValue?: boolean): boolean | undefined; // Get a CSS size value from an expression asSize(expression?: any): string; }; // This function retrieves an async function for a particular component's specified // event to be invoked as an event handler (`undefined` if the particular event // handler is not defined). export type LookupEventHandlerFn<TMd extends ComponentMetadata = ComponentMetadata> = ( eventName: keyof NonNullable<TMd["events"]>, actionOptions?: LookupActionOptions, ) => AsyncFunction | undefined; // This type represents a function that registers all API endpoints of a particular component. export type RegisterComponentApiFn = (componentApi: ComponentApi) => void; // Function signature to render a particular child component (or set of child components) export type RenderChildFn<L extends ComponentDef = ComponentDef> = ( children?: | ComponentDef | ComponentDef[] | DynamicChildComponentDef | DynamicChildComponentDef[] | string, layoutContext?: LayoutContext<L>, parentRenderContext?: ParentRenderContext, uidInfoRef?: RefObject<Record<string, any>>, ref?: ForwardedRef<any>, rest?: Record<string, any> ) => ReactNode | ReactNode[]; // Each component is rendered in a particular layout context (for example, within a // stack). This type provides information about that context and the operations that // render children in it. export type LayoutContext<T extends ComponentDef = ComponentDef> = { type?: string; // The type of the layout context // This function allows the React representation of a particular child node to be // wrapped in whatever React components to accommodate the current layout context. // When the engine is about to render children in a particular layout context, it // checks the existence of this function. If declared, the engine invokes it. wrapChild?: ( context: RendererContext<T>, renderedChild: ReactNode, metadata?: ComponentMetadata, ) => ReactNode; // Arbitrary props extending the layout context [key: string]: any; }; export type NonCssLayoutProps = { horizontalAlignment?: string; verticalAlignment?: string; orientation?: string; }; // This function renders a component definition into a React component export type ComponentRendererFn<T extends ComponentDef> = ( context: RendererContext<T>, ) => ReactNode; // This function renders a component definition into a React component export type CompoundComponentRendererInfo = { compoundComponentDef: CompoundComponentDef; metadata?: ComponentMetadata; }; // Components must be registered with a component registry so the engine can use them. // This type collects the information held by the registry. export type ComponentRendererDef<T extends ComponentDef = any> = { // The component's type identifier. In the markup, the component must use this name // to be recognized. type: string; // This function renders the component from its definition to its React representation. renderer: ComponentRendererFn<T>; // The metadata to use when rendering the component metadata?: ComponentMetadata; }; // Rendering components (turning component definitions into their React node // representation) is a complicated process that requires information describing the // actual context. This interface defines the common properties of that context. export interface ComponentRendererContextBase<TMd extends ComponentMetadata = ComponentMetadata> { // The definition of the component to render node: ComponentDef<TMd>; // The state of the container in which the component is rendered state: ContainerState; // The application context the component (and its binding expressions) can use appContext?: AppContextObject; // The component can use this function to render its child components renderChild: RenderChildFn; // Information about the layout context in which the component is rendered layoutContext?: LayoutContext; } ``` -------------------------------------------------------------------------------- /xmlui/src/components/DatePicker/DatePicker.tsx: -------------------------------------------------------------------------------- ```typescript import styles from "./DatePicker.module.scss"; import { createComponentRenderer } from "../../components-core/renderers"; import { parseScssVar } from "../../components-core/theming/themeVars"; import { createMetadata, dAutoFocus, dDidChange, dEnabled, dEndIcon, dEndText, dGotFocus, dInitialValue, dLostFocus, dPlaceholder, dReadonly, dStartIcon, dStartText, dValidationStatus, } from "../metadata-helpers"; import { dateFormats, DatePicker, DatePickerModeValues, defaultProps, WeekDays, } from "./DatePickerNative"; const COMP = "DatePicker"; export const DatePickerMd = createMetadata({ status: "experimental", description: "`DatePicker` provides an interactive calendar interface for selecting single dates " + "or date ranges, with customizable formatting and validation options. It displays " + "a text input that opens a calendar popup when clicked, offering both keyboard and " + "mouse interaction.", props: { placeholder: dPlaceholder(), initialValue: dInitialValue(), autoFocus: dAutoFocus(), readOnly: dReadonly(), enabled: dEnabled(defaultProps.enabled), validationStatus: dValidationStatus(defaultProps.validationStatus), mode: { description: "The mode of the datepicker (single or range)", valueType: "string", availableValues: DatePickerModeValues, defaultValue: defaultProps.mode, }, dateFormat: { description: "The format of the date displayed in the input field", valueType: "string", defaultValue: defaultProps.dateFormat, availableValues: dateFormats, }, showWeekNumber: { description: "Whether to show the week number in the calendar", valueType: "boolean", defaultValue: defaultProps.showWeekNumber, }, weekStartsOn: { description: "The first day of the week. 0 is Sunday, 1 is Monday, etc.", valueType: "number", defaultValue: defaultProps.weekStartsOn, availableValues: [ { value: WeekDays.Sunday, description: "Sunday", }, { value: WeekDays.Monday, description: "Monday", }, { value: WeekDays.Tuesday, description: "Tuesday", }, { value: WeekDays.Wednesday, description: "Wednesday", }, { value: WeekDays.Thursday, description: "Thursday", }, { value: WeekDays.Friday, description: "Friday", }, { value: WeekDays.Saturday, description: "Saturday", }, ], }, startDate: { description: "The earliest month to start the month navigation from (inclusive). " + "If not defined, the component allows any dates in the past. " + "Accepts the same date format as the `initialValue`." + "Example: '2023-01-01' ensures the first month to select a date from is January 2023.", valueType: "string", }, endDate: { description: "The latest month to start the month navigation from (inclusive). " + "If not defined, the component allows any future dates. " + "Accepts the same date format as the `initialValue`." + "Example: '2023-12-31' ensures the last month to select a date from is December 2023.", valueType: "string", }, disabledDates: { description: "An optional array of dates that are to be disabled.", valueType: "any", }, inline: { description: "If set to true, the calendar is always visible and its panel is rendered as part of the layout." + " If false, the calendar is shown in a popup when the input is focused or clicked.", valueType: "boolean", defaultValue: defaultProps.inline, }, startText: dStartText(), startIcon: dStartIcon(), endText: dEndText(), endIcon: dEndIcon(), }, events: { didChange: dDidChange(COMP), gotFocus: dGotFocus(COMP), lostFocus: dLostFocus(COMP), }, apis: { focus: { description: `Focus the ${COMP} component.`, signature: "focus(): void", }, value: { description: `You can query the component's value. If no value is set, it will retrieve \`undefined\`.`, signature: "get value(): any", }, setValue: { description: `This method sets the current value of the ${COMP}.`, signature: "set value(value: any): void", parameters: { value: "The new value to set for the date picker.", }, }, }, themeVars: parseScssVar(styles.themeVars), defaultThemeVars: { [`boxShadow-menu-${COMP}`]: "$boxShadow-md", [`borderRadius-menu-${COMP}`]: "$borderRadius", [`textColor-value-${COMP}`]: "$textColor-primary", [`borderColor-selectedItem-${COMP}`]: "$color-primary-200", [`backgroundColor-menu-${COMP}`]: "$color-surface-50", [`backgroundColor-item-${COMP}--hover`]: "$color-surface-100", [`backgroundColor-item-${COMP}--active`]: "$color-surface-200", [`paddingVertical-${COMP}`]: "$space-2", [`paddingHorizontal-${COMP}`]: "$space-2", }, }); export const datePickerComponentRenderer = createComponentRenderer( COMP, DatePickerMd, ({ node, state, updateState, extractValue, className, lookupEventHandler, registerComponentApi, }) => { return ( <DatePicker className={className} mode={extractValue(node.props?.mode)} value={state?.value} initialValue={extractValue(node.props.initialValue)} enabled={extractValue.asOptionalBoolean(node.props.enabled)} placeholder={extractValue.asOptionalString(node.props.placeholder)} validationStatus={extractValue(node.props.validationStatus)} updateState={updateState} onDidChange={lookupEventHandler("didChange")} onFocus={lookupEventHandler("gotFocus")} onBlur={lookupEventHandler("lostFocus")} registerComponentApi={registerComponentApi} dateFormat={extractValue(node.props.dateFormat)} showWeekNumber={extractValue.asOptionalBoolean(node.props.showWeekNumber)} weekStartsOn={extractValue(node.props.weekStartsOn)} startDate={extractValue(node.props.startDate)} endDate={extractValue(node.props.endDate)} disabledDates={extractValue(node.props.disabledDates)} inline={extractValue.asOptionalBoolean(node.props.inline)} startText={extractValue.asOptionalString(node.props.startText)} startIcon={extractValue.asOptionalString(node.props.startIcon)} endText={extractValue.asOptionalString(node.props.endText)} endIcon={extractValue.asOptionalString(node.props.endIcon)} readOnly={extractValue.asOptionalBoolean(node.props.readOnly)} autoFocus={extractValue.asOptionalBoolean(node.props.autoFocus)} /> ); }, ); ``` -------------------------------------------------------------------------------- /xmlui/src/components/Card/Card.spec.ts: -------------------------------------------------------------------------------- ```typescript import { getBounds, SKIP_REASON } from "../../testing/component-test-helpers"; import { expect, test } from "../../testing/fixtures"; // ============================================================================= // BASIC FUNCTIONALITY TESTS // ============================================================================= test.describe("Basic Functionality", () => { test("Card renders", async ({ initTestBed, createCardDriver }) => { await initTestBed(`<Card />`); const driver = await createCardDriver(); await expect(driver.component).toBeVisible(); }); test("Card renders with title", async ({ initTestBed, createCardDriver }) => { await initTestBed(`<Card title="Test Title" />`); const title = (await createCardDriver()).component.getByRole("heading"); await expect(title).toBeVisible(); await expect(title).toHaveText("Test Title"); }); test("Card renders with subtitle", async ({ initTestBed, createCardDriver }) => { await initTestBed(`<Card subtitle="Test Subtitle" />`); const subtitle = (await createCardDriver()).component.locator("div").first(); await expect(subtitle).toBeVisible(); await expect(subtitle).toHaveText("Test Subtitle"); }); test("Card renders with both title and subtitle", async ({ initTestBed, createCardDriver }) => { await initTestBed(` <Card title="Test Title" subtitle="Test Subtitle"> <Text value="Card content" /> </Card> `); const driver = await createCardDriver(); const title = driver.component.getByRole("heading"); const subtitle = driver.component.getByText("Test Subtitle"); await expect(title).toHaveText("Test Title"); await expect(subtitle).toBeVisible(); }); test("displays avatar when avatarUrl is provided", async ({ initTestBed, createCardDriver }) => { await initTestBed(`<Card avatarUrl="/resources/flower-640x480.jpg" />`); const avatar = (await createCardDriver()).avatar; await expect(avatar).toBeVisible(); await expect(avatar).toHaveAttribute("src", "/resources/flower-640x480.jpg"); }); test("clicking linkTo title navigates", async ({ page, initTestBed, createCardDriver }) => { await initTestBed(`<Card title="Clickable Title" linkTo="/test-link" />`); const titleLink = (await createCardDriver()).component.getByRole("link", { name: "Clickable Title", }); await titleLink.click(); await expect(page).toHaveURL(/\/test-link$/); }); test("showAvatar=false hides avatar even when avatarUrl is provided", async ({ initTestBed, createCardDriver, }) => { await initTestBed(` <Card avatarUrl="https://i.pravatar.cc/100" showAvatar="false" title="Test Title" /> `); const driver = await createCardDriver(); await expect(driver.avatar).not.toBeVisible(); await expect(driver.component.getByText("Test Title")).toBeVisible(); }); test("showAvatar displays initials with single word title", async ({ initTestBed, createCardDriver, }) => { await initTestBed(`<Card showAvatar="true" title="John" />`); await expect( (await createCardDriver()).component.getByText("J", { exact: true }), ).toBeVisible(); }); test("showAvatar=true with empty title displays no initials", async ({ initTestBed, createCardDriver, }) => { await initTestBed(`<Card showAvatar="true" />`); const avatar = (await createCardDriver()).avatar; await expect(avatar).toHaveText(/^$/); }); test("linkTo without title does not create link", async ({ initTestBed, createCardDriver }) => { await initTestBed(` <Card linkTo="/test-link"> <Text value="Content" /> </Card> `); const driver = await createCardDriver(); await expect(driver.component.getByRole("link")).not.toBeVisible(); await expect(driver.component.getByText("Content")).toBeVisible(); }); test("orientation horizontal displays children in row", async ({ initTestBed, createTextDriver, }) => { await initTestBed(` <Card orientation="horizontal"> <Text testId="text-1" value="Child 1" /> <Text testId="text-2" value="Child 2" /> </Card> `); const text1Driver = await createTextDriver("text-1"); const text2Driver = await createTextDriver("text-2"); const { right: text1Right } = await getBounds(text1Driver); const { right: text2Left } = await getBounds(text2Driver); expect(text1Right).toBeLessThan(text2Left); }); test("orientation vertical displays children in column", async ({ initTestBed, createTextDriver, }) => { await initTestBed(` <Card orientation="vertical"> <Text testId="text-1" value="Child 1" /> <Text testId="text-2" value="Child 2" /> </Card> `); const text1Driver = await createTextDriver("text-1"); const text2Driver = await createTextDriver("text-2"); const { bottom: text1Bottom } = await getBounds(text1Driver); const { top: text2Top } = await getBounds(text2Driver); expect(text1Bottom).toBeLessThan(text2Top); }); }); // ============================================================================= // EVENT HANDLING TESTS // ============================================================================= test.describe("Event Handling", () => { test("click event is triggered when Card is clicked", async ({ initTestBed, createCardDriver, }) => { const { testStateDriver } = await initTestBed(`<Card onClick="testState = true" />`); await (await createCardDriver()).click(); await expect.poll(testStateDriver.testState).toEqual(true); }); test("Card click does not interfere with link click", async ({ page, initTestBed, createCardDriver, createTextDriver, }) => { await initTestBed(` <Card title="Title" linkTo="/test-link" onClick="testState = true"> <Text testId="text-1" when="{testState}" value="visible" /> </Card> `); const cardDriver = await createCardDriver(); const textDriver = await createTextDriver("text-1"); await cardDriver.click(); await expect(textDriver.component).toHaveText("visible"); await expect(page).not.toHaveURL(/\/test-link$/); }); test("Link click does not interfere with Card click", async ({ page, initTestBed, createCardDriver, createTextDriver, }) => { const { testStateDriver } = await initTestBed(` <Card title="Title" linkTo="/test-link" onClick="testState = true"> <Text testId="text-1" when="{testState}" value="visible" /> </Card> `); const textDriver = await createTextDriver("text-1"); const title = (await createCardDriver()).component.getByRole("heading"); await expect(textDriver.component).not.toBeVisible(); await title.click(); await page.waitForTimeout(200); await expect(page).toHaveURL(/\/test-link$/); await expect(textDriver.component).toBeVisible(); }); }); ``` -------------------------------------------------------------------------------- /packages/xmlui-website-blocks/src/HeroSection/HeroSection.tsx: -------------------------------------------------------------------------------- ```typescript import styles from "./HeroSection.module.scss"; import { createComponentRenderer, createMetadata, dComponent, d, parseScssVar } from "xmlui"; import { HeroSection, defaultProps } from "./HeroSectionNative"; const COMP = "HeroSection"; export const HeroSectionMd = createMetadata({ status: "experimental", description: "HeroSection", parts: { header: { description: "The header section containing all text content and CTA button", }, content: { description: "The content section containing image and children", }, headingSection: { description: "The heading section containing preamble, headline, and subheadline", }, preamble: { description: "The preamble text for the hero section", }, headline: { description: "The headline text for the hero section", }, subheadline: { description: "The subheadline text for the hero section", }, mainText: { description: "The main text content for the hero section", }, ctaButton: { description: "The call-to-action button for the hero section", }, image: { description: "The image for the hero section", }, background: { description: "The background template area of the hero section", }, }, props: { headerAlignment: { description: "Alignment of the header content", type: "string", defaultValue: "center", options: ["start", "center", "end"], }, contentPlacement: { description: "Position of the content area relative to the header", type: "string", defaultValue: defaultProps.contentPlacement, options: ["left", "right", "bottom"], }, contentAlignment: { description: "Horizontal alignment of the content within its area", type: "string", defaultValue: defaultProps.contentAlignment, options: ["start", "center", "end"], }, headerWidth: { description: "Width of the header section in horizontal layouts", type: "string", defaultValue: defaultProps.headerWidth, }, contentWidth: { description: "Width of the hero content (header + content sections)", type: "string", defaultValue: defaultProps.contentWidth, }, gap: { description: "Gap between header and content sections", type: "string", }, preamble: { description: "The preamble text for the hero section", type: "string", }, headline: { description: "The headline text for the hero section", type: "string", }, subheadline: { description: "The subheadline text for the hero section", type: "string", }, mainText: { description: "The main text content for the hero section", type: "string", }, mainTextTemplate: dComponent("The template for the text content in the hero section"), ctaButtonIcon: { description: "The icon for the call-to-action button", type: "string", }, ctaButtonText: { description: "The text for the call-to-action button", type: "string", }, ctaButtonTemplate: dComponent("The template for the call-to-action button"), fullWidthBackground: { description: "Whether the background should span the full width of the viewport", type: "boolean", defaultValue: defaultProps.fullWidthBackground, }, image: { description: "The image for the hero section", type: "string", }, imageWidth: { description: "The width of the image", type: "string", }, imageHeight: { description: "The height of the image", type: "string", }, backgroundTemplate: dComponent("The template for the background of the hero section"), headerTone: { description: "The tone for the header section, affecting text colors", type: "string", options: ["light", "dark", "reverse"], defaultValue: "dark", }, contentTone: { description: "The tone for the content section, affecting text colors", type: "string", options: ["light", "dark", "reverse"], defaultValue: "dark", }, className: d("Additional CSS class names to apply to the hero section", undefined, "string"), }, events: { ctaClick: d("Triggered when the call-to-action button is clicked"), }, themeVars: parseScssVar(styles.themeVars), defaultThemeVars: { [`paddingTop-${COMP}`]: "$space-12", [`paddingBottom-${COMP}`]: "$space-12", [`paddingHorizontal-${COMP}`]: "$space-12", [`fontSize-headline-${COMP}`]: "3em", [`fontWeight-headline-${COMP}`]: "$fontWeight-bold", [`lineHeight-headline-${COMP}`]: "1.4em", [`gap-headline-${COMP}`]: "$space-8", [`fontSize-subheadline-${COMP}`]: "2em", [`lineHeight-subheadline-${COMP}`]: "1.1em", [`fontWeight-subheadline-${COMP}`]: "$fontWeight-bold", [`gap-subheadline-${COMP}`]: "$space-4", [`fontSize-mainText-${COMP}`]: "1.4em", [`lineHeight-mainText-${COMP}`]: "1.1em", [`gap-mainText-${COMP}`]: "$space-4", [`textColor-preamble-${COMP}`]: "$textColor-primary", [`textColor-headline-${COMP}`]: "$textColor-primary", [`textColor-subheadline-${COMP}`]: "$textColor-primary", [`textColor-mainText-${COMP}`]: "$textColor-primary", }, }); export const heroSectionComponentRenderer = createComponentRenderer( COMP, HeroSectionMd, ({ node, extractValue, renderChild, lookupEventHandler, className }) => { const props = (node.props as typeof HeroSectionMd.props)!; return ( <HeroSection headerAlignment={extractValue(props.headerAlignment)} contentPlacement={extractValue(props.contentPlacement)} contentAlignment={extractValue(props.contentAlignment)} headerWidth={extractValue(props.headerWidth)} contentWidth={extractValue(props.contentWidth)} headerTone={extractValue(props.headerTone)} contentTone={extractValue(props.contentTone)} gap={extractValue(props.gap)} preamble={extractValue(props.preamble)} headline={extractValue(props.headline)} subheadline={extractValue(props.subheadline)} mainText={extractValue(props.mainText)} mainTextTemplate={renderChild(props.mainTextTemplate as any)} ctaButtonIcon={extractValue(props.ctaButtonIcon)} ctaButtonText={extractValue(props.ctaButtonText)} ctaButtonTemplate={renderChild(props.ctaButtonTemplate as any)} image={extractValue(props.image)} imageWidth={extractValue(props.imageWidth)} imageHeight={extractValue(props.imageHeight)} fullWidthBackground={extractValue.asOptionalBoolean(props.fullWidthBackground)} className={extractValue(className)} onCtaClick={lookupEventHandler("ctaClick")} backgroundTemplate={renderChild(props.backgroundTemplate as any)} > {renderChild(node.children)} </HeroSection> ); }, ); ``` -------------------------------------------------------------------------------- /xmlui/src/components/NestedApp/AppWithCodeViewNative.tsx: -------------------------------------------------------------------------------- ```typescript import { type ReactNode, useCallback, useRef, useState } from "react"; import { IndexAwareNestedApp } from "./NestedAppNative"; import { Markdown } from "../Markdown/Markdown"; import type { ThemeTone } from "../../abstractions/ThemingDefs"; import { Button } from "../Button/ButtonNative"; import styles from "./NestedApp.module.scss"; import { Tooltip } from "./Tooltip"; import { RxOpenInNewWindow } from "react-icons/rx"; import { LiaUndoAltSolid } from "react-icons/lia"; import { createQueryString, withoutTrailingSlash } from "./utils"; import { useAppContext } from "../../components-core/AppContext"; import classnames from "classnames"; import Logo from "./logo.svg?react"; import { useTheme } from "../../components-core/theming/ThemeContext"; type AppWithCodeViewNativeProps = { // Markdown content to display in the left column markdown: string; // Display layout in side-by-side mode (horizontal) when true, // or stacked (vertical) when false or undefined splitView?: boolean; // Indicates that the split view should initially show the code initiallyShowCode?: boolean; // Optional URL for the playground pop-out popOutUrl?: string; api?: any; app: string; components?: any[]; config?: any; activeTone?: ThemeTone; activeTheme?: string; title?: string; height?: string | number; allowPlaygroundPopup?: boolean; withFrame?: boolean; noHeader?: boolean; immediate?: boolean; withSplashScreen?: boolean; closeButton?: ReactNode; controlsWidth?: string | number; }; /** * A component that displays markdown content on the left and a NestedApp on the right */ export function AppWithCodeViewNative({ markdown, splitView, withFrame = true, noHeader = false, initiallyShowCode = false, popOutUrl, app, api, components = [], config, activeTone, activeTheme, title, height, allowPlaygroundPopup, withSplashScreen, immediate, closeButton = null, controlsWidth, }: AppWithCodeViewNativeProps): ReactNode { const [showCode, setShowCode] = useState(initiallyShowCode); const appContext = useAppContext(); const [refreshVersion, setRefreshVersion] = useState(0); const { activeTheme: currentTheme, activeThemeTone, activeThemeId } = useTheme(); const safePopOutUrl = withoutTrailingSlash( popOutUrl || appContext?.appGlobals?.popOutUrl || "https://playground.xmlui.org/#/playground", ); const openPlayground = useCallback(async () => { const data = { standalone: { app, components, config: { name: title, themes: [currentTheme], defaultTheme: activeTheme, }, api: api, }, options: { fixedTheme: false, swapped: false, previewMode: false, orientation: "horizontal", activeTheme: activeThemeId || activeTheme, activeTone: activeTone || activeThemeTone, content: "app", }, }; const appQueryString = await createQueryString(JSON.stringify(data)); window.open(`${safePopOutUrl}/#${appQueryString}`, "_blank"); }, [app, components, title, activeTheme, api, activeTone, safePopOutUrl]); if (withFrame) { return ( <> {!!markdown && !splitView && <Markdown>{markdown}</Markdown>} <div className={styles.nestedAppContainer} style={{ height }}> {!noHeader && ( <div className={styles.header}> {!splitView && <span className={styles.headerText}>{title}</span>} {splitView && ( <> <div className={styles.wrapper} style={{ width: controlsWidth }}> <Logo className={styles.logo} /> </div> <div className={styles.viewControls}> <Button onClick={() => setShowCode(true)} className={classnames(styles.splitViewButton, { [styles.show]: showCode, [styles.hide]: !showCode, })} > XML </Button> <Button onClick={() => setShowCode(false)} className={classnames(styles.splitViewButton, { [styles.show]: !showCode, [styles.hide]: showCode, })} > UI </Button> </div> </> )} <div className={styles.wrapper} style={{ width: controlsWidth }}> {allowPlaygroundPopup && ( <Tooltip trigger={ <button className={styles.headerButton} onClick={() => { void openPlayground(); }} > <RxOpenInNewWindow /> </button> } label="View and edit in new full-width window" /> )} <Tooltip trigger={ <button className={styles.headerButton} onClick={() => { setShowCode(false); setRefreshVersion(refreshVersion + 1); }} > <LiaUndoAltSolid /> </button> } label="Reset the app" /> {closeButton} </div> </div> )} <div className={styles.contentContainer}> <Markdown className={classnames(styles.splitViewMarkdown, { [styles.hidden]: !showCode })} > {markdown} </Markdown> <IndexAwareNestedApp className={classnames({ [styles.hidden]: showCode })} height="100%" app={app} api={api} components={components} config={config} activeTone={activeTone} activeTheme={activeTheme} refreshVersion={refreshVersion} withSplashScreen={withSplashScreen} immediate={immediate} /> </div> </div> </> ); } return ( <> {!!markdown && <Markdown>{markdown}</Markdown>} <IndexAwareNestedApp height={height} app={app} api={api} components={components} config={config} activeTone={activeTone} activeTheme={activeTheme} refreshVersion={refreshVersion} withSplashScreen={withSplashScreen} immediate={immediate} /> </> ); } /** * Export a named default for easier imports */ export default AppWithCodeViewNative; ```