This is page 10 of 181. Use http://codebase.md/xmlui-org/xmlui/mockApiDef.js?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .changeset │ ├── config.json │ └── stale-days-care.md ├── .eslintrc.cjs ├── .github │ ├── build-checklist.png │ ├── ISSUE_TEMPLATE │ │ ├── bug_report.md │ │ └── feature_request.md │ └── workflows │ ├── deploy-blog-optimized.yml │ ├── deploy-docs-optimized.yml │ ├── deploy-docs.yml │ ├── prepare-versions.yml │ ├── release-packages.yml │ ├── run-all-tests.yml │ └── run-smoke-tests.yml ├── .gitignore ├── .prettierrc.js ├── .vscode │ ├── launch.json │ └── settings.json ├── blog │ ├── .gitignore │ ├── .gitkeep │ ├── CHANGELOG.md │ ├── extensions.ts │ ├── index.html │ ├── index.ts │ ├── package.json │ ├── public │ │ ├── blog │ │ │ ├── images │ │ │ │ ├── blog-page-component.png │ │ │ │ ├── blog-scrabble.png │ │ │ │ ├── integrated-blog-search.png │ │ │ │ └── lorem-ipsum.png │ │ │ ├── lorem-ipsum.md │ │ │ ├── newest-post.md │ │ │ ├── older-post.md │ │ │ └── welcome-to-the-xmlui-blog.md │ │ ├── mockServiceWorker.js │ │ ├── resources │ │ │ ├── favicon.ico │ │ │ ├── files │ │ │ │ └── for-download │ │ │ │ └── xmlui │ │ │ │ └── xmlui-standalone.umd.js │ │ │ ├── github.svg │ │ │ ├── llms.txt │ │ │ ├── logo-dark.svg │ │ │ ├── logo.svg │ │ │ ├── pg-popout.svg │ │ │ └── xmlui-logo.svg │ │ ├── serve.json │ │ └── web.config │ ├── scripts │ │ ├── download-latest-xmlui.js │ │ ├── generate-rss.js │ │ ├── get-releases.js │ │ └── utils.js │ ├── src │ │ ├── components │ │ │ ├── BlogOverview.xmlui │ │ │ ├── BlogPage.xmlui │ │ │ └── PageNotFound.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 ├── CONTRIBUTING.md ├── docs │ ├── .gitignore │ ├── CHANGELOG.md │ ├── ComponentRefLinks.txt │ ├── content │ │ ├── _meta.json │ │ ├── components │ │ │ ├── _meta.json │ │ │ ├── _overview.md │ │ │ ├── APICall.md │ │ │ ├── App.md │ │ │ ├── AppHeader.md │ │ │ ├── AppState.md │ │ │ ├── AutoComplete.md │ │ │ ├── Avatar.md │ │ │ ├── Backdrop.md │ │ │ ├── Badge.md │ │ │ ├── BarChart.md │ │ │ ├── Bookmark.md │ │ │ ├── Breakout.md │ │ │ ├── Button.md │ │ │ ├── Card.md │ │ │ ├── Carousel.md │ │ │ ├── ChangeListener.md │ │ │ ├── Checkbox.md │ │ │ ├── CHStack.md │ │ │ ├── ColorPicker.md │ │ │ ├── Column.md │ │ │ ├── ContentSeparator.md │ │ │ ├── CVStack.md │ │ │ ├── DataSource.md │ │ │ ├── DateInput.md │ │ │ ├── DatePicker.md │ │ │ ├── DonutChart.md │ │ │ ├── DropdownMenu.md │ │ │ ├── EmojiSelector.md │ │ │ ├── ExpandableItem.md │ │ │ ├── FileInput.md │ │ │ ├── FileUploadDropZone.md │ │ │ ├── FlowLayout.md │ │ │ ├── Footer.md │ │ │ ├── Form.md │ │ │ ├── FormItem.md │ │ │ ├── FormSection.md │ │ │ ├── Fragment.md │ │ │ ├── H1.md │ │ │ ├── H2.md │ │ │ ├── H3.md │ │ │ ├── H4.md │ │ │ ├── H5.md │ │ │ ├── H6.md │ │ │ ├── Heading.md │ │ │ ├── HSplitter.md │ │ │ ├── HStack.md │ │ │ ├── Icon.md │ │ │ ├── IFrame.md │ │ │ ├── Image.md │ │ │ ├── Items.md │ │ │ ├── LabelList.md │ │ │ ├── Legend.md │ │ │ ├── LineChart.md │ │ │ ├── Link.md │ │ │ ├── List.md │ │ │ ├── Logo.md │ │ │ ├── Markdown.md │ │ │ ├── MenuItem.md │ │ │ ├── MenuSeparator.md │ │ │ ├── ModalDialog.md │ │ │ ├── NavGroup.md │ │ │ ├── NavLink.md │ │ │ ├── NavPanel.md │ │ │ ├── NoResult.md │ │ │ ├── NumberBox.md │ │ │ ├── Option.md │ │ │ ├── Page.md │ │ │ ├── PageMetaTitle.md │ │ │ ├── Pages.md │ │ │ ├── Pagination.md │ │ │ ├── PasswordInput.md │ │ │ ├── PieChart.md │ │ │ ├── ProgressBar.md │ │ │ ├── Queue.md │ │ │ ├── RadioGroup.md │ │ │ ├── RealTimeAdapter.md │ │ │ ├── Redirect.md │ │ │ ├── Select.md │ │ │ ├── Slider.md │ │ │ ├── Slot.md │ │ │ ├── SpaceFiller.md │ │ │ ├── Spinner.md │ │ │ ├── Splitter.md │ │ │ ├── Stack.md │ │ │ ├── StickyBox.md │ │ │ ├── SubMenuItem.md │ │ │ ├── Switch.md │ │ │ ├── TabItem.md │ │ │ ├── Table.md │ │ │ ├── TableOfContents.md │ │ │ ├── Tabs.md │ │ │ ├── Text.md │ │ │ ├── TextArea.md │ │ │ ├── TextBox.md │ │ │ ├── Theme.md │ │ │ ├── TimeInput.md │ │ │ ├── Timer.md │ │ │ ├── ToneChangerButton.md │ │ │ ├── ToneSwitch.md │ │ │ ├── Tooltip.md │ │ │ ├── Tree.md │ │ │ ├── VSplitter.md │ │ │ ├── VStack.md │ │ │ ├── xmlui-animations │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ ├── Animation.md │ │ │ │ ├── FadeAnimation.md │ │ │ │ ├── FadeInAnimation.md │ │ │ │ ├── FadeOutAnimation.md │ │ │ │ ├── ScaleAnimation.md │ │ │ │ └── SlideInAnimation.md │ │ │ ├── xmlui-pdf │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ └── Pdf.md │ │ │ ├── xmlui-spreadsheet │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ └── Spreadsheet.md │ │ │ └── xmlui-website-blocks │ │ │ ├── _meta.json │ │ │ ├── _overview.md │ │ │ ├── Carousel.md │ │ │ ├── HelloMd.md │ │ │ ├── HeroSection.md │ │ │ └── ScrollToTop.md │ │ └── extensions │ │ ├── _meta.json │ │ ├── xmlui-animations │ │ │ ├── _meta.json │ │ │ ├── _overview.md │ │ │ ├── Animation.md │ │ │ ├── FadeAnimation.md │ │ │ ├── FadeInAnimation.md │ │ │ ├── FadeOutAnimation.md │ │ │ ├── ScaleAnimation.md │ │ │ └── SlideInAnimation.md │ │ └── xmlui-website-blocks │ │ ├── _meta.json │ │ ├── _overview.md │ │ ├── Carousel.md │ │ ├── HelloMd.md │ │ ├── HeroSection.md │ │ └── ScrollToTop.md │ ├── extensions.ts │ ├── index.html │ ├── index.ts │ ├── package.json │ ├── public │ │ ├── feed.rss │ │ ├── mockServiceWorker.js │ │ ├── pages │ │ │ ├── _meta.json │ │ │ ├── app-structure.md │ │ │ ├── build-editor-component.md │ │ │ ├── build-hello-world-component.md │ │ │ ├── components-intro.md │ │ │ ├── context-variables.md │ │ │ ├── forms.md │ │ │ ├── globals.md │ │ │ ├── glossary.md │ │ │ ├── helper-tags.md │ │ │ ├── hosted-deployment.md │ │ │ ├── howto │ │ │ │ ├── assign-a-complex-json-literal-to-a-component-variable.md │ │ │ │ ├── chain-a-refetch.md │ │ │ │ ├── debug-a-component.md │ │ │ │ ├── delay-a-datasource-until-another-datasource-is-ready.md │ │ │ │ ├── delegate-a-method.md │ │ │ │ ├── do-custom-form-validation.md │ │ │ │ ├── expose-a-method-from-a-component.md │ │ │ │ ├── filter-and-transform-data-from-an-api.md │ │ │ │ ├── group-items-in-list-by-a-property.md │ │ │ │ ├── handle-background-operations.md │ │ │ │ ├── hide-an-element-until-its-datasource-is-ready.md │ │ │ │ ├── make-a-set-of-equal-width-cards.md │ │ │ │ ├── make-a-table-responsive.md │ │ │ │ ├── modify-a-value-reported-in-a-column.md │ │ │ │ ├── paginate-a-list.md │ │ │ │ ├── pass-data-to-a-modal-dialog.md │ │ │ │ ├── react-to-button-click-not-keystrokes.md │ │ │ │ ├── set-the-initial-value-of-a-select-from-fetched-data.md │ │ │ │ ├── share-a-modaldialog-across-components.md │ │ │ │ ├── sync-selections-between-table-and-list-views.md │ │ │ │ ├── update-ui-optimistically.md │ │ │ │ ├── use-built-in-form-validation.md │ │ │ │ └── use-the-same-modaldialog-to-add-or-edit.md │ │ │ ├── howto.md │ │ │ ├── intro.md │ │ │ ├── layout.md │ │ │ ├── markup.md │ │ │ ├── mcp.md │ │ │ ├── modal-dialogs.md │ │ │ ├── news-and-reviews.md │ │ │ ├── reactive-intro.md │ │ │ ├── refactoring.md │ │ │ ├── routing-and-links.md │ │ │ ├── samples │ │ │ │ ├── color-palette.xmlui │ │ │ │ ├── color-values.xmlui │ │ │ │ ├── shadow-sizes.xmlui │ │ │ │ ├── spacing-sizes.xmlui │ │ │ │ ├── swatch.xmlui │ │ │ │ ├── theme-gallery-brief.xmlui │ │ │ │ └── theme-gallery.xmlui │ │ │ ├── scoping.md │ │ │ ├── scripting.md │ │ │ ├── styles-and-themes │ │ │ │ ├── common-units.md │ │ │ │ ├── layout-props.md │ │ │ │ ├── theme-variable-defaults.md │ │ │ │ ├── theme-variables.md │ │ │ │ └── themes.md │ │ │ ├── template-properties.md │ │ │ ├── test.md │ │ │ ├── tutorial-01.md │ │ │ ├── tutorial-02.md │ │ │ ├── tutorial-03.md │ │ │ ├── tutorial-04.md │ │ │ ├── tutorial-05.md │ │ │ ├── tutorial-06.md │ │ │ ├── tutorial-07.md │ │ │ ├── tutorial-08.md │ │ │ ├── tutorial-09.md │ │ │ ├── tutorial-10.md │ │ │ ├── tutorial-11.md │ │ │ ├── tutorial-12.md │ │ │ ├── universal-properties.md │ │ │ ├── user-defined-components.md │ │ │ ├── vscode.md │ │ │ ├── working-with-markdown.md │ │ │ ├── working-with-text.md │ │ │ ├── xmlui-animations │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ ├── Animation.md │ │ │ │ ├── FadeAnimation.md │ │ │ │ ├── FadeInAnimation.md │ │ │ │ ├── FadeOutAnimation.md │ │ │ │ ├── ScaleAnimation.md │ │ │ │ └── SlideInAnimation.md │ │ │ ├── xmlui-charts │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ ├── BarChart.md │ │ │ │ ├── DonutChart.md │ │ │ │ ├── LabelList.md │ │ │ │ ├── Legend.md │ │ │ │ ├── LineChart.md │ │ │ │ └── PieChart.md │ │ │ ├── xmlui-pdf │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ └── Pdf.md │ │ │ └── xmlui-spreadsheet │ │ │ ├── _meta.json │ │ │ ├── _overview.md │ │ │ └── Spreadsheet.md │ │ ├── resources │ │ │ ├── devdocs │ │ │ │ ├── debug-proxy-object-2.png │ │ │ │ ├── debug-proxy-object.png │ │ │ │ ├── table_editor_01.png │ │ │ │ ├── table_editor_02.png │ │ │ │ ├── table_editor_03.png │ │ │ │ ├── table_editor_04.png │ │ │ │ ├── table_editor_05.png │ │ │ │ ├── table_editor_06.png │ │ │ │ ├── table_editor_07.png │ │ │ │ ├── table_editor_08.png │ │ │ │ ├── table_editor_09.png │ │ │ │ ├── table_editor_10.png │ │ │ │ ├── table_editor_11.png │ │ │ │ ├── table-editor-01.png │ │ │ │ ├── table-editor-02.png │ │ │ │ ├── table-editor-03.png │ │ │ │ ├── table-editor-04.png │ │ │ │ ├── table-editor-06.png │ │ │ │ ├── table-editor-07.png │ │ │ │ ├── table-editor-08.png │ │ │ │ ├── table-editor-09.png │ │ │ │ └── xmlui-rendering-of-tiptap-markdown.png │ │ │ ├── favicon.ico │ │ │ ├── files │ │ │ │ ├── clients.json │ │ │ │ ├── daily-revenue.json │ │ │ │ ├── dashboard-stats.json │ │ │ │ ├── demo.xmlui │ │ │ │ ├── demo.xmlui.xs │ │ │ │ ├── downloads │ │ │ │ │ └── downloads.json │ │ │ │ ├── for-download │ │ │ │ │ ├── index-with-api.html │ │ │ │ │ ├── index.html │ │ │ │ │ ├── mockApi.js │ │ │ │ │ ├── start-darwin.sh │ │ │ │ │ ├── start-linux.sh │ │ │ │ │ ├── start.bat │ │ │ │ │ └── xmlui │ │ │ │ │ └── xmlui-standalone.umd.js │ │ │ │ ├── getting-started │ │ │ │ │ ├── cl-tutorial-final.zip │ │ │ │ │ ├── cl-tutorial.zip │ │ │ │ │ ├── cl-tutorial2.zip │ │ │ │ │ ├── cl-tutorial3.zip │ │ │ │ │ ├── cl-tutorial4.zip │ │ │ │ │ ├── cl-tutorial5.zip │ │ │ │ │ ├── cl-tutorial6.zip │ │ │ │ │ ├── getting-started.zip │ │ │ │ │ ├── hello-xmlui.zip │ │ │ │ │ ├── xmlui-empty.zip │ │ │ │ │ └── xmlui-starter.zip │ │ │ │ ├── howto │ │ │ │ │ └── component-icons │ │ │ │ │ └── up-arrow.svg │ │ │ │ ├── invoices.json │ │ │ │ ├── monthly-status.json │ │ │ │ ├── news-and-reviews.json │ │ │ │ ├── products.json │ │ │ │ ├── releases.json │ │ │ │ ├── tutorials │ │ │ │ │ ├── datasource │ │ │ │ │ │ └── api.ts │ │ │ │ │ └── p2do │ │ │ │ │ ├── api.ts │ │ │ │ │ └── todo-logo.svg │ │ │ │ └── xmlui.json │ │ │ ├── github.svg │ │ │ ├── images │ │ │ │ ├── apiaction-tutorial │ │ │ │ │ ├── add-success.png │ │ │ │ │ ├── apiaction-param.png │ │ │ │ │ ├── change-completed.png │ │ │ │ │ ├── change-in-progress.png │ │ │ │ │ ├── confirm-delete.png │ │ │ │ │ ├── data-error.png │ │ │ │ │ ├── data-progress.png │ │ │ │ │ ├── data-success.png │ │ │ │ │ ├── display-1.png │ │ │ │ │ ├── item-deleted.png │ │ │ │ │ ├── item-updated.png │ │ │ │ │ ├── missing-api-key.png │ │ │ │ │ ├── new-item-added.png │ │ │ │ │ └── test-message.png │ │ │ │ ├── chat-api │ │ │ │ │ └── domain-model.svg │ │ │ │ ├── components │ │ │ │ │ ├── image │ │ │ │ │ │ └── breakfast.jpg │ │ │ │ │ ├── markdown │ │ │ │ │ │ └── colors.png │ │ │ │ │ └── modal │ │ │ │ │ ├── deep_link_dialog_1.jpg │ │ │ │ │ └── deep_link_dialog_2.jpg │ │ │ │ ├── create-apps │ │ │ │ │ ├── collapsed-vertical.png │ │ │ │ │ ├── using-forms-warning-dialog.png │ │ │ │ │ └── using-forms.png │ │ │ │ ├── datasource-tutorial │ │ │ │ │ ├── data-with-header.png │ │ │ │ │ ├── filtered-data.png │ │ │ │ │ ├── filtered-items.png │ │ │ │ │ ├── initial-page-items.png │ │ │ │ │ ├── list-items.png │ │ │ │ │ ├── next-page-items.png │ │ │ │ │ ├── no-data.png │ │ │ │ │ ├── pagination-1.jpg │ │ │ │ │ ├── pagination-1.png │ │ │ │ │ ├── polling-1.png │ │ │ │ │ ├── refetch-data.png │ │ │ │ │ ├── slow-loading.png │ │ │ │ │ ├── test-message.png │ │ │ │ │ ├── Thumbs.db │ │ │ │ │ ├── unconventional-data.png │ │ │ │ │ └── unfiltered-items.png │ │ │ │ ├── flower.jpg │ │ │ │ ├── get-started │ │ │ │ │ ├── add-new-contact.png │ │ │ │ │ ├── app-modified.png │ │ │ │ │ ├── app-start.png │ │ │ │ │ ├── app-with-boxes.png │ │ │ │ │ ├── app-with-toast.png │ │ │ │ │ ├── boilerplate-structure.png │ │ │ │ │ ├── cl-initial.png │ │ │ │ │ ├── cl-start.png │ │ │ │ │ ├── contact-counts.png │ │ │ │ │ ├── contact-dialog-title.png │ │ │ │ │ ├── contact-dialog.png │ │ │ │ │ ├── contact-menus.png │ │ │ │ │ ├── contact-predicates.png │ │ │ │ │ ├── context-menu.png │ │ │ │ │ ├── dashboard-numbers.png │ │ │ │ │ ├── default-contact-list.png │ │ │ │ │ ├── delete-contact.png │ │ │ │ │ ├── delete-task.png │ │ │ │ │ ├── detailed-template.png │ │ │ │ │ ├── edit-contact-details.png │ │ │ │ │ ├── edited-contact-saved.png │ │ │ │ │ ├── empty-sections.png │ │ │ │ │ ├── filter-completed.png │ │ │ │ │ ├── fullwidth-desktop.png │ │ │ │ │ ├── fullwidth-mobile.png │ │ │ │ │ ├── initial-table.png │ │ │ │ │ ├── items-and-badges.png │ │ │ │ │ ├── loading-message.png │ │ │ │ │ ├── new-contact-button.png │ │ │ │ │ ├── new-contact-saved.png │ │ │ │ │ ├── no-empty-sections.png │ │ │ │ │ ├── personal-todo-initial.png │ │ │ │ │ ├── piechart.png │ │ │ │ │ ├── review-today.png │ │ │ │ │ ├── rudimentary-dashboard.png │ │ │ │ │ ├── section-collapsed.png │ │ │ │ │ ├── sectioned-items.png │ │ │ │ │ ├── sections-ordered.png │ │ │ │ │ ├── spacex-list-with-links.png │ │ │ │ │ ├── spacex-list.png │ │ │ │ │ ├── start-personal-todo-1.png │ │ │ │ │ ├── submit-new-contact.png │ │ │ │ │ ├── submit-new-task.png │ │ │ │ │ ├── syntax-highlighting.png │ │ │ │ │ ├── table-with-badge.png │ │ │ │ │ ├── template-with-card.png │ │ │ │ │ ├── test-emulated-api.png │ │ │ │ │ ├── Thumbs.db │ │ │ │ │ ├── todo-logo.png │ │ │ │ │ └── xmlui-tools.png │ │ │ │ ├── HelloApp.png │ │ │ │ ├── HelloApp2.png │ │ │ │ ├── logos │ │ │ │ │ ├── xmlui1.svg │ │ │ │ │ ├── xmlui2.svg │ │ │ │ │ ├── xmlui3.svg │ │ │ │ │ ├── xmlui4.svg │ │ │ │ │ ├── xmlui5.svg │ │ │ │ │ ├── xmlui6.svg │ │ │ │ │ └── xmlui7.svg │ │ │ │ ├── pdf │ │ │ │ │ └── dummy-pdf.jpg │ │ │ │ ├── rendering-engine │ │ │ │ │ ├── AppEngine-flow.svg │ │ │ │ │ ├── Component.svg │ │ │ │ │ ├── CompoundComponent.svg │ │ │ │ │ ├── RootComponent.svg │ │ │ │ │ └── tree-with-containers.svg │ │ │ │ ├── reviewers-guide │ │ │ │ │ ├── AppEngine-flow.svg │ │ │ │ │ └── incbutton-in-action.png │ │ │ │ ├── tools │ │ │ │ │ └── boilerplate-structure.png │ │ │ │ ├── try.svg │ │ │ │ ├── tutorial │ │ │ │ │ ├── app-chat-history.png │ │ │ │ │ ├── app-content-placeholder.png │ │ │ │ │ ├── app-header-and-content.png │ │ │ │ │ ├── app-links-channel-selected.png │ │ │ │ │ ├── app-links-click.png │ │ │ │ │ ├── app-navigation.png │ │ │ │ │ ├── finished-ex01.png │ │ │ │ │ ├── finished-ex02.png │ │ │ │ │ ├── hello.png │ │ │ │ │ ├── splash-screen-advanced.png │ │ │ │ │ ├── splash-screen-after-click.png │ │ │ │ │ ├── splash-screen-centered.png │ │ │ │ │ ├── splash-screen-events.png │ │ │ │ │ ├── splash-screen-expression.png │ │ │ │ │ ├── splash-screen-reuse-after.png │ │ │ │ │ ├── splash-screen-reuse-before.png │ │ │ │ │ └── splash-screen.png │ │ │ │ └── tutorial-01.png │ │ │ ├── llms.txt │ │ │ ├── logo-dark.svg │ │ │ ├── logo.svg │ │ │ ├── pg-popout.svg │ │ │ └── xmlui-logo.svg │ │ ├── serve.json │ │ └── web.config │ ├── scripts │ │ ├── download-latest-xmlui.js │ │ ├── generate-rss.js │ │ ├── get-releases.js │ │ └── utils.js │ ├── src │ │ ├── components │ │ │ ├── BlogOverview.xmlui │ │ │ ├── BlogPage.xmlui │ │ │ ├── Boxes.xmlui │ │ │ ├── Breadcrumb.xmlui │ │ │ ├── ChangeLog.xmlui │ │ │ ├── ColorPalette.xmlui │ │ │ ├── DocumentLinks.xmlui │ │ │ ├── DocumentPage.xmlui │ │ │ ├── DocumentPageNoTOC.xmlui │ │ │ ├── Icons.xmlui │ │ │ ├── IncButton.xmlui │ │ │ ├── IncButton2.xmlui │ │ │ ├── NameValue.xmlui │ │ │ ├── PageNotFound.xmlui │ │ │ ├── PaletteItem.xmlui │ │ │ ├── Palettes.xmlui │ │ │ ├── SectionHeader.xmlui │ │ │ ├── TBD.xmlui │ │ │ ├── Test.xmlui │ │ │ ├── ThemesIntro.xmlui │ │ │ ├── ThousandThemes.xmlui │ │ │ ├── TubeStops.xmlui │ │ │ ├── TubeStops.xmlui.xs │ │ │ └── TwoColumnCode.xmlui │ │ ├── config.ts │ │ ├── Main.xmlui │ │ └── themes │ │ ├── docs-theme.ts │ │ ├── earthtone.ts │ │ ├── xmlui-gray-on-default.ts │ │ ├── xmlui-green-on-default.ts │ │ └── xmlui-orange-on-default.ts │ └── tsconfig.json ├── LICENSE ├── package-lock.json ├── package.json ├── packages │ ├── xmlui-animations │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── Animation.tsx │ │ │ ├── AnimationNative.tsx │ │ │ ├── FadeAnimation.tsx │ │ │ ├── FadeInAnimation.tsx │ │ │ ├── FadeOutAnimation.tsx │ │ │ ├── index.tsx │ │ │ ├── ScaleAnimation.tsx │ │ │ └── SlideInAnimation.tsx │ │ └── tsconfig.json │ ├── xmlui-devtools │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── devtools │ │ │ │ ├── DevTools.tsx │ │ │ │ ├── DevToolsNative.module.scss │ │ │ │ ├── DevToolsNative.tsx │ │ │ │ ├── ModalDialog.module.scss │ │ │ │ ├── ModalDialog.tsx │ │ │ │ ├── ModalVisibilityContext.tsx │ │ │ │ ├── Tooltip.module.scss │ │ │ │ ├── Tooltip.tsx │ │ │ │ └── utils.ts │ │ │ ├── editor │ │ │ │ └── Editor.tsx │ │ │ └── index.tsx │ │ ├── tsconfig.json │ │ └── vite.config-overrides.ts │ ├── xmlui-hello-world │ │ ├── .gitignore │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── HelloWorld.module.scss │ │ │ ├── HelloWorld.tsx │ │ │ ├── HelloWorldNative.tsx │ │ │ └── index.tsx │ │ └── tsconfig.json │ ├── xmlui-os-frames │ │ ├── .gitignore │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── IPhoneFrame.module.scss │ │ │ ├── IPhoneFrame.tsx │ │ │ ├── MacOSAppFrame.module.scss │ │ │ ├── MacOSAppFrame.tsx │ │ │ ├── WindowsAppFrame.module.scss │ │ │ └── WindowsAppFrame.tsx │ │ └── tsconfig.json │ ├── xmlui-pdf │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ ├── components │ │ │ │ └── Pdf.xmlui │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── LazyPdfNative.tsx │ │ │ ├── Pdf.module.scss │ │ │ └── Pdf.tsx │ │ └── tsconfig.json │ ├── xmlui-playground │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── hooks │ │ │ │ ├── usePlayground.ts │ │ │ │ └── useToast.ts │ │ │ ├── index.tsx │ │ │ ├── playground │ │ │ │ ├── Box.module.scss │ │ │ │ ├── Box.tsx │ │ │ │ ├── CodeSelector.tsx │ │ │ │ ├── ConfirmationDialog.module.scss │ │ │ │ ├── ConfirmationDialog.tsx │ │ │ │ ├── Editor.tsx │ │ │ │ ├── Header.module.scss │ │ │ │ ├── Header.tsx │ │ │ │ ├── Playground.tsx │ │ │ │ ├── PlaygroundContent.module.scss │ │ │ │ ├── PlaygroundContent.tsx │ │ │ │ ├── PlaygroundNative.module.scss │ │ │ │ ├── PlaygroundNative.tsx │ │ │ │ ├── Preview.module.scss │ │ │ │ ├── Preview.tsx │ │ │ │ ├── Select.module.scss │ │ │ │ ├── StandalonePlayground.tsx │ │ │ │ ├── StandalonePlaygroundNative.module.scss │ │ │ │ ├── StandalonePlaygroundNative.tsx │ │ │ │ ├── ThemeSwitcher.module.scss │ │ │ │ ├── ThemeSwitcher.tsx │ │ │ │ ├── ToneSwitcher.tsx │ │ │ │ ├── Tooltip.module.scss │ │ │ │ ├── Tooltip.tsx │ │ │ │ └── utils.ts │ │ │ ├── providers │ │ │ │ ├── Toast.module.scss │ │ │ │ └── ToastProvider.tsx │ │ │ ├── state │ │ │ │ └── store.ts │ │ │ ├── themes │ │ │ │ └── theme.ts │ │ │ └── utils │ │ │ └── helpers.ts │ │ └── tsconfig.json │ ├── xmlui-search │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── Search.module.scss │ │ │ └── Search.tsx │ │ └── tsconfig.json │ ├── xmlui-spreadsheet │ │ ├── .gitignore │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── Spreadsheet.tsx │ │ │ └── SpreadsheetNative.tsx │ │ └── tsconfig.json │ └── xmlui-website-blocks │ ├── .gitignore │ ├── CHANGELOG.md │ ├── demo │ │ ├── components │ │ │ ├── HeroBackgroundBreakoutPage.xmlui │ │ │ ├── HeroBackgroundsPage.xmlui │ │ │ ├── HeroContentsPage.xmlui │ │ │ ├── HeroTextAlignPage.xmlui │ │ │ ├── HeroTextPage.xmlui │ │ │ └── HeroTonesPage.xmlui │ │ ├── Main.xmlui │ │ └── themes │ │ └── default.ts │ ├── index.html │ ├── index.ts │ ├── meta │ │ └── componentsMetadata.ts │ ├── package.json │ ├── public │ │ └── resources │ │ ├── building.jpg │ │ └── xmlui-logo.svg │ ├── src │ │ ├── Carousel │ │ │ ├── Carousel.module.scss │ │ │ ├── Carousel.tsx │ │ │ ├── CarouselContext.tsx │ │ │ └── CarouselNative.tsx │ │ ├── FancyButton │ │ │ ├── FancyButton.module.scss │ │ │ ├── FancyButton.tsx │ │ │ └── FancyButton.xmlui │ │ ├── Hello │ │ │ ├── Hello.tsx │ │ │ ├── Hello.xmlui │ │ │ └── Hello.xmlui.xs │ │ ├── HeroSection │ │ │ ├── HeroSection.module.scss │ │ │ ├── HeroSection.tsx │ │ │ └── HeroSectionNative.tsx │ │ ├── index.tsx │ │ ├── ScrollToTop │ │ │ ├── ScrollToTop.module.scss │ │ │ ├── ScrollToTop.tsx │ │ │ └── ScrollToTopNative.tsx │ │ └── vite-env.d.ts │ └── tsconfig.json ├── README.md ├── tools │ ├── codefence │ │ └── xmlui-code-fence-docs.md │ ├── create-app │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── create-app.ts │ │ ├── helpers │ │ │ ├── copy.ts │ │ │ ├── get-pkg-manager.ts │ │ │ ├── git.ts │ │ │ ├── install.ts │ │ │ ├── is-folder-empty.ts │ │ │ ├── is-writeable.ts │ │ │ ├── make-dir.ts │ │ │ └── validate-pkg.ts │ │ ├── index.ts │ │ ├── package.json │ │ ├── templates │ │ │ ├── default │ │ │ │ └── ts │ │ │ │ ├── gitignore │ │ │ │ ├── index.html │ │ │ │ ├── index.ts │ │ │ │ ├── public │ │ │ │ │ ├── mockServiceWorker.js │ │ │ │ │ ├── resources │ │ │ │ │ │ ├── favicon.ico │ │ │ │ │ │ └── xmlui-logo.svg │ │ │ │ │ └── serve.json │ │ │ │ └── src │ │ │ │ ├── components │ │ │ │ │ ├── ApiAware.xmlui │ │ │ │ │ ├── Home.xmlui │ │ │ │ │ ├── IncButton.xmlui │ │ │ │ │ └── PagePanel.xmlui │ │ │ │ ├── config.ts │ │ │ │ └── Main.xmlui │ │ │ ├── index.ts │ │ │ └── types.ts │ │ └── tsconfig.json │ ├── create-xmlui-hello-world │ │ ├── index.js │ │ └── package.json │ └── vscode │ ├── .gitignore │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ ├── .vscodeignore │ ├── build.sh │ ├── CHANGELOG.md │ ├── esbuild.js │ ├── eslint.config.mjs │ ├── formatter-docs.md │ ├── generate-test-sample.sh │ ├── LICENSE.md │ ├── package-lock.json │ ├── package.json │ ├── README.md │ ├── resources │ │ ├── xmlui-logo.png │ │ └── xmlui-markup-syntax-highlighting.png │ ├── src │ │ ├── extension.ts │ │ └── server.ts │ ├── syntaxes │ │ └── xmlui.tmLanguage.json │ ├── test-samples │ │ └── sample.xmlui │ ├── tsconfig.json │ └── tsconfig.tsbuildinfo ├── turbo.json └── xmlui ├── .gitignore ├── bin │ ├── bootstrap.js │ ├── build-lib.ts │ ├── build.ts │ ├── index.ts │ ├── preview.ts │ ├── start.ts │ ├── vite-xmlui-plugin.ts │ └── viteConfig.ts ├── CHANGELOG.md ├── conventions │ ├── component-qa-checklist.md │ ├── copilot-conventions.md │ ├── create-xmlui-components.md │ ├── mermaid.md │ ├── testing-conventions.md │ └── xmlui-in-a-nutshell.md ├── dev-docs │ ├── accessibility.md │ ├── build-system.md │ ├── build-xmlui.md │ ├── containers.md │ ├── glossary.md │ ├── index.md │ ├── next │ │ ├── component-dev-guide.md │ │ ├── configuration-management-enhancement-summary.md │ │ ├── documentation-scripts-refactoring-complete-summary.md │ │ ├── documentation-scripts-refactoring-plan.md │ │ ├── duplicate-pattern-extraction-summary.md │ │ ├── error-handling-standardization-summary.md │ │ ├── generating-component-reference.md │ │ ├── index.md │ │ ├── logging-consistency-implementation-summary.md │ │ ├── project-build.md │ │ ├── project-structure.md │ │ ├── theme-context.md │ │ ├── tiptap-design-considerations.md │ │ ├── working-with-code.md │ │ ├── xmlui-runtime-architecture │ │ └── xmlui-wcag-accessibility-report.md │ ├── react-fundamentals.md │ ├── release-method.md │ ├── standalone-app.md │ ├── state-management.md │ └── xmlui-repo.md ├── package.json ├── playwright.config.ts ├── scripts │ ├── coverage-only.js │ ├── e2e-test-summary.js │ ├── generate-docs │ │ ├── build-downloads-map.mjs │ │ ├── build-pages-map.mjs │ │ ├── components-config.json │ │ ├── configuration-management.mjs │ │ ├── constants.mjs │ │ ├── create-theme-files.mjs │ │ ├── DocsGenerator.mjs │ │ ├── error-handling.mjs │ │ ├── extensions-config.json │ │ ├── folders.mjs │ │ ├── generate-summary-files.mjs │ │ ├── get-docs.mjs │ │ ├── input-handler.mjs │ │ ├── logger.mjs │ │ ├── logging-standards.mjs │ │ ├── MetadataProcessor.mjs │ │ ├── pattern-utilities.mjs │ │ └── utils.mjs │ ├── get-langserver-metadata.mjs │ ├── inline-links.mjs │ └── README-e2e-summary.md ├── src │ ├── abstractions │ │ ├── _conventions.md │ │ ├── ActionDefs.ts │ │ ├── AppContextDefs.ts │ │ ├── ComponentDefs.ts │ │ ├── ContainerDefs.ts │ │ ├── ExtensionDefs.ts │ │ ├── FunctionDefs.ts │ │ ├── RendererDefs.ts │ │ ├── scripting │ │ │ ├── BlockScope.ts │ │ │ ├── Compilation.ts │ │ │ ├── LogicalThread.ts │ │ │ ├── LoopScope.ts │ │ │ ├── modules.ts │ │ │ ├── ScriptParserError.ts │ │ │ ├── Token.ts │ │ │ ├── TryScope.ts │ │ │ └── TryScopeExp.ts │ │ └── ThemingDefs.ts │ ├── components │ │ ├── _conventions.md │ │ ├── abstractions.ts │ │ ├── Accordion │ │ │ ├── Accordion.md │ │ │ ├── Accordion.module.scss │ │ │ ├── Accordion.spec.ts │ │ │ ├── Accordion.tsx │ │ │ ├── AccordionContext.tsx │ │ │ ├── AccordionItem.tsx │ │ │ ├── AccordionItemNative.tsx │ │ │ └── AccordionNative.tsx │ │ ├── Animation │ │ │ └── AnimationNative.tsx │ │ ├── APICall │ │ │ ├── APICall.md │ │ │ ├── APICall.spec.ts │ │ │ ├── APICall.tsx │ │ │ └── APICallNative.tsx │ │ ├── App │ │ │ ├── App.md │ │ │ ├── App.module.scss │ │ │ ├── App.spec.ts │ │ │ ├── App.tsx │ │ │ ├── AppLayoutContext.ts │ │ │ ├── AppNative.tsx │ │ │ ├── AppStateContext.ts │ │ │ ├── doc-resources │ │ │ │ ├── condensed-sticky.xmlui │ │ │ │ ├── condensed.xmlui │ │ │ │ ├── horizontal-sticky.xmlui │ │ │ │ ├── horizontal.xmlui │ │ │ │ ├── vertical-full-header.xmlui │ │ │ │ ├── vertical-sticky.xmlui │ │ │ │ └── vertical.xmlui │ │ │ ├── IndexerContext.ts │ │ │ ├── LinkInfoContext.ts │ │ │ ├── SearchContext.tsx │ │ │ ├── Sheet.module.scss │ │ │ └── Sheet.tsx │ │ ├── AppHeader │ │ │ ├── AppHeader.md │ │ │ ├── AppHeader.module.scss │ │ │ ├── AppHeader.spec.ts │ │ │ ├── AppHeader.tsx │ │ │ └── AppHeaderNative.tsx │ │ ├── AppState │ │ │ ├── AppState.md │ │ │ ├── AppState.spec.ts │ │ │ ├── AppState.tsx │ │ │ └── AppStateNative.tsx │ │ ├── AutoComplete │ │ │ ├── AutoComplete.md │ │ │ ├── AutoComplete.module.scss │ │ │ ├── AutoComplete.spec.ts │ │ │ ├── AutoComplete.tsx │ │ │ ├── AutoCompleteContext.tsx │ │ │ └── AutoCompleteNative.tsx │ │ ├── Avatar │ │ │ ├── Avatar.md │ │ │ ├── Avatar.module.scss │ │ │ ├── Avatar.spec.ts │ │ │ ├── Avatar.tsx │ │ │ └── AvatarNative.tsx │ │ ├── Backdrop │ │ │ ├── Backdrop.md │ │ │ ├── Backdrop.module.scss │ │ │ ├── Backdrop.spec.ts │ │ │ ├── Backdrop.tsx │ │ │ └── BackdropNative.tsx │ │ ├── Badge │ │ │ ├── Badge.md │ │ │ ├── Badge.module.scss │ │ │ ├── Badge.spec.ts │ │ │ ├── Badge.tsx │ │ │ └── BadgeNative.tsx │ │ ├── Bookmark │ │ │ ├── Bookmark.md │ │ │ ├── Bookmark.module.scss │ │ │ ├── Bookmark.spec.ts │ │ │ ├── Bookmark.tsx │ │ │ └── BookmarkNative.tsx │ │ ├── Breakout │ │ │ ├── Breakout.module.scss │ │ │ ├── Breakout.spec.ts │ │ │ ├── Breakout.tsx │ │ │ └── BreakoutNative.tsx │ │ ├── Button │ │ │ ├── Button-style.spec.ts │ │ │ ├── Button.md │ │ │ ├── Button.module.scss │ │ │ ├── Button.spec.ts │ │ │ ├── Button.tsx │ │ │ └── ButtonNative.tsx │ │ ├── Card │ │ │ ├── Card.md │ │ │ ├── Card.module.scss │ │ │ ├── Card.spec.ts │ │ │ ├── Card.tsx │ │ │ └── CardNative.tsx │ │ ├── Carousel │ │ │ ├── Carousel.md │ │ │ ├── Carousel.module.scss │ │ │ ├── Carousel.spec.ts │ │ │ ├── Carousel.tsx │ │ │ ├── CarouselContext.tsx │ │ │ ├── CarouselItem.tsx │ │ │ ├── CarouselItemNative.tsx │ │ │ └── CarouselNative.tsx │ │ ├── ChangeListener │ │ │ ├── ChangeListener.md │ │ │ ├── ChangeListener.spec.ts │ │ │ ├── ChangeListener.tsx │ │ │ └── ChangeListenerNative.tsx │ │ ├── chart-color-schemes.ts │ │ ├── Charts │ │ │ ├── AreaChart │ │ │ │ ├── AreaChart.md │ │ │ │ ├── AreaChart.spec.ts │ │ │ │ ├── AreaChart.tsx │ │ │ │ └── AreaChartNative.tsx │ │ │ ├── BarChart │ │ │ │ ├── BarChart.md │ │ │ │ ├── BarChart.module.scss │ │ │ │ ├── BarChart.spec.ts │ │ │ │ ├── BarChart.tsx │ │ │ │ └── BarChartNative.tsx │ │ │ ├── DonutChart │ │ │ │ ├── DonutChart.spec.ts │ │ │ │ └── DonutChart.tsx │ │ │ ├── LabelList │ │ │ │ ├── LabelList.spec.ts │ │ │ │ ├── LabelList.tsx │ │ │ │ ├── LabelListNative.module.scss │ │ │ │ └── LabelListNative.tsx │ │ │ ├── Legend │ │ │ │ ├── Legend.spec.ts │ │ │ │ ├── Legend.tsx │ │ │ │ └── LegendNative.tsx │ │ │ ├── LineChart │ │ │ │ ├── LineChart.md │ │ │ │ ├── LineChart.module.scss │ │ │ │ ├── LineChart.spec.ts │ │ │ │ ├── LineChart.tsx │ │ │ │ └── LineChartNative.tsx │ │ │ ├── PieChart │ │ │ │ ├── PieChart.md │ │ │ │ ├── PieChart.spec.ts │ │ │ │ ├── PieChart.tsx │ │ │ │ ├── PieChartNative.module.scss │ │ │ │ └── PieChartNative.tsx │ │ │ ├── RadarChart │ │ │ │ ├── RadarChart.md │ │ │ │ ├── RadarChart.spec.ts │ │ │ │ ├── RadarChart.tsx │ │ │ │ └── RadarChartNative.tsx │ │ │ ├── Tooltip │ │ │ │ ├── TooltipContent.module.scss │ │ │ │ ├── TooltipContent.spec.ts │ │ │ │ └── TooltipContent.tsx │ │ │ └── utils │ │ │ ├── abstractions.ts │ │ │ └── ChartProvider.tsx │ │ ├── Checkbox │ │ │ ├── Checkbox.md │ │ │ ├── Checkbox.spec.ts │ │ │ └── Checkbox.tsx │ │ ├── CodeBlock │ │ │ ├── CodeBlock.module.scss │ │ │ ├── CodeBlock.spec.ts │ │ │ ├── CodeBlock.tsx │ │ │ ├── CodeBlockNative.tsx │ │ │ └── highlight-code.ts │ │ ├── collectedComponentMetadata.ts │ │ ├── ColorPicker │ │ │ ├── ColorPicker.md │ │ │ ├── ColorPicker.module.scss │ │ │ ├── ColorPicker.spec.ts │ │ │ ├── ColorPicker.tsx │ │ │ └── ColorPickerNative.tsx │ │ ├── Column │ │ │ ├── Column.md │ │ │ ├── Column.tsx │ │ │ ├── ColumnNative.tsx │ │ │ ├── doc-resources │ │ │ │ └── list-component-data.js │ │ │ └── TableContext.tsx │ │ ├── component-utils.ts │ │ ├── ComponentProvider.tsx │ │ ├── ComponentRegistryContext.tsx │ │ ├── container-helpers.tsx │ │ ├── ContentSeparator │ │ │ ├── ContentSeparator.md │ │ │ ├── ContentSeparator.module.scss │ │ │ ├── ContentSeparator.spec.ts │ │ │ ├── ContentSeparator.tsx │ │ │ └── ContentSeparatorNative.tsx │ │ ├── DataSource │ │ │ ├── DataSource.md │ │ │ └── DataSource.tsx │ │ ├── DateInput │ │ │ ├── DateInput.md │ │ │ ├── DateInput.module.scss │ │ │ ├── DateInput.spec.ts │ │ │ ├── DateInput.tsx │ │ │ └── DateInputNative.tsx │ │ ├── DatePicker │ │ │ ├── DatePicker.md │ │ │ ├── DatePicker.module.scss │ │ │ ├── DatePicker.spec.ts │ │ │ ├── DatePicker.tsx │ │ │ └── DatePickerNative.tsx │ │ ├── DropdownMenu │ │ │ ├── DropdownMenu.md │ │ │ ├── DropdownMenu.module.scss │ │ │ ├── DropdownMenu.spec.ts │ │ │ ├── DropdownMenu.tsx │ │ │ ├── DropdownMenuNative.tsx │ │ │ ├── MenuItem.md │ │ │ └── SubMenuItem.md │ │ ├── EmojiSelector │ │ │ ├── EmojiSelector.md │ │ │ ├── EmojiSelector.spec.ts │ │ │ ├── EmojiSelector.tsx │ │ │ └── EmojiSelectorNative.tsx │ │ ├── ExpandableItem │ │ │ ├── ExpandableItem.module.scss │ │ │ ├── ExpandableItem.spec.ts │ │ │ ├── ExpandableItem.tsx │ │ │ └── ExpandableItemNative.tsx │ │ ├── FileInput │ │ │ ├── FileInput.md │ │ │ ├── FileInput.module.scss │ │ │ ├── FileInput.spec.ts │ │ │ ├── FileInput.tsx │ │ │ └── FileInputNative.tsx │ │ ├── FileUploadDropZone │ │ │ ├── FileUploadDropZone.md │ │ │ ├── FileUploadDropZone.module.scss │ │ │ ├── FileUploadDropZone.spec.ts │ │ │ ├── FileUploadDropZone.tsx │ │ │ └── FileUploadDropZoneNative.tsx │ │ ├── FlowLayout │ │ │ ├── FlowLayout.md │ │ │ ├── FlowLayout.module.scss │ │ │ ├── FlowLayout.spec.ts │ │ │ ├── FlowLayout.spec.ts-snapshots │ │ │ │ └── Edge-cases-boxShadow-is-not-clipped-1-non-smoke-darwin.png │ │ │ ├── FlowLayout.tsx │ │ │ └── FlowLayoutNative.tsx │ │ ├── Footer │ │ │ ├── Footer.md │ │ │ ├── Footer.module.scss │ │ │ ├── Footer.spec.ts │ │ │ ├── Footer.tsx │ │ │ └── FooterNative.tsx │ │ ├── Form │ │ │ ├── Form.md │ │ │ ├── Form.module.scss │ │ │ ├── Form.spec.ts │ │ │ ├── Form.tsx │ │ │ ├── formActions.ts │ │ │ ├── FormContext.ts │ │ │ └── FormNative.tsx │ │ ├── FormItem │ │ │ ├── FormItem.md │ │ │ ├── FormItem.module.scss │ │ │ ├── FormItem.spec.ts │ │ │ ├── FormItem.tsx │ │ │ ├── FormItemNative.tsx │ │ │ ├── HelperText.module.scss │ │ │ ├── HelperText.tsx │ │ │ ├── ItemWithLabel.tsx │ │ │ └── Validations.ts │ │ ├── FormSection │ │ │ ├── FormSection.md │ │ │ ├── FormSection.ts │ │ │ └── FormSection.xmlui │ │ ├── Fragment │ │ │ ├── Fragment.spec.ts │ │ │ └── Fragment.tsx │ │ ├── Heading │ │ │ ├── abstractions.ts │ │ │ ├── H1.md │ │ │ ├── H1.spec.ts │ │ │ ├── H2.md │ │ │ ├── H2.spec.ts │ │ │ ├── H3.md │ │ │ ├── H3.spec.ts │ │ │ ├── H4.md │ │ │ ├── H4.spec.ts │ │ │ ├── H5.md │ │ │ ├── H5.spec.ts │ │ │ ├── H6.md │ │ │ ├── H6.spec.ts │ │ │ ├── Heading.md │ │ │ ├── Heading.module.scss │ │ │ ├── Heading.spec.ts │ │ │ ├── Heading.tsx │ │ │ └── HeadingNative.tsx │ │ ├── HoverCard │ │ │ ├── HoverCard.tsx │ │ │ └── HovercardNative.tsx │ │ ├── HtmlTags │ │ │ ├── HtmlTags.module.scss │ │ │ ├── HtmlTags.spec.ts │ │ │ └── HtmlTags.tsx │ │ ├── Icon │ │ │ ├── AdmonitionDanger.tsx │ │ │ ├── AdmonitionInfo.tsx │ │ │ ├── AdmonitionNote.tsx │ │ │ ├── AdmonitionTip.tsx │ │ │ ├── AdmonitionWarning.tsx │ │ │ ├── ApiIcon.tsx │ │ │ ├── ArrowDropDown.module.scss │ │ │ ├── ArrowDropDown.tsx │ │ │ ├── ArrowDropUp.module.scss │ │ │ ├── ArrowDropUp.tsx │ │ │ ├── ArrowLeft.module.scss │ │ │ ├── ArrowLeft.tsx │ │ │ ├── ArrowRight.module.scss │ │ │ ├── ArrowRight.tsx │ │ │ ├── Attach.tsx │ │ │ ├── Binding.module.scss │ │ │ ├── Binding.tsx │ │ │ ├── BoardIcon.tsx │ │ │ ├── BoxIcon.tsx │ │ │ ├── CheckIcon.tsx │ │ │ ├── ChevronDownIcon.tsx │ │ │ ├── ChevronLeft.tsx │ │ │ ├── ChevronRight.tsx │ │ │ ├── ChevronUpIcon.tsx │ │ │ ├── CodeFileIcon.tsx │ │ │ ├── CodeSandbox.tsx │ │ │ ├── CompactListIcon.tsx │ │ │ ├── ContentCopyIcon.tsx │ │ │ ├── DarkToLightIcon.tsx │ │ │ ├── DatabaseIcon.module.scss │ │ │ ├── DatabaseIcon.tsx │ │ │ ├── DocFileIcon.tsx │ │ │ ├── DocIcon.tsx │ │ │ ├── DotMenuHorizontalIcon.tsx │ │ │ ├── DotMenuIcon.tsx │ │ │ ├── EmailIcon.tsx │ │ │ ├── EmptyFolderIcon.tsx │ │ │ ├── ErrorIcon.tsx │ │ │ ├── ExpressionIcon.tsx │ │ │ ├── FillPlusCricleIcon.tsx │ │ │ ├── FilterIcon.tsx │ │ │ ├── FolderIcon.tsx │ │ │ ├── GlobeIcon.tsx │ │ │ ├── HomeIcon.tsx │ │ │ ├── HyperLinkIcon.tsx │ │ │ ├── Icon.md │ │ │ ├── Icon.module.scss │ │ │ ├── Icon.spec.ts │ │ │ ├── Icon.tsx │ │ │ ├── IconNative.tsx │ │ │ ├── ImageFileIcon.tsx │ │ │ ├── Inspect.tsx │ │ │ ├── LightToDark.tsx │ │ │ ├── LinkIcon.tsx │ │ │ ├── ListIcon.tsx │ │ │ ├── LooseListIcon.tsx │ │ │ ├── MoonIcon.tsx │ │ │ ├── MoreOptionsIcon.tsx │ │ │ ├── NoSortIcon.tsx │ │ │ ├── PDFIcon.tsx │ │ │ ├── PenIcon.tsx │ │ │ ├── PhoneIcon.tsx │ │ │ ├── PhotoIcon.tsx │ │ │ ├── PlusIcon.tsx │ │ │ ├── SearchIcon.tsx │ │ │ ├── ShareIcon.tsx │ │ │ ├── SortAscendingIcon.tsx │ │ │ ├── SortDescendingIcon.tsx │ │ │ ├── StarsIcon.tsx │ │ │ ├── SunIcon.tsx │ │ │ ├── svg │ │ │ │ ├── admonition_danger.svg │ │ │ │ ├── admonition_info.svg │ │ │ │ ├── admonition_note.svg │ │ │ │ ├── admonition_tip.svg │ │ │ │ ├── admonition_warning.svg │ │ │ │ ├── api.svg │ │ │ │ ├── arrow-dropdown.svg │ │ │ │ ├── arrow-left.svg │ │ │ │ ├── arrow-right.svg │ │ │ │ ├── arrow-up.svg │ │ │ │ ├── attach.svg │ │ │ │ ├── binding.svg │ │ │ │ ├── box.svg │ │ │ │ ├── bulb.svg │ │ │ │ ├── code-file.svg │ │ │ │ ├── code-sandbox.svg │ │ │ │ ├── dark_to_light.svg │ │ │ │ ├── database.svg │ │ │ │ ├── doc.svg │ │ │ │ ├── empty-folder.svg │ │ │ │ ├── expression.svg │ │ │ │ ├── eye-closed.svg │ │ │ │ ├── eye-dark.svg │ │ │ │ ├── eye.svg │ │ │ │ ├── file-text.svg │ │ │ │ ├── filter.svg │ │ │ │ ├── folder.svg │ │ │ │ ├── img.svg │ │ │ │ ├── inspect.svg │ │ │ │ ├── light_to_dark.svg │ │ │ │ ├── moon.svg │ │ │ │ ├── pdf.svg │ │ │ │ ├── photo.svg │ │ │ │ ├── share.svg │ │ │ │ ├── stars.svg │ │ │ │ ├── sun.svg │ │ │ │ ├── trending-down.svg │ │ │ │ ├── trending-level.svg │ │ │ │ ├── trending-up.svg │ │ │ │ ├── txt.svg │ │ │ │ ├── unknown-file.svg │ │ │ │ ├── unlink.svg │ │ │ │ └── xls.svg │ │ │ ├── TableDeleteColumnIcon.tsx │ │ │ ├── TableDeleteRowIcon.tsx │ │ │ ├── TableInsertColumnIcon.tsx │ │ │ ├── TableInsertRowIcon.tsx │ │ │ ├── TrashIcon.tsx │ │ │ ├── TrendingDownIcon.tsx │ │ │ ├── TrendingLevelIcon.tsx │ │ │ ├── TrendingUpIcon.tsx │ │ │ ├── TxtIcon.tsx │ │ │ ├── UnknownFileIcon.tsx │ │ │ ├── UnlinkIcon.tsx │ │ │ ├── UserIcon.tsx │ │ │ ├── WarningIcon.tsx │ │ │ └── XlsIcon.tsx │ │ ├── IconProvider.tsx │ │ ├── IconRegistryContext.tsx │ │ ├── IFrame │ │ │ ├── IFrame.md │ │ │ ├── IFrame.module.scss │ │ │ ├── IFrame.spec.ts │ │ │ ├── IFrame.tsx │ │ │ └── IFrameNative.tsx │ │ ├── Image │ │ │ ├── Image.md │ │ │ ├── Image.module.scss │ │ │ ├── Image.spec.ts │ │ │ ├── Image.tsx │ │ │ └── ImageNative.tsx │ │ ├── Input │ │ │ ├── index.ts │ │ │ ├── InputAdornment.module.scss │ │ │ ├── InputAdornment.tsx │ │ │ ├── InputDivider.module.scss │ │ │ ├── InputDivider.tsx │ │ │ ├── InputLabel.module.scss │ │ │ ├── InputLabel.tsx │ │ │ ├── PartialInput.module.scss │ │ │ └── PartialInput.tsx │ │ ├── InspectButton │ │ │ ├── InspectButton.module.scss │ │ │ └── InspectButton.tsx │ │ ├── Items │ │ │ ├── Items.md │ │ │ ├── Items.spec.ts │ │ │ ├── Items.tsx │ │ │ └── ItemsNative.tsx │ │ ├── Link │ │ │ ├── Link.md │ │ │ ├── Link.module.scss │ │ │ ├── Link.spec.ts │ │ │ ├── Link.tsx │ │ │ └── LinkNative.tsx │ │ ├── List │ │ │ ├── doc-resources │ │ │ │ └── list-component-data.js │ │ │ ├── List.md │ │ │ ├── List.module.scss │ │ │ ├── List.spec.ts │ │ │ ├── List.tsx │ │ │ └── ListNative.tsx │ │ ├── Logo │ │ │ ├── doc-resources │ │ │ │ └── xmlui-logo.svg │ │ │ ├── Logo.md │ │ │ ├── Logo.tsx │ │ │ └── LogoNative.tsx │ │ ├── Markdown │ │ │ ├── CodeText.module.scss │ │ │ ├── CodeText.tsx │ │ │ ├── Markdown.md │ │ │ ├── Markdown.module.scss │ │ │ ├── Markdown.spec.ts │ │ │ ├── Markdown.tsx │ │ │ ├── MarkdownNative.tsx │ │ │ ├── parse-binding-expr.ts │ │ │ └── utils.ts │ │ ├── metadata-helpers.ts │ │ ├── ModalDialog │ │ │ ├── ConfirmationModalContextProvider.tsx │ │ │ ├── Dialog.module.scss │ │ │ ├── Dialog.tsx │ │ │ ├── ModalDialog.md │ │ │ ├── ModalDialog.module.scss │ │ │ ├── ModalDialog.spec.ts │ │ │ ├── ModalDialog.tsx │ │ │ ├── ModalDialogNative.tsx │ │ │ └── ModalVisibilityContext.tsx │ │ ├── NavGroup │ │ │ ├── NavGroup.md │ │ │ ├── NavGroup.module.scss │ │ │ ├── NavGroup.spec.ts │ │ │ ├── NavGroup.tsx │ │ │ ├── NavGroupContext.ts │ │ │ └── NavGroupNative.tsx │ │ ├── NavLink │ │ │ ├── NavLink.md │ │ │ ├── NavLink.module.scss │ │ │ ├── NavLink.spec.ts │ │ │ ├── NavLink.tsx │ │ │ └── NavLinkNative.tsx │ │ ├── NavPanel │ │ │ ├── NavPanel.md │ │ │ ├── NavPanel.module.scss │ │ │ ├── NavPanel.spec.ts │ │ │ ├── NavPanel.tsx │ │ │ └── NavPanelNative.tsx │ │ ├── NestedApp │ │ │ ├── AppWithCodeView.module.scss │ │ │ ├── AppWithCodeView.tsx │ │ │ ├── AppWithCodeViewNative.tsx │ │ │ ├── defaultProps.tsx │ │ │ ├── logo.svg │ │ │ ├── NestedApp.module.scss │ │ │ ├── NestedApp.tsx │ │ │ ├── NestedAppNative.tsx │ │ │ ├── Tooltip.module.scss │ │ │ ├── Tooltip.tsx │ │ │ └── utils.ts │ │ ├── NoResult │ │ │ ├── NoResult.md │ │ │ ├── NoResult.module.scss │ │ │ ├── NoResult.spec.ts │ │ │ ├── NoResult.tsx │ │ │ └── NoResultNative.tsx │ │ ├── NumberBox │ │ │ ├── numberbox-abstractions.ts │ │ │ ├── NumberBox.md │ │ │ ├── NumberBox.module.scss │ │ │ ├── NumberBox.spec.ts │ │ │ ├── NumberBox.tsx │ │ │ └── NumberBoxNative.tsx │ │ ├── Option │ │ │ ├── Option.md │ │ │ ├── Option.spec.ts │ │ │ ├── Option.tsx │ │ │ ├── OptionNative.tsx │ │ │ └── OptionTypeProvider.tsx │ │ ├── PageMetaTitle │ │ │ ├── PageMetaTilteNative.tsx │ │ │ ├── PageMetaTitle.md │ │ │ ├── PageMetaTitle.spec.ts │ │ │ └── PageMetaTitle.tsx │ │ ├── Pages │ │ │ ├── Page.md │ │ │ ├── Pages.md │ │ │ ├── Pages.module.scss │ │ │ ├── Pages.tsx │ │ │ └── PagesNative.tsx │ │ ├── Pagination │ │ │ ├── Pagination.md │ │ │ ├── Pagination.module.scss │ │ │ ├── Pagination.spec.ts │ │ │ ├── Pagination.tsx │ │ │ └── PaginationNative.tsx │ │ ├── PositionedContainer │ │ │ ├── PositionedContainer.module.scss │ │ │ ├── PositionedContainer.tsx │ │ │ └── PositionedContainerNative.tsx │ │ ├── ProfileMenu │ │ │ ├── ProfileMenu.module.scss │ │ │ └── ProfileMenu.tsx │ │ ├── ProgressBar │ │ │ ├── ProgressBar.md │ │ │ ├── ProgressBar.module.scss │ │ │ ├── ProgressBar.spec.ts │ │ │ ├── ProgressBar.tsx │ │ │ └── ProgressBarNative.tsx │ │ ├── Queue │ │ │ ├── Queue.md │ │ │ ├── Queue.spec.ts │ │ │ ├── Queue.tsx │ │ │ ├── queueActions.ts │ │ │ └── QueueNative.tsx │ │ ├── RadioGroup │ │ │ ├── RadioGroup.md │ │ │ ├── RadioGroup.module.scss │ │ │ ├── RadioGroup.spec.ts │ │ │ ├── RadioGroup.tsx │ │ │ ├── RadioGroupNative.tsx │ │ │ ├── RadioItem.tsx │ │ │ └── RadioItemNative.tsx │ │ ├── RealTimeAdapter │ │ │ ├── RealTimeAdapter.tsx │ │ │ └── RealTimeAdapterNative.tsx │ │ ├── Redirect │ │ │ ├── Redirect.md │ │ │ ├── Redirect.spec.ts │ │ │ └── Redirect.tsx │ │ ├── ResponsiveBar │ │ │ ├── README.md │ │ │ ├── ResponsiveBar.md │ │ │ ├── ResponsiveBar.module.scss │ │ │ ├── ResponsiveBar.spec.ts │ │ │ ├── ResponsiveBar.tsx │ │ │ └── ResponsiveBarNative.tsx │ │ ├── Select │ │ │ ├── HiddenOption.tsx │ │ │ ├── MultiSelectOption.tsx │ │ │ ├── OptionContext.ts │ │ │ ├── Select.md │ │ │ ├── Select.module.scss │ │ │ ├── Select.spec.ts │ │ │ ├── Select.tsx │ │ │ ├── SelectContext.tsx │ │ │ ├── SelectNative.tsx │ │ │ ├── SelectOption.tsx │ │ │ └── SimpleSelect.tsx │ │ ├── SelectionStore │ │ │ ├── SelectionStore.md │ │ │ ├── SelectionStore.tsx │ │ │ └── SelectionStoreNative.tsx │ │ ├── Slider │ │ │ ├── Slider.md │ │ │ ├── Slider.module.scss │ │ │ ├── Slider.spec.ts │ │ │ ├── Slider.tsx │ │ │ └── SliderNative.tsx │ │ ├── Slot │ │ │ ├── Slot.md │ │ │ ├── Slot.spec.ts │ │ │ └── Slot.ts │ │ ├── SlotItem.tsx │ │ ├── SpaceFiller │ │ │ ├── SpaceFiller.md │ │ │ ├── SpaceFiller.module.scss │ │ │ ├── SpaceFiller.spec.ts │ │ │ ├── SpaceFiller.tsx │ │ │ └── SpaceFillerNative.tsx │ │ ├── Spinner │ │ │ ├── Spinner.md │ │ │ ├── Spinner.module.scss │ │ │ ├── Spinner.spec.ts │ │ │ ├── Spinner.tsx │ │ │ └── SpinnerNative.tsx │ │ ├── Splitter │ │ │ ├── HSplitter.md │ │ │ ├── HSplitter.spec.ts │ │ │ ├── Splitter.md │ │ │ ├── Splitter.module.scss │ │ │ ├── Splitter.spec.ts │ │ │ ├── Splitter.tsx │ │ │ ├── SplitterNative.tsx │ │ │ ├── utils.ts │ │ │ ├── VSplitter.md │ │ │ └── VSplitter.spec.ts │ │ ├── Stack │ │ │ ├── CHStack.md │ │ │ ├── CHStack.spec.ts │ │ │ ├── CVStack.md │ │ │ ├── CVStack.spec.ts │ │ │ ├── HStack.md │ │ │ ├── HStack.spec.ts │ │ │ ├── Stack.md │ │ │ ├── Stack.module.scss │ │ │ ├── Stack.spec.ts │ │ │ ├── Stack.tsx │ │ │ ├── StackNative.tsx │ │ │ ├── VStack.md │ │ │ └── VStack.spec.ts │ │ ├── StickyBox │ │ │ ├── StickyBox.md │ │ │ ├── StickyBox.module.scss │ │ │ ├── StickyBox.tsx │ │ │ └── StickyBoxNative.tsx │ │ ├── Switch │ │ │ ├── Switch.md │ │ │ ├── Switch.spec.ts │ │ │ └── Switch.tsx │ │ ├── Table │ │ │ ├── doc-resources │ │ │ │ └── list-component-data.js │ │ │ ├── react-table-config.d.ts │ │ │ ├── Table.md │ │ │ ├── Table.module.scss │ │ │ ├── Table.spec.ts │ │ │ ├── Table.tsx │ │ │ ├── TableNative.tsx │ │ │ └── useRowSelection.tsx │ │ ├── TableOfContents │ │ │ ├── TableOfContents.module.scss │ │ │ ├── TableOfContents.spec.ts │ │ │ ├── TableOfContents.tsx │ │ │ └── TableOfContentsNative.tsx │ │ ├── Tabs │ │ │ ├── TabContext.tsx │ │ │ ├── TabItem.md │ │ │ ├── TabItem.tsx │ │ │ ├── TabItemNative.tsx │ │ │ ├── Tabs.md │ │ │ ├── Tabs.module.scss │ │ │ ├── Tabs.spec.ts │ │ │ ├── Tabs.tsx │ │ │ └── TabsNative.tsx │ │ ├── Text │ │ │ ├── Text.md │ │ │ ├── Text.module.scss │ │ │ ├── Text.spec.ts │ │ │ ├── Text.tsx │ │ │ └── TextNative.tsx │ │ ├── TextArea │ │ │ ├── TextArea.md │ │ │ ├── TextArea.module.scss │ │ │ ├── TextArea.spec.ts │ │ │ ├── TextArea.tsx │ │ │ ├── TextAreaNative.tsx │ │ │ ├── TextAreaResizable.tsx │ │ │ └── useComposedRef.ts │ │ ├── TextBox │ │ │ ├── TextBox.md │ │ │ ├── TextBox.module.scss │ │ │ ├── TextBox.spec.ts │ │ │ ├── TextBox.tsx │ │ │ └── TextBoxNative.tsx │ │ ├── Theme │ │ │ ├── NotificationToast.tsx │ │ │ ├── Theme.md │ │ │ ├── Theme.module.scss │ │ │ ├── Theme.spec.ts │ │ │ ├── Theme.tsx │ │ │ └── ThemeNative.tsx │ │ ├── TimeInput │ │ │ ├── TimeInput.md │ │ │ ├── TimeInput.module.scss │ │ │ ├── TimeInput.spec.ts │ │ │ ├── TimeInput.tsx │ │ │ ├── TimeInputNative.tsx │ │ │ └── utils.ts │ │ ├── Timer │ │ │ ├── Timer.md │ │ │ ├── Timer.spec.ts │ │ │ ├── Timer.tsx │ │ │ └── TimerNative.tsx │ │ ├── Toggle │ │ │ ├── Toggle.module.scss │ │ │ └── Toggle.tsx │ │ ├── ToneChangerButton │ │ │ ├── ToneChangerButton.md │ │ │ ├── ToneChangerButton.spec.ts │ │ │ └── ToneChangerButton.tsx │ │ ├── ToneSwitch │ │ │ ├── ToneSwitch.md │ │ │ ├── ToneSwitch.module.scss │ │ │ ├── ToneSwitch.spec.ts │ │ │ ├── ToneSwitch.tsx │ │ │ └── ToneSwitchNative.tsx │ │ ├── Tooltip │ │ │ ├── Tooltip.md │ │ │ ├── Tooltip.module.scss │ │ │ ├── Tooltip.spec.ts │ │ │ ├── Tooltip.tsx │ │ │ └── TooltipNative.tsx │ │ ├── Tree │ │ │ ├── testData.ts │ │ │ ├── Tree-dynamic.spec.ts │ │ │ ├── Tree-icons.spec.ts │ │ │ ├── Tree.md │ │ │ ├── Tree.spec.ts │ │ │ ├── TreeComponent.module.scss │ │ │ ├── TreeComponent.tsx │ │ │ └── TreeNative.tsx │ │ ├── TreeDisplay │ │ │ ├── TreeDisplay.md │ │ │ ├── TreeDisplay.module.scss │ │ │ ├── TreeDisplay.tsx │ │ │ └── TreeDisplayNative.tsx │ │ ├── ValidationSummary │ │ │ ├── ValidationSummary.module.scss │ │ │ └── ValidationSummary.tsx │ │ └── VisuallyHidden.tsx │ ├── components-core │ │ ├── abstractions │ │ │ ├── ComponentRenderer.ts │ │ │ ├── LoaderRenderer.ts │ │ │ ├── standalone.ts │ │ │ └── treeAbstractions.ts │ │ ├── action │ │ │ ├── actions.ts │ │ │ ├── APICall.tsx │ │ │ ├── FileDownloadAction.tsx │ │ │ ├── FileUploadAction.tsx │ │ │ ├── NavigateAction.tsx │ │ │ └── TimedAction.tsx │ │ ├── ApiBoundComponent.tsx │ │ ├── appContext │ │ │ ├── date-functions.ts │ │ │ ├── math-function.ts │ │ │ └── misc-utils.ts │ │ ├── AppContext.tsx │ │ ├── behaviors │ │ │ ├── Behavior.tsx │ │ │ ├── BehaviorContext.tsx │ │ │ └── CoreBehaviors.tsx │ │ ├── component-hooks.ts │ │ ├── ComponentDecorator.tsx │ │ ├── ComponentViewer.tsx │ │ ├── CompoundComponent.tsx │ │ ├── constants.ts │ │ ├── DebugViewProvider.tsx │ │ ├── descriptorHelper.ts │ │ ├── devtools │ │ │ ├── InspectorDialog.module.scss │ │ │ ├── InspectorDialog.tsx │ │ │ └── InspectorDialogVisibilityContext.tsx │ │ ├── EngineError.ts │ │ ├── event-handlers.ts │ │ ├── InspectorButton.module.scss │ │ ├── InspectorContext.tsx │ │ ├── interception │ │ │ ├── abstractions.ts │ │ │ ├── ApiInterceptor.ts │ │ │ ├── ApiInterceptorProvider.tsx │ │ │ ├── apiInterceptorWorker.ts │ │ │ ├── Backend.ts │ │ │ ├── Errors.ts │ │ │ ├── IndexedDb.ts │ │ │ ├── initMock.ts │ │ │ ├── InMemoryDb.ts │ │ │ ├── ReadonlyCollection.ts │ │ │ └── useApiInterceptorContext.tsx │ │ ├── loader │ │ │ ├── ApiLoader.tsx │ │ │ ├── DataLoader.tsx │ │ │ ├── ExternalDataLoader.tsx │ │ │ ├── Loader.tsx │ │ │ ├── MockLoaderRenderer.tsx │ │ │ └── PageableLoader.tsx │ │ ├── LoaderComponent.tsx │ │ ├── markup-check.ts │ │ ├── parts.ts │ │ ├── renderers.ts │ │ ├── rendering │ │ │ ├── AppContent.tsx │ │ │ ├── AppRoot.tsx │ │ │ ├── AppWrapper.tsx │ │ │ ├── buildProxy.ts │ │ │ ├── collectFnVarDeps.ts │ │ │ ├── ComponentAdapter.tsx │ │ │ ├── ComponentWrapper.tsx │ │ │ ├── Container.tsx │ │ │ ├── containers.ts │ │ │ ├── ContainerWrapper.tsx │ │ │ ├── ErrorBoundary.module.scss │ │ │ ├── ErrorBoundary.tsx │ │ │ ├── InvalidComponent.module.scss │ │ │ ├── InvalidComponent.tsx │ │ │ ├── nodeUtils.ts │ │ │ ├── reducer.ts │ │ │ ├── renderChild.tsx │ │ │ ├── StandaloneComponent.tsx │ │ │ ├── StateContainer.tsx │ │ │ ├── UnknownComponent.module.scss │ │ │ ├── UnknownComponent.tsx │ │ │ └── valueExtractor.ts │ │ ├── reportEngineError.ts │ │ ├── RestApiProxy.ts │ │ ├── script-runner │ │ │ ├── asyncProxy.ts │ │ │ ├── AttributeValueParser.ts │ │ │ ├── bannedFunctions.ts │ │ │ ├── BindingTreeEvaluationContext.ts │ │ │ ├── eval-tree-async.ts │ │ │ ├── eval-tree-common.ts │ │ │ ├── eval-tree-sync.ts │ │ │ ├── ParameterParser.ts │ │ │ ├── process-statement-async.ts │ │ │ ├── process-statement-common.ts │ │ │ ├── process-statement-sync.ts │ │ │ ├── ScriptingSourceTree.ts │ │ │ ├── simplify-expression.ts │ │ │ ├── statement-queue.ts │ │ │ └── visitors.ts │ │ ├── StandaloneApp.tsx │ │ ├── StandaloneExtensionManager.ts │ │ ├── TableOfContentsContext.tsx │ │ ├── theming │ │ │ ├── _themes.scss │ │ │ ├── component-layout-resolver.ts │ │ │ ├── extendThemeUtils.ts │ │ │ ├── hvar.ts │ │ │ ├── layout-resolver.ts │ │ │ ├── parse-layout-props.ts │ │ │ ├── StyleContext.tsx │ │ │ ├── StyleRegistry.ts │ │ │ ├── ThemeContext.tsx │ │ │ ├── ThemeProvider.tsx │ │ │ ├── themes │ │ │ │ ├── base-utils.ts │ │ │ │ ├── palette.ts │ │ │ │ ├── root.ts │ │ │ │ ├── solid.ts │ │ │ │ ├── theme-colors.ts │ │ │ │ └── xmlui.ts │ │ │ ├── themeVars.module.scss │ │ │ ├── themeVars.ts │ │ │ ├── transformThemeVars.ts │ │ │ └── utils.ts │ │ ├── utils │ │ │ ├── actionUtils.ts │ │ │ ├── audio-utils.ts │ │ │ ├── compound-utils.ts │ │ │ ├── css-utils.ts │ │ │ ├── DataLoaderQueryKeyGenerator.ts │ │ │ ├── date-utils.ts │ │ │ ├── extractParam.ts │ │ │ ├── hooks.tsx │ │ │ ├── LruCache.ts │ │ │ ├── mergeProps.ts │ │ │ ├── misc.ts │ │ │ ├── request-params.ts │ │ │ ├── statementUtils.ts │ │ │ └── treeUtils.ts │ │ └── xmlui-parser.ts │ ├── index-standalone.ts │ ├── index.scss │ ├── index.ts │ ├── language-server │ │ ├── server-common.ts │ │ ├── server-web-worker.ts │ │ ├── server.ts │ │ ├── services │ │ │ ├── common │ │ │ │ ├── docs-generation.ts │ │ │ │ ├── lsp-utils.ts │ │ │ │ ├── metadata-utils.ts │ │ │ │ └── syntax-node-utilities.ts │ │ │ ├── completion.ts │ │ │ ├── diagnostic.ts │ │ │ ├── format.ts │ │ │ └── hover.ts │ │ └── xmlui-metadata-generated.mjs │ ├── logging │ │ ├── LoggerContext.tsx │ │ ├── LoggerInitializer.tsx │ │ ├── LoggerService.ts │ │ └── xmlui.ts │ ├── logo.svg │ ├── parsers │ │ ├── common │ │ │ ├── GenericToken.ts │ │ │ ├── InputStream.ts │ │ │ └── utils.ts │ │ ├── scripting │ │ │ ├── code-behind-collect.ts │ │ │ ├── Lexer.ts │ │ │ ├── modules.ts │ │ │ ├── Parser.ts │ │ │ ├── ParserError.ts │ │ │ ├── ScriptingNodeTypes.ts │ │ │ ├── TokenTrait.ts │ │ │ ├── TokenType.ts │ │ │ └── tree-visitor.ts │ │ ├── style-parser │ │ │ ├── errors.ts │ │ │ ├── source-tree.ts │ │ │ ├── StyleInputStream.ts │ │ │ ├── StyleLexer.ts │ │ │ ├── StyleParser.ts │ │ │ └── tokens.ts │ │ └── xmlui-parser │ │ ├── CharacterCodes.ts │ │ ├── diagnostics.ts │ │ ├── fileExtensions.ts │ │ ├── index.ts │ │ ├── lint.ts │ │ ├── parser.ts │ │ ├── ParserError.ts │ │ ├── scanner.ts │ │ ├── syntax-kind.ts │ │ ├── syntax-node.ts │ │ ├── transform.ts │ │ ├── utils.ts │ │ ├── xmlui-serializer.ts │ │ └── xmlui-tree.ts │ ├── react-app-env.d.ts │ ├── syntax │ │ ├── monaco │ │ │ ├── grammar.monacoLanguage.ts │ │ │ ├── index.ts │ │ │ ├── xmlui-dark.ts │ │ │ ├── xmlui-light.ts │ │ │ └── xmluiscript.monacoLanguage.ts │ │ └── textMate │ │ ├── index.ts │ │ ├── xmlui-dark.json │ │ ├── xmlui-light.json │ │ ├── xmlui.json │ │ └── xmlui.tmLanguage.json │ ├── testing │ │ ├── assertions.ts │ │ ├── component-test-helpers.ts │ │ ├── ComponentDrivers.ts │ │ ├── drivers │ │ │ ├── DateInputDriver.ts │ │ │ ├── ModalDialogDriver.ts │ │ │ ├── NumberBoxDriver.ts │ │ │ ├── TextBoxDriver.ts │ │ │ ├── TimeInputDriver.ts │ │ │ ├── TimerDriver.ts │ │ │ └── TreeDriver.ts │ │ ├── fixtures.ts │ │ ├── infrastructure │ │ │ ├── index.html │ │ │ ├── main.tsx │ │ │ ├── public │ │ │ │ ├── mockServiceWorker.js │ │ │ │ ├── resources │ │ │ │ │ ├── bell.svg │ │ │ │ │ ├── box.svg │ │ │ │ │ ├── doc.svg │ │ │ │ │ ├── eye.svg │ │ │ │ │ ├── flower-640x480.jpg │ │ │ │ │ ├── sun.svg │ │ │ │ │ ├── test-image-100x100.jpg │ │ │ │ │ └── txt.svg │ │ │ │ └── serve.json │ │ │ └── TestBed.tsx │ │ └── themed-app-test-helpers.ts │ └── vite-env.d.ts ├── tests │ ├── components │ │ ├── CodeBlock │ │ │ └── hightlight-code.test.ts │ │ ├── playground-pattern.test.ts │ │ └── Tree │ │ └── Tree-states.test.ts │ ├── components-core │ │ ├── abstractions │ │ │ └── treeAbstractions.test.ts │ │ ├── container │ │ │ └── buildProxy.test.ts │ │ ├── interception │ │ │ ├── orderBy.test.ts │ │ │ ├── ReadOnlyCollection.test.ts │ │ │ └── request-param-converter.test.ts │ │ ├── scripts-runner │ │ │ ├── AttributeValueParser.test.ts │ │ │ ├── eval-tree-arrow-async.test.ts │ │ │ ├── eval-tree-arrow.test.ts │ │ │ ├── eval-tree-func-decl-async.test.ts │ │ │ ├── eval-tree-func-decl.test.ts │ │ │ ├── eval-tree-pre-post.test.ts │ │ │ ├── eval-tree-regression.test.ts │ │ │ ├── eval-tree.test.ts │ │ │ ├── function-proxy.test.ts │ │ │ ├── parser-regression.test.ts │ │ │ ├── process-event.test.ts │ │ │ ├── process-function.test.ts │ │ │ ├── process-implicit-context.test.ts │ │ │ ├── process-statement-asgn.test.ts │ │ │ ├── process-statement-destruct.test.ts │ │ │ ├── process-statement-regs.test.ts │ │ │ ├── process-statement-sync.test.ts │ │ │ ├── process-statement.test.ts │ │ │ ├── process-switch-sync.test.ts │ │ │ ├── process-switch.test.ts │ │ │ ├── process-try-sync.test.ts │ │ │ ├── process-try.test.ts │ │ │ └── test-helpers.ts │ │ ├── test-metadata-handler.ts │ │ ├── theming │ │ │ ├── border-segments.test.ts │ │ │ ├── component-layout.resolver.test.ts │ │ │ ├── layout-property-parser.test.ts │ │ │ ├── layout-resolver.test.ts │ │ │ ├── layout-resolver2.test.ts │ │ │ ├── layout-vp-override.test.ts │ │ │ └── padding-segments.test.ts │ │ └── utils │ │ ├── date-utils.test.ts │ │ ├── format-human-elapsed-time.test.ts │ │ └── LruCache.test.ts │ ├── language-server │ │ ├── completion.test.ts │ │ ├── format.test.ts │ │ ├── hover.test.ts │ │ └── mockData.ts │ └── parsers │ ├── common │ │ └── input-stream.test.ts │ ├── markdown │ │ └── parse-binding-expression.test.ts │ ├── parameter-parser.test.ts │ ├── paremeter-parser.test.ts │ ├── scripting │ │ ├── eval-tree-arrow.test.ts │ │ ├── eval-tree-pre-post.test.ts │ │ ├── eval-tree.test.ts │ │ ├── function-proxy.test.ts │ │ ├── lexer-literals.test.ts │ │ ├── lexer-misc.test.ts │ │ ├── module-parse.test.ts │ │ ├── parser-arrow.test.ts │ │ ├── parser-assignments.test.ts │ │ ├── parser-binary.test.ts │ │ ├── parser-destructuring.test.ts │ │ ├── parser-errors.test.ts │ │ ├── parser-expressions.test.ts │ │ ├── parser-function.test.ts │ │ ├── parser-literals.test.ts │ │ ├── parser-primary.test.ts │ │ ├── parser-regex.test.ts │ │ ├── parser-statements.test.ts │ │ ├── parser-unary.test.ts │ │ ├── process-event.test.ts │ │ ├── process-implicit-context.test.ts │ │ ├── process-statement-asgn.test.ts │ │ ├── process-statement-destruct.test.ts │ │ ├── process-statement-regs.test.ts │ │ ├── process-statement-sync.test.ts │ │ ├── process-statement.test.ts │ │ ├── process-switch-sync.test.ts │ │ ├── process-switch.test.ts │ │ ├── process-try-sync.test.ts │ │ ├── process-try.test.ts │ │ ├── simplify-expression.test.ts │ │ ├── statement-hooks.test.ts │ │ └── test-helpers.ts │ ├── style-parser │ │ ├── generateHvarChain.test.ts │ │ ├── parseHVar.test.ts │ │ ├── parser.test.ts │ │ └── tokens.test.ts │ └── xmlui │ ├── lint.test.ts │ ├── parser.test.ts │ ├── scanner.test.ts │ ├── transform.attr.test.ts │ ├── transform.circular.test.ts │ ├── transform.element.test.ts │ ├── transform.errors.test.ts │ ├── transform.escape.test.ts │ ├── transform.regression.test.ts │ ├── transform.script.test.ts │ ├── transform.test.ts │ └── xmlui.ts ├── tests-e2e │ ├── api-bound-component-regression.spec.ts │ ├── api-call-as-extracted-component.spec.ts │ ├── assign-to-object-or-array-regression.spec.ts │ ├── binding-regression.spec.ts │ ├── children-as-template-context-vars.spec.ts │ ├── compound-component.spec.ts │ ├── context-vars-regression.spec.ts │ ├── data-bindings.spec.ts │ ├── datasource-and-api-usage-in-var.spec.ts │ ├── datasource-direct-binding.spec.ts │ ├── datasource-onLoaded-regression.spec.ts │ ├── modify-array-item-regression.spec.ts │ ├── namespaces.spec.ts │ ├── push-to-array-regression.spec.ts │ ├── screen-breakpoints.spec.ts │ ├── scripting.spec.ts │ ├── state-scope-in-pages.spec.ts │ └── state-var-scopes.spec.ts ├── tsconfig.bin.json ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── vitest.config.ts ``` # Files -------------------------------------------------------------------------------- /xmlui/tests-e2e/datasource-direct-binding.spec.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { expect, test } from "../src/testing/fixtures"; 2 | 3 | test("directly bound datasource doesn't show empty list template during load", async ({ page, initTestBed }) => { 4 | await initTestBed(` 5 | <Fragment> 6 | <DataSource url="/data1" id="data"/> 7 | <List data="{data}" testId="list"> 8 | <property name="emptyListTemplate"> 9 | <Text testId="emptyLabel">Empty</Text> 10 | </property> 11 | </List> 12 | </Fragment> 13 | `, { 14 | apiInterceptor: { 15 | operations: { 16 | "load-api-data1": { 17 | url: "/data1", 18 | method: "get", 19 | handler: `() => { return ['data1', 'data2']; }`, 20 | }, 21 | }, 22 | }, 23 | }); 24 | await page.waitForRequest("/data1", {timeout: 0}); 25 | //asserts that the emptyListTemplate is not rendered 26 | expect(await page.getByTestId("emptyLabel").count()).toEqual(0); 27 | await expect(page.getByTestId("list")).toHaveText("data1data2"); 28 | }); 29 | 30 | 31 | test("directly bound datasource through multiple container levels", async ({ page, initTestBed }) => { 32 | await initTestBed(` 33 | <App> 34 | <DataSource id="allContacts" url="/data1" /> 35 | <Pages> 36 | <Page url="/"> 37 | <List data="{allContacts}" testId="list"> 38 | <property name="emptyListTemplate"> 39 | <Text testId="emptyLabel">Empty</Text> 40 | </property> 41 | </List> 42 | </Page> 43 | </Pages> 44 | </App> 45 | `, { 46 | apiInterceptor: { 47 | operations: { 48 | "load-api-data1": { 49 | url: "/data1", 50 | method: "get", 51 | handler: `() => { return ['data1', 'data2']; }`, 52 | }, 53 | }, 54 | }, 55 | }); 56 | 57 | await page.waitForRequest("/data1", {timeout: 0}); 58 | //asserts that the emptyListTemplate is not rendered 59 | expect(await page.getByTestId("emptyLabel").count()).toEqual(0); 60 | await expect(page.getByTestId("list")).toHaveText("data1data2"); 61 | }); 62 | ``` -------------------------------------------------------------------------------- /xmlui/src/parsers/scripting/ScriptingNodeTypes.ts: -------------------------------------------------------------------------------- ```typescript 1 | // --- Statement node type values 2 | export const T_BLOCK_STATEMENT = 1; 3 | export const T_EMPTY_STATEMENT = 2; 4 | export const T_EXPRESSION_STATEMENT = 3; 5 | export const T_ARROW_EXPRESSION_STATEMENT = 4; 6 | export const T_LET_STATEMENT = 5; 7 | export const T_CONST_STATEMENT = 6; 8 | export const T_VAR_STATEMENT = 7; 9 | export const T_IF_STATEMENT = 8; 10 | export const T_RETURN_STATEMENT = 9; 11 | export const T_BREAK_STATEMENT = 10; 12 | export const T_CONTINUE_STATEMENT = 11; 13 | export const T_WHILE_STATEMENT = 12; 14 | export const T_DO_WHILE_STATEMENT = 13; 15 | export const T_FOR_STATEMENT = 14; 16 | export const T_FOR_IN_STATEMENT = 15; 17 | export const T_FOR_OF_STATEMENT = 16; 18 | export const T_THROW_STATEMENT = 17; 19 | export const T_TRY_STATEMENT = 18; 20 | export const T_SWITCH_STATEMENT = 19; 21 | export const T_FUNCTION_DECLARATION = 20; 22 | 23 | // --- Expression node type values 24 | export const T_UNARY_EXPRESSION = 100; 25 | export const T_BINARY_EXPRESSION = 101; 26 | export const T_SEQUENCE_EXPRESSION = 102; 27 | export const T_CONDITIONAL_EXPRESSION = 103; 28 | export const T_FUNCTION_INVOCATION_EXPRESSION = 104; 29 | export const T_MEMBER_ACCESS_EXPRESSION = 105; 30 | export const T_CALCULATED_MEMBER_ACCESS_EXPRESSION = 106; 31 | export const T_IDENTIFIER = 107; 32 | export const T_TEMPLATE_LITERAL_EXPRESSION = 108; 33 | export const T_LITERAL = 109; 34 | export const T_ARRAY_LITERAL = 110; 35 | export const T_OBJECT_LITERAL = 111; 36 | export const T_SPREAD_EXPRESSION = 112; 37 | export const T_ASSIGNMENT_EXPRESSION = 113; 38 | export const T_NO_ARG_EXPRESSION = 114; 39 | export const T_ARROW_EXPRESSION = 115; 40 | export const T_PREFIX_OP_EXPRESSION = 116; 41 | export const T_POSTFIX_OP_EXPRESSION = 117; 42 | export const T_REACTIVE_VAR_DECLARATION = 118; 43 | 44 | // --- Other node type values 45 | export const T_VAR_DECLARATION = 200; 46 | export const T_DESTRUCTURE = 201; 47 | export const T_ARRAY_DESTRUCTURE = 202; 48 | export const T_OBJECT_DESTRUCTURE = 203; 49 | export const T_SWITCH_CASE = 204; 50 | ``` -------------------------------------------------------------------------------- /xmlui/src/components/ToneSwitch/ToneSwitch.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import { createComponentRenderer } from "../../components-core/renderers"; 2 | import { createMetadata } from "../metadata-helpers"; 3 | import { parseScssVar } from "../../components-core/theming/themeVars"; 4 | import styles from "./ToneSwitch.module.scss"; 5 | import { ToneSwitch, type ToneSwitchProps } from "./ToneSwitchNative"; 6 | 7 | const COMP = "ToneSwitch"; 8 | 9 | export const ToneSwitchMd = createMetadata({ 10 | status: "stable", 11 | description: 12 | "`ToneSwitch` enables the user to switch between light and dark modes using a switch control.", 13 | props: { 14 | iconLight: { 15 | type: "string", 16 | description: "Icon to display for light mode", 17 | defaultValue: "sun", 18 | }, 19 | iconDark: { 20 | type: "string", 21 | description: "Icon to display for dark mode", 22 | defaultValue: "moon", 23 | }, 24 | }, 25 | themeVars: parseScssVar(styles.themeVars), 26 | limitThemeVarsToComponent: true, 27 | defaultThemeVars: { 28 | [`backgroundColor-${COMP}-light`]: "$color-surface-200", 29 | [`color-${COMP}-light`]: "$color-text-primary", 30 | [`backgroundColor-${COMP}-dark`]: "$color-primary-500", 31 | [`color-${COMP}-dark`]: "$color-surface-0", 32 | [`borderColor-${COMP}`]: "$color-surface-200", 33 | [`borderColor-${COMP}--hover`]: "$color-surface-300", 34 | 35 | dark: { 36 | [`backgroundColor-${COMP}-light`]: "$color-surface-700", 37 | [`color-${COMP}-light`]: "$color-text-primary", 38 | [`borderColor-${COMP}`]: "$color-surface-600", 39 | [`borderColor-${COMP}--hover`]: "$color-surface-500", 40 | }, 41 | }, 42 | }); 43 | 44 | /** 45 | * Define the renderer for the ToneSwitch component 46 | */ 47 | export const toneSwitchComponentRenderer = createComponentRenderer( 48 | COMP, 49 | ToneSwitchMd, 50 | ({ node, extractValue, className }) => { 51 | return <ToneSwitch 52 | className={className} 53 | iconLight={extractValue(node.props.iconLight)} 54 | iconDark={extractValue(node.props.iconDark)} 55 | />; 56 | } 57 | ); 58 | ``` -------------------------------------------------------------------------------- /xmlui/src/components/TextArea/TextAreaResizable.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import * as React from "react"; 2 | import { useEffect, useRef } from "react"; 3 | import { useComposedRef } from "./useComposedRef"; 4 | 5 | const noop = () => {}; 6 | 7 | type TextAreaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>; 8 | 9 | type Style = Omit<NonNullable<TextAreaProps["style"]>, "maxHeight" | "minHeight"> & { 10 | height?: number; 11 | }; 12 | 13 | interface TextAreaAutosizeProps extends Omit<TextAreaProps, "style"> { 14 | maxRows?: number; 15 | minRows?: number; 16 | style?: Style; 17 | } 18 | 19 | const TextAreaResizable: React.ForwardRefRenderFunction<HTMLTextAreaElement, TextAreaAutosizeProps> = ( 20 | { maxRows, minRows, onChange = noop, style, ...props }, 21 | userRef 22 | ) => { 23 | const libRef = useRef<HTMLTextAreaElement | null>(null); 24 | const ref = useComposedRef(libRef, userRef); 25 | const [minCompHeight, setMinCompHeight] = React.useState<number>(); 26 | const [maxCompHeight, setMaxCompHeight] = React.useState<number>(); 27 | 28 | useEffect(() => { 29 | if (!libRef.current) return; 30 | 31 | // --- Simple get the physical sizes of the component... 32 | const style = getComputedStyle(libRef.current as HTMLElement); 33 | const lineHeight = parseFloat(style.lineHeight); 34 | const paddingSize = parseFloat(style.paddingTop) + parseFloat(style.paddingBottom); 35 | const borderSize = parseFloat(style.borderTop) + parseFloat(style.borderBottom); 36 | const extraSize = (style.boxSizing === "border-box" ? borderSize : 0) + paddingSize; 37 | 38 | // --- ... and calculate the min and max heights based on the line height 39 | setMinCompHeight(lineHeight * (minRows ?? 1) + extraSize); 40 | setMaxCompHeight(lineHeight * (maxRows ?? 10_000) + extraSize); 41 | }, [maxRows, minRows]); 42 | 43 | return ( 44 | <textarea 45 | ref={ref} 46 | {...props} 47 | onChange={onChange} 48 | style={{ ...style, minHeight: minCompHeight, maxHeight: maxCompHeight }} 49 | /> 50 | ); 51 | }; 52 | 53 | export default React.forwardRef(TextAreaResizable); 54 | ``` -------------------------------------------------------------------------------- /xmlui/src/components-core/utils/request-params.ts: -------------------------------------------------------------------------------- ```typescript 1 | // Testable helper function to convert request parameters 2 | export function convertRequestParamPart( 3 | part: Record<string, any>, 4 | paramTypes?: Record<string, string> 5 | ): Record<string, any> { 6 | if (!paramTypes) return part; 7 | 8 | const result = { ...part }; 9 | Object.keys(part).forEach((key) => { 10 | if (!(key in paramTypes)) return; 11 | 12 | const partValue = part[key]; 13 | const partValueType = typeof partValue; 14 | switch (paramTypes[key]) { 15 | // --- Parameter to integer 16 | case "integer": 17 | switch (partValueType) { 18 | case "number": 19 | result[key] = Math.round(partValue); 20 | break; 21 | case "string": 22 | result[key] = parseInt(partValue, 10); 23 | break; 24 | case "boolean": 25 | result[key] = partValue ? 1 : 0; 26 | break; 27 | } 28 | break; 29 | 30 | // --- Parameter to float 31 | case "float": 32 | case "real": 33 | case "double": 34 | switch (partValueType) { 35 | case "number": 36 | result[key] = partValue; 37 | break; 38 | case "string": 39 | result[key] = parseFloat(partValue); 40 | break; 41 | case "boolean": 42 | result[key] = partValue ? 1 : 0; 43 | break; 44 | } 45 | break; 46 | 47 | // --- Parameter to boolean 48 | case "boolean": 49 | switch (partValueType) { 50 | case "string": 51 | switch (partValue.toLowerCase()) { 52 | case "true": 53 | case "yes": 54 | case "on": 55 | result[key] = true; 56 | break; 57 | case "false": 58 | case "no": 59 | case "off": 60 | result[key] = false; 61 | break; 62 | } 63 | break; 64 | case "number": 65 | result[key] = !!partValue; 66 | break; 67 | } 68 | break; 69 | } 70 | }); 71 | return result; 72 | } 73 | ``` -------------------------------------------------------------------------------- /docs/public/pages/universal-properties.md: -------------------------------------------------------------------------------- ```markdown 1 | # Universal Properties 2 | 3 | These properties can be used with any XMLUI component. 4 | 5 | ## id 6 | 7 | The `id` property assigns a unique identifier to a component. 8 | 9 | ```xmlui 10 | <Button id="submit-button" onClick="{handleSubmit}">Submit</Button> 11 | ``` 12 | 13 | ```xmlui 14 | <TextBox id="email-input" placeholder="Enter your email" /> 15 | ``` 16 | 17 | 18 | ```xmlui /detailsDialog/ 19 | <!-- Modal with ID used by table action --> 20 | <Table data="/api/clients"> 21 | <Column bindTo="name" /> 22 | <Column header="Details"> 23 | <Icon name="doc-outline" onClick="detailsDialog.open($item)" /> 24 | </Column> 25 | </Table> 26 | 27 | <ModalDialog id="detailsDialog" maxWidth="800px"> 28 | <ClientDetails client="{$param}" /> 29 | </ModalDialog> 30 | ``` 31 | 32 | ```xmlui 33 | <!-- Dynamic IDs with data --> 34 | <Items data="{posts}"> 35 | <DataSource id="replyAccount-{$item.id}" url="/api/accounts/{$item.author_id}"> 36 | <Text>Post by {$item.title}</Text> 37 | </DataSource> 38 | </Items> 39 | ``` 40 | 41 | ## when 42 | 43 | Set `when` to `false` to prevent rendering of a component. 44 | 45 | ```xmlui 46 | <Text when="{user.isLoggedIn}">Welcome back!</Text> 47 | ``` 48 | 49 | ```xmlui 50 | <Spinner when="{isLoading}">Loading...</Spinner> 51 | ``` 52 | 53 | ## data 54 | 55 | The `data` property makes external data available to a component. It's typically used with [List](/components/List), [Items](/components/Items), and [Table](/components/Table), but any component can receive data this way. 56 | 57 | When the property is a reference to a `DataSource`, its value is the data returned from that `DataSource`. 58 | 59 | ```xmlui 60 | <List data="{users}"> 61 | <Text>{$item.name}</Text> 62 | </List> 63 | ``` 64 | 65 | ```xmlui 66 | <Items data="{products}"> 67 | <Card> 68 | <Text>{$item.title}</Text> 69 | <Text>{$item.price}</Text> 70 | </Card> 71 | </Items> 72 | ``` 73 | 74 | When the property is a string, the value is interpreted as an URL that returns JSON. 75 | 76 | ```xmlui 77 | <List data="https://api.tfl.gov.uk/line/mode/tube/status"> 78 | <Text>{$item.name}: {$item.lineStatuses[0].statusSeverityDescription}</Text> 79 | </List> 80 | ``` 81 | 82 | ``` -------------------------------------------------------------------------------- /xmlui/src/components/CodeBlock/CodeBlock.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import styles from "./CodeBlock.module.scss"; 2 | 3 | import { createComponentRenderer } from "../../components-core/renderers"; 4 | import { parseScssVar } from "../../components-core/theming/themeVars"; 5 | import { CodeBlock } from "./CodeBlockNative"; 6 | import { createMetadata } from "../metadata-helpers"; 7 | 8 | const COMP = "CodeBlock"; 9 | 10 | export const CodeBlockMd = createMetadata({ 11 | status: "stable", 12 | description: `The \`${COMP}\` component displays code with optional syntax highlighting and meta information.`, 13 | props: {}, 14 | themeVars: parseScssVar(styles.themeVars), 15 | defaultThemeVars: { 16 | "backgroundColor-CodeBlock": "$color-primary-50", 17 | "backgroundColor-CodeBlock-header": "$color-primary-100", 18 | "marginTop-CodeBlock": "$space-5", 19 | "marginBottom-CodeBlock": "$space-5", 20 | "backgroundColor-CodeBlock-highlightRow": "rgb(from $color-primary-200 r g b / 0.25)", 21 | "backgroundColor-CodeBlock-highlightString": "rgb(from $color-primary-200 r g b / 0.5)", 22 | 23 | "borderColor-CodeBlock-highlightString-emphasis": "$color-attention", 24 | "border-CodeBlock": "0.5px solid $borderColor", 25 | "borderRadius-CodeBlock": "$space-2", 26 | "height-CodeBlock": "fit-content", 27 | "paddingVertical-content-CodeBlock": "0", 28 | "paddingHorizontal-content-CodeBlock": "0", 29 | 30 | dark: { 31 | "backgroundColor-CodeBlock-header": "$color-surface-200", 32 | "backgroundColor-CodeBlock": "$color-surface-50", 33 | "backgroundColor-CodeBlock-highlightRow": "rgb(from $color-primary-300 r g b / 0.15)", 34 | "backgroundColor-CodeBlock-highlightString": "rgb(from $color-primary-300 r g b / 0.5)", 35 | } 36 | }, 37 | }); 38 | 39 | export const codeBlockComponentRenderer = createComponentRenderer( 40 | "CodeBlock", 41 | CodeBlockMd, 42 | ({ node, renderChild, className }) => { 43 | return ( 44 | <CodeBlock className={className}> 45 | {renderChild(node.children)} 46 | </CodeBlock> 47 | ); 48 | }, 49 | ); 50 | ``` -------------------------------------------------------------------------------- /xmlui/src/components/Pagination/Pagination.md: -------------------------------------------------------------------------------- ```markdown 1 | %-DESC-START 2 | 3 | ## Standalone 4 | 5 | If the `itemCount` property is provided, the component shows information about the number of entries shown per page, the total number of entries, as well as the current page index: 6 | 7 | ```xmlui-pg copy display 8 | <App> 9 | <Pagination itemCount="100" pageSize="10" /> 10 | </App> 11 | ``` 12 | 13 | If not, the [`hasPrevPage`](#hasprevpage) and [`hasNextPage`](#hasnextpage) properties can be used to control the enabled/disabled state of the previous and next buttons, while only the previous and next buttons are displayed: 14 | 15 | ```xmlui-pg copy display 16 | <App> 17 | <Pagination hasPrevPage="true" hasNextPage="true" /> 18 | </App> 19 | ``` 20 | 21 | ## Integrations 22 | 23 | ### Table 24 | 25 | `Pagination` has first-class support in the Table component. `Pagination` is controlled via the [`isPaginated`](./Table#ispaginated-default-false), [`pageSize`](./Table#pagesize), [`pageSizeOptions`](./Table#pagesizeoptions) and [`paginationControlsLocation`](./Table#paginationcontrolslocation-default-bottom) properties. 26 | 27 | ```xmlui 28 | <Table data="/api" isPaginated pageSize="5" pageSizeOptions="{[5, 10, 20]}"> 29 | <Column header="ID" bindTo="elem" width="80px"> 30 | <!-- ... --> 31 | </Column> 32 | <!-- ... --> 33 | </Table> 34 | ``` 35 | 36 | See the [Table reference](./Table#ispaginated-default-false) for a working example. 37 | 38 | ### List 39 | 40 | The `List` is a perfect example of a component that does not implement its own pagination. Thus, use the Pagination with a Datasource component and connect them to the List: 41 | 42 | ```xmlui 43 | <DataSource id="ds" url="/api" queryParams="{{ from: before, to: after }}" /> 44 | <Pagination 45 | itemCount="20" 46 | pageSize="{pageSize}" 47 | pageIndex="{currentPage}" 48 | onPageDidChange="(page, size, total) => { 49 | currentPage = page; 50 | before = page * size; 51 | after = before + size - 1; 52 | }" 53 | /> 54 | <List data="{ds}" /> 55 | ``` 56 | 57 | For a comprehensive example, see [How to paginate a List](../howto/paginate-a-list). 58 | 59 | %-DESC-END 60 | ``` -------------------------------------------------------------------------------- /xmlui/tests/components-core/abstractions/treeAbstractions.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { describe, expect, it } from "vitest"; 2 | import { NodeLoadingState, FlatTreeNodeWithState, FlatTreeNode } from '../../../src/components-core/abstractions/treeAbstractions'; 3 | 4 | describe('Tree Abstractions - Loading States', () => { 5 | it('NodeLoadingState type should have correct values', () => { 6 | const unloaded: NodeLoadingState = 'unloaded'; 7 | const loading: NodeLoadingState = 'loading'; 8 | const loaded: NodeLoadingState = 'loaded'; 9 | 10 | expect(unloaded).toBe('unloaded'); 11 | expect(loading).toBe('loading'); 12 | expect(loaded).toBe('loaded'); 13 | }); 14 | 15 | it('FlatTreeNodeWithState should extend FlatTreeNode', () => { 16 | const mockFlatTreeNode: FlatTreeNode = { 17 | id: 1, 18 | key: 1, 19 | path: [], 20 | displayName: 'Test Node', 21 | parentIds: [], 22 | selectable: true, 23 | isExpanded: false, 24 | depth: 0, 25 | hasChildren: false, 26 | }; 27 | 28 | const nodeWithState: FlatTreeNodeWithState = { 29 | ...mockFlatTreeNode, 30 | loadingState: 'unloaded', 31 | }; 32 | 33 | expect(nodeWithState.loadingState).toBe('unloaded'); 34 | expect(nodeWithState.id).toBe(1); 35 | expect(nodeWithState.hasChildren).toBe(false); 36 | }); 37 | 38 | it('FlatTreeNodeWithState should support all loading states', () => { 39 | const baseNode: FlatTreeNode = { 40 | id: 1, 41 | key: 1, 42 | path: [], 43 | displayName: 'Test Node', 44 | parentIds: [], 45 | selectable: true, 46 | isExpanded: false, 47 | depth: 0, 48 | hasChildren: true, 49 | }; 50 | 51 | const unloadedNode: FlatTreeNodeWithState = { ...baseNode, loadingState: 'unloaded' }; 52 | const loadingNode: FlatTreeNodeWithState = { ...baseNode, loadingState: 'loading' }; 53 | const loadedNode: FlatTreeNodeWithState = { ...baseNode, loadingState: 'loaded' }; 54 | 55 | expect(unloadedNode.loadingState).toBe('unloaded'); 56 | expect(loadingNode.loadingState).toBe('loading'); 57 | expect(loadedNode.loadingState).toBe('loaded'); 58 | }); 59 | }); ``` -------------------------------------------------------------------------------- /xmlui/src/components/NavGroup/NavGroup.module.scss: -------------------------------------------------------------------------------- ```scss 1 | @use "../../components-core/theming/themes" as t; 2 | 3 | // --- This code snippet is required to collect the theme variables used in this module 4 | $themeVars: (); 5 | $time: 0.3s; 6 | @function createThemeVar($componentVariable) { 7 | $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global; 8 | @return t.getThemeVar($themeVars, $componentVariable); 9 | } 10 | 11 | $backgroundColor-dropdown-NavGroup: createThemeVar("backgroundColor-dropdown-NavGroup"); 12 | $boxShadow-dropdown-NavGroup: createThemeVar("boxShadow-dropdown-NavGroup"); 13 | $borderRadius-dropdown-NavGroup: createThemeVar("borderRadius-dropdown-NavGroup"); 14 | 15 | @layer components { 16 | .dropdownList { 17 | padding: 0; 18 | margin: 0; 19 | left: 0; 20 | top: 0; 21 | position: absolute; 22 | overflow-y: hidden; 23 | background: $backgroundColor-dropdown-NavGroup; 24 | list-style: none; 25 | height: fit-content; 26 | transition: height 0.2s ease; 27 | box-shadow: $boxShadow-dropdown-NavGroup; 28 | min-width: 11rem; 29 | border-radius: $borderRadius-dropdown-NavGroup; 30 | 31 | &:focus, 32 | &:focus-visible { 33 | outline: none; 34 | } 35 | } 36 | 37 | .groupContent { 38 | /* The core of the grid animation technique */ 39 | display: grid; 40 | grid-template-rows: 0fr; /* Initially the content row has 0 height */ 41 | will-change: grid-template-rows; 42 | transition: grid-template-rows $time cubic-bezier(0.4, 0, 0.2, 1); 43 | .groupContentInner { 44 | will-change: opacity; 45 | opacity: 0; 46 | } 47 | &.expanded { 48 | .groupContentInner { 49 | opacity: 1; 50 | overflow: hidden; 51 | } 52 | grid-template-rows: 1fr; 53 | } 54 | } 55 | 56 | .groupContentInner { 57 | transition: opacity $time ease-out; 58 | /* This is the grid item. Its content determines the 'auto' height. */ 59 | overflow: hidden; 60 | display: flex; 61 | flex-direction: column; 62 | } 63 | } 64 | 65 | // --- We export the theme variables to add them to the component renderer 66 | :export { 67 | themeVars: t.json-stringify($themeVars); 68 | } 69 | ``` -------------------------------------------------------------------------------- /xmlui/src/parsers/xmlui-parser/syntax-node.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { SyntaxKind } from "./syntax-kind"; 2 | import { findTokenAtPos } from "./utils"; 3 | 4 | export class Node { 5 | public readonly kind: SyntaxKind; 6 | /** Start position of the node including it's trivia */ 7 | public readonly start: number; 8 | public readonly pos: number; 9 | public readonly end: number; 10 | public readonly triviaBefore?: Node[]; 11 | public readonly children?: Node[]; 12 | 13 | constructor( 14 | kind: SyntaxKind, 15 | pos: number, 16 | end: number, 17 | triviaBefore?: Node[], 18 | children?: Node[], 19 | ) { 20 | this.kind = kind; 21 | this.pos = pos; 22 | this.end = end; 23 | this.triviaBefore = triviaBefore; 24 | this.children = children; 25 | 26 | if (triviaBefore) { 27 | this.start = triviaBefore[0]?.start ?? pos; 28 | } else if (children) { 29 | this.start = children[0]?.start ?? pos; 30 | } else { 31 | this.start = pos; 32 | } 33 | } 34 | 35 | isElementNode(): this is ElementNode { 36 | return this.kind === SyntaxKind.ElementNode; 37 | } 38 | 39 | isAttributeNode(): this is AttributeNode { 40 | return this.kind === SyntaxKind.AttributeNode; 41 | } 42 | 43 | isAttributeKeyNode(): this is AttributeKeyNode { 44 | return this.kind === SyntaxKind.AttributeKeyNode; 45 | } 46 | 47 | isContentListNode(): this is ContentListNode { 48 | return this.kind === SyntaxKind.ContentListNode; 49 | } 50 | 51 | isAttributeListNode(): this is AttributeListNode { 52 | return this.kind === SyntaxKind.AttributeListNode; 53 | } 54 | 55 | isTagNameNode(): this is TagNameNode { 56 | return this.kind === SyntaxKind.TagNameNode; 57 | } 58 | 59 | findTokenAtPos(position: number) { 60 | return findTokenAtPos(this, position); 61 | } 62 | } 63 | 64 | export class ElementNode extends Node { 65 | getAttributeListNode() { 66 | return this.children!.find((c) => c.isContentListNode()); 67 | } 68 | } 69 | 70 | export class AttributeNode extends Node {} 71 | export class AttributeKeyNode extends Node {} 72 | export class ContentListNode extends Node {} 73 | export class AttributeListNode extends Node {} 74 | export class TagNameNode extends Node {} 75 | ``` -------------------------------------------------------------------------------- /xmlui/src/components/Tabs/TabItem.md: -------------------------------------------------------------------------------- ```markdown 1 | %-DESC-START 2 | 3 | **Key features:** 4 | - **Label definition**: Provides the clickable tab header text via the label property 5 | - **Content container**: Wraps any child components that display when the tab is active 6 | - **Structural organization**: Creates the relationship between tab headers and their corresponding content 7 | - **Seamless integration**: Designed exclusively for use within [Tabs](/components/Tabs) components 8 | 9 | **Usage pattern:** 10 | Always used as a direct child of [Tabs](/components/Tabs) components. The `label` property defines the tab button text, while child components placed within the TabItem provide the content that displays when the tab is selected. 11 | 12 | %-DESC-END 13 | 14 | %-PROP-START headerTemplate 15 | 16 | ```xmlui-pg copy {7-9} display name="Example: headerTemplate" /headerTemplate/ height="200px" 17 | <App> 18 | <Tabs> 19 | <TabItem label="Home"> 20 | Home content 21 | </TabItem> 22 | <TabItem label="Accounts"> 23 | <property name="headerTemplate"> 24 | <Text variant="title" color="green">Accounts</Text> 25 | </property> 26 | Accounts content 27 | </TabItem> 28 | <TabItem label="Settings"> 29 | Settings content 30 | </TabItem> 31 | </Tabs> 32 | </App> 33 | ``` 34 | 35 | > [!INFO] You can customize the [header templates](./Tabs#headertemplate) of **all** tab items, too. You can mix the `Tabs` level header templates with the `TabItem` level ones. In this case, the `TabItem` level template prevails. 36 | 37 | %-PROP-END 38 | 39 | %-EVENT-START activated 40 | 41 | ```xmlui-pg copy display name="Example: activated" /onActivated/ height="200px" 42 | <App var.activationCount="{0}"> 43 | <Tabs> 44 | <TabItem label="Account" onActivated="activationCount++"> 45 | <Text>Account</Text> 46 | </TabItem> 47 | <TabItem label="Stream"> 48 | <Text>Stream</Text> 49 | </TabItem> 50 | <TabItem label="Support"> 51 | <Text>Support</Text> 52 | </TabItem> 53 | </Tabs> 54 | <Text>The Account tab has been activated {activationCount} times.</Text> 55 | </App> 56 | ``` 57 | 58 | %-EVENT-END ``` -------------------------------------------------------------------------------- /xmlui/src/components/APICall/APICall.md: -------------------------------------------------------------------------------- ```markdown 1 | %-DESC-START 2 | 3 | **Key characteristics:** 4 | - **Manual execution**: Call `execute()` method to trigger the API request 5 | - **Form integration**: Commonly used in `<event name="submit">` handlers for forms 6 | - **Parameter passing**: Pass data to the API call via `execute()` parameters 7 | - **Built-in notifications**: Supports automatic progress, success, and error messages 8 | 9 | %-DESC-END 10 | 11 | %-PROP-START completedNotificationMessage 12 | 13 | This property customizes the success message displayed in a toast after the finished API invocation. The `$result` context variable can refer to the response body. For example, you can use the following code snippet to display the first 100 characters in the completed operation's response body: 14 | 15 | ```xmlui copy 16 | <APICall 17 | id="ds" 18 | method="post" 19 | url="/api/shopping-list" 20 | completedNotificationMessage="Result: {JSON.stringify($result).substring(0, 100)}" /> 21 | ``` 22 | 23 | %-PROP-END 24 | 25 | %-PROP-START errorNotificationMessage 26 | 27 | This property customizes the message displayed in a toast when the API invocation results in an error. The `$error.statusCode` context variable can refer to the response's status code, while `$error. details` to the response body. For example, you can use the following code snippet to display the status code and the details: 28 | 29 | ```xmlui copy 30 | <APICall 31 | id="ds" 32 | method="post" 33 | url="/api/shopping-list" 34 | errorNotificationMessage="${error.statusCode}, {JSON.stringify($error.details)}" /> 35 | ``` 36 | %-PROP-END 37 | 38 | %-PROP-START invalidates 39 | 40 | This property takes either a string or a list of strings representing URL endpoints to invalidate the local data cache based on the given URL routes. 41 | 42 | For a short overview on client side caching, see the [DataSource component](/components/DataSource). 43 | 44 | %-PROP-END 45 | 46 | %-PROP-START updates 47 | 48 | This property takes either a string or a list of strings representing URL endpoints to indicate which data should be updated in the cache. 49 | 50 | %-PROP-END 51 | ``` -------------------------------------------------------------------------------- /xmlui/src/components/SelectionStore/SelectionStore.md: -------------------------------------------------------------------------------- ```markdown 1 | %-DESC-START 2 | 3 | For an example that covers all props, and API methods and values, see the [\`Selection-Aware Components\`](#selection-aware-components) section. 4 | 5 | ## Using `SelectionStore` 6 | 7 | A `SelectionStore` instance is a mediator between a component that can manage a list of selected items and the external context. 8 | The wrapped component can report its selection state (which items are currently selected); 9 | so that the external context can access and manage the selection state through the component's API. 10 | 11 | ## Selection-Aware Components 12 | 13 | Some components know they are wrapped with a `SelectionStore` and manage their current selection state through it. 14 | For example, the following sample `SelectionStore` wraps a `Table` with its `rowsSelectable` property set to true. 15 | Whenever the user changes the selection state of a particular row, the `SelectionStore` updates its state accordingly. 16 | 17 | ```xmlui-pg 18 | ---app copy display name="Example: using SelectionStore" 19 | <App> 20 | <H3>Rockets {rockets.value.length ? "(" + rockets.value.length + " selected)" : ""}</H3> 21 | <HStack> 22 | <Button label="Select First" 23 | onClick="if (rockets.value?.length) rockets.setSelectedRowIds([rockets.value[0].id])" /> 24 | <Button label="Refresh Table" onClick="rockets.refreshSelection()" /> 25 | <Button label="Clear Selection" onClick="rockets.clearSelection()" /> 26 | </HStack> 27 | <SelectionStore id="rockets"> 28 | <Table 29 | width="100%" 30 | rowsSelectable="{true}" 31 | data="https://api.spacexdata.com/v3/rockets" 32 | height="300px"> 33 | <Column header="Image" size="80px"> 34 | <Image height="80px" fit="cover" src="{$item.flickr_images[0]}"/> 35 | </Column> 36 | <Column canSort="true" bindTo="country"/> 37 | <Column canSort="true" bindTo="company"/> 38 | </Table> 39 | </SelectionStore> 40 | </App> 41 | ---desc 42 | The UI refreshes the number of selected items as you check or uncheck the rows in the following table: 43 | ``` 44 | 45 | %-DESC-END 46 | ``` -------------------------------------------------------------------------------- /packages/xmlui-playground/src/playground/ConfirmationDialog.module.scss: -------------------------------------------------------------------------------- ```scss 1 | .Overlay { 2 | background-color: rgba(0, 0, 0, 0.4); 3 | position: fixed; 4 | inset: 0; 5 | animation: overlayShow 150ms cubic-bezier(0.16, 1, 0.3, 1); 6 | } 7 | 8 | .Content { 9 | background-color: #ffffff; 10 | border-radius: 6px; 11 | box-shadow: 12 | rgba(0, 0, 0, 0.35) 0px 10px 38px -10px, 13 | rgba(0, 0, 0, 0.2) 0px 10px 20px -15px; 14 | position: fixed; 15 | top: 50%; 16 | left: 50%; 17 | transform: translate(-50%, -50%); 18 | width: 90vw; 19 | max-width: 500px; 20 | max-height: 85vh; 21 | padding: 25px; 22 | animation: contentShow 150ms cubic-bezier(0.16, 1, 0.3, 1); 23 | &:focus { 24 | outline: none; 25 | } 26 | } 27 | 28 | .Title { 29 | margin: 0; 30 | color: #1b1c1d; 31 | font-size: 17px; 32 | font-weight: 500; 33 | } 34 | 35 | .Description { 36 | margin-bottom: 20px; 37 | color: #6b6c70; 38 | font-size: 15px; 39 | line-height: 1.5; 40 | } 41 | 42 | .Button { 43 | all: unset; 44 | display: inline-flex; 45 | align-items: center; 46 | justify-content: center; 47 | border-radius: 4px; 48 | padding: 0 15px; 49 | font-size: 15px; 50 | line-height: 1; 51 | font-weight: 500; 52 | height: 35px; 53 | } 54 | 55 | .Button.violet { 56 | background-color: #ffffff; 57 | color: #7c4dff; 58 | box-shadow: rgba(0, 0, 0, 0.7) 0px 2px 10px; 59 | } 60 | .Button.violet:hover { 61 | background-color: #f2f2f2; 62 | } 63 | .Button.violet:focus { 64 | box-shadow: 0 0 0 2px #000000; 65 | } 66 | 67 | .Button.red { 68 | background-color: #ffcccc; 69 | color: #cc0000; 70 | } 71 | .Button.red:hover { 72 | background-color: #ff9999; 73 | } 74 | .Button.red:focus { 75 | box-shadow: 0 0 0 2px #ff6666; 76 | } 77 | 78 | .Button.mauve { 79 | background-color: #e5e5e5; 80 | color: #6b6c70; 81 | } 82 | .Button.mauve:hover { 83 | background-color: #d9d9d9; 84 | } 85 | .Button.mauve:focus { 86 | box-shadow: 0 0 0 2px #a6a6a6; 87 | } 88 | 89 | .Actions { 90 | width: 100%; 91 | justify-content: flex-end; 92 | align-self: center; 93 | display: flex; 94 | gap: 1rem; 95 | } 96 | 97 | @keyframes overlayShow { 98 | from { 99 | opacity: 0; 100 | } 101 | to { 102 | opacity: 1; 103 | } 104 | } 105 | 106 | @keyframes contentShow { 107 | from { 108 | opacity: 0; 109 | transform: translate(-50%, -48%) scale(0.96); 110 | } 111 | to { 112 | opacity: 1; 113 | transform: translate(-50%, -50%) scale(1); 114 | } 115 | } 116 | ``` -------------------------------------------------------------------------------- /xmlui/src/components/Stack/Stack.module.scss: -------------------------------------------------------------------------------- ```scss 1 | @use "../../components-core/theming/themes" as t; 2 | 3 | // --- This code snippet is required to collect the theme variables used in this module 4 | $themeVars: (); 5 | @function createThemeVar($componentVariable) { 6 | $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global; 7 | @return t.getThemeVar($themeVars, $componentVariable); 8 | } 9 | 10 | @layer components { 11 | .base { 12 | display: flex; 13 | /* default flexbox alignment */ 14 | justify-content: flex-start; 15 | align-items: stretch; 16 | min-height: 0; 17 | min-width: 0; 18 | gap: var(--stack-gap-default); 19 | 20 | &.hoverContainer{ 21 | :global(.display-on-hover){ 22 | transition: opacity .1s; 23 | opacity: 0; 24 | visibility: hidden; 25 | } 26 | 27 | &:has(:global(.dropdown-visible)) :global(.display-on-hover){ 28 | opacity: 1; 29 | visibility: visible; 30 | } 31 | 32 | &:hover, &:focus-within{ 33 | :global(.display-on-hover) { 34 | opacity: 1; 35 | visibility: visible; 36 | } 37 | } 38 | } 39 | 40 | &.handlesClick{ 41 | cursor: pointer; 42 | } 43 | } 44 | 45 | .vertical { 46 | flex-direction: column; 47 | min-height: 0; 48 | 49 | &.reverse{ 50 | flex-direction: column-reverse; 51 | } 52 | } 53 | 54 | .horizontal { 55 | flex-direction: row; 56 | &.reverse{ 57 | flex-direction: row-reverse; 58 | } 59 | } 60 | 61 | /* Main axis */ 62 | .justifyItemsStart { 63 | justify-content: flex-start; 64 | } 65 | .justifyItemsCenter { 66 | justify-content: center; 67 | } 68 | .justifyItemsStretch { 69 | justify-content: stretch; 70 | } 71 | .justifyItemsEnd { 72 | justify-content: flex-end; 73 | } 74 | 75 | /* Cross axis */ 76 | .alignItemsStart { 77 | align-items: flex-start; 78 | } 79 | .alignItemsCenter { 80 | align-items: center; 81 | } 82 | .alignItemsStretch { 83 | align-items: stretch; 84 | } 85 | .alignItemsEnd { 86 | align-items: flex-end; 87 | } 88 | .alignItemsBaseline { 89 | align-items: baseline; 90 | } 91 | } 92 | 93 | // --- We export the theme variables to add them to the component renderer 94 | :export { 95 | themeVars: t.json-stringify($themeVars); 96 | } 97 | ``` -------------------------------------------------------------------------------- /xmlui/src/components/IFrame/IFrameNative.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import type React from "react"; 2 | import { type CSSProperties, forwardRef, memo, useEffect, useRef } from "react"; 3 | import classnames from "classnames"; 4 | 5 | import styles from "./IFrame.module.scss"; 6 | import type { RegisterComponentApiFn } from "../../abstractions/RendererDefs"; 7 | 8 | type Props = { 9 | src?: string; 10 | srcdoc?: string; 11 | allow?: string; 12 | name?: string; 13 | referrerPolicy?: "no-referrer" | "no-referrer-when-downgrade" | "origin" | "origin-when-cross-origin" | "same-origin" | "strict-origin" | "strict-origin-when-cross-origin" | "unsafe-url"; 14 | sandbox?: string; 15 | style?: CSSProperties; 16 | className?: string; 17 | registerComponentApi?: RegisterComponentApiFn; 18 | } & Pick<React.HTMLAttributes<HTMLIFrameElement>, "onLoad">; 19 | 20 | export const IFrame = memo(forwardRef(function IFrame( 21 | { 22 | src, 23 | srcdoc, 24 | allow, 25 | name, 26 | referrerPolicy, 27 | sandbox, 28 | style, 29 | className, 30 | onLoad, 31 | registerComponentApi, 32 | ...rest 33 | }: Props, 34 | ref: React.ForwardedRef<HTMLIFrameElement>, 35 | ) { 36 | const iframeRef = useRef<HTMLIFrameElement>(null); 37 | 38 | // Register component APIs 39 | useEffect(() => { 40 | registerComponentApi?.({ 41 | postMessage: (message: any, targetOrigin: string = "*") => { 42 | const contentWindow = iframeRef.current?.contentWindow; 43 | if (contentWindow) { 44 | contentWindow.postMessage(message, targetOrigin); 45 | } 46 | }, 47 | getContentWindow: () => { 48 | return iframeRef.current?.contentWindow || null; 49 | }, 50 | getContentDocument: () => { 51 | return iframeRef.current?.contentDocument || null; 52 | }, 53 | }); 54 | }, [registerComponentApi]); 55 | 56 | return ( 57 | <iframe 58 | {...rest} 59 | ref={iframeRef} 60 | src={src} 61 | srcDoc={srcdoc} 62 | allow={allow} 63 | name={name} 64 | referrerPolicy={referrerPolicy} 65 | sandbox={sandbox} 66 | className={classnames(className, styles.iframe)} 67 | style={style} 68 | onLoad={onLoad} 69 | /> 70 | ); 71 | })); 72 | ``` -------------------------------------------------------------------------------- /xmlui/src/components/Card/Card.module.scss: -------------------------------------------------------------------------------- ```scss 1 | @use "../../components-core/theming/themes" as t; 2 | 3 | // --- This code snippet is required to collect the theme variables used in this module 4 | $themeVars: (); 5 | @function createThemeVar($componentVariable) { 6 | $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global; 7 | @return t.getThemeVar($themeVars, $componentVariable); 8 | } 9 | 10 | $component: "Card"; 11 | $themeVars: t.composePaddingVars($themeVars, $component); 12 | $themeVars: t.composeBorderVars($themeVars, $component); 13 | $boxShadow-Card: createThemeVar("boxShadow-Card"); 14 | $backgroundColor-Card: createThemeVar("backgroundColor-Card"); 15 | $borderRadius-Card: createThemeVar("borderRadius-Card"); 16 | $gap-Card: createThemeVar("gap-Card"); 17 | $gap-title-Card: createThemeVar("gap-title-Card"); 18 | $gap-avatar-Card: createThemeVar("gap-avatar-Card"); 19 | $horizontalAlignment-title-Card: createThemeVar("horizontalAlignment-title-Card"); 20 | $verticalalAlignment-title-Card: createThemeVar("verticalAlignment-title-Card"); 21 | 22 | 23 | @layer components { 24 | .wrapper { 25 | @include t.borderVars($themeVars, $component); 26 | @include t.paddingVars($themeVars, $component); 27 | box-shadow: $boxShadow-Card; 28 | background-color: $backgroundColor-Card; 29 | display: flex; 30 | flex-direction: column; 31 | gap: $gap-Card; 32 | 33 | .avatarWrapper { 34 | display: flex; 35 | flex-direction: row; 36 | align-items: $verticalalAlignment-title-Card; 37 | gap: $gap-avatar-Card; 38 | min-width: 0; 39 | } 40 | 41 | .titleWrapper { 42 | display: flex; 43 | flex-direction: column; 44 | align-items: $horizontalAlignment-title-Card; 45 | width: 100%; 46 | gap: $gap-title-Card; 47 | min-width: 0; 48 | } 49 | 50 | &.horizontal { 51 | flex-direction: row; 52 | align-items: center; 53 | } 54 | 55 | &.vertical { 56 | flex-direction: column; 57 | } 58 | 59 | &.isClickable { 60 | cursor: pointer; 61 | } 62 | } 63 | } 64 | 65 | // --- We export the theme variables to add them to the component renderer 66 | :export{ 67 | themeVars: t.json-stringify($themeVars) 68 | } 69 | ``` -------------------------------------------------------------------------------- /packages/xmlui-os-frames/src/MacOSAppFrame.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import styles from "./MacOSAppFrame.module.scss"; 2 | import type { ReactNode } from "react"; 3 | import { forwardRef } from "react"; 4 | 5 | export const MacOSAppFrame = forwardRef(({ children }: { children: ReactNode }, ref)=> { 6 | return ( 7 | <section role="application" className={styles.app} tabIndex={-1} ref={ref as any}> 8 | <div className={styles.appButtons}> 9 | <div className={styles.appButtonsInner}> 10 | <button className={`${styles.close} ${styles.button}`}> 11 | <svg width="7" height="7" fill="none" xmlns="http://www.w3.org/2000/svg"> 12 | <path 13 | stroke="#000" 14 | strokeWidth="1.2" 15 | strokeLinecap="round" 16 | d="M1.182 5.99L5.99 1.182m0 4.95L1.182 1.323" 17 | ></path> 18 | </svg> 19 | </button> 20 | <button className={`${styles.minimize} ${styles.button}`}> 21 | <svg fill="none" xmlns="http://www.w3.org/2000/svg" width="6" height="1"> 22 | <path stroke="#000" strokeLinecap="round" d="M.61.703h5.8" strokeWidth="2"></path> 23 | </svg> 24 | </button> 25 | <button className={`${styles.stretch} ${styles.button}`}> 26 | <svg 27 | viewBox="0 0 13 13" 28 | xmlns="http://www.w3.org/2000/svg" 29 | fillRule="evenodd" 30 | clipRule="evenodd" 31 | strokeLinejoin="round" 32 | strokeMiterlimit="2" 33 | > 34 | <path d="M4.871 3.553L9.37 8.098V3.553H4.871zm3.134 5.769L3.506 4.777v4.545h4.499z"></path> 35 | <circle fill="none" cx="6.438" cy="6.438" r="6.438"></circle> 36 | </svg> 37 | </button> 38 | </div> 39 | </div> 40 | <section className={styles.container}> 41 | <header className={styles.titleBar}> 42 | <span className="s-85680v">XMLUI</span> 43 | </header> 44 | <section className={styles.mainArea}>{children}</section> 45 | </section> 46 | </section> 47 | ); 48 | }); 49 | ``` -------------------------------------------------------------------------------- /xmlui/src/abstractions/scripting/Compilation.ts: -------------------------------------------------------------------------------- ```typescript 1 | import type { ComponentDef, CompoundComponentDef } from "../ComponentDefs"; 2 | import type { ThemeDefinition } from "../ThemingDefs"; 3 | import type { Expression, Statement } from "../../components-core/script-runner/ScriptingSourceTree"; 4 | 5 | /** Contains the compilation result of a project */ 6 | export type ProjectCompilation = { 7 | /** The compiled Main.xmlui file (with its optional code behind) */ 8 | entrypoint: EntrypointCompilation; 9 | 10 | /** The compiled component files (with their optional code behind) */ 11 | components: ComponentCompilation[]; 12 | 13 | /** The compiled theme files */ 14 | themes: Record<string, ThemeDefinition>; 15 | }; 16 | 17 | /** The compilation result of a single file */ 18 | export type FileCompilation = EntrypointCompilation | ComponentCompilation; 19 | 20 | type CompilationUnit = { 21 | /** The file name */ 22 | filename: string; 23 | /** Optional markup source (used in dev mode) */ 24 | markupSource?: string; 25 | /** Optional code behind source (used in dev mode) */ 26 | codeBehindSource?: string; 27 | /** Other (non-core) component names this component depends on */ 28 | dependencies: Set<string>; 29 | }; 30 | 31 | export type ComponentCompilation = CompilationUnit & { 32 | /** The compiled markup of the component file */ 33 | definition: CompoundComponentDef; 34 | }; 35 | 36 | export type EntrypointCompilation = CompilationUnit & { 37 | /** The compiled markup of the main file */ 38 | definition: ComponentDef; 39 | }; 40 | 41 | export type ParsedEventValue = { 42 | __PARSED: true; 43 | parseId: number; 44 | statements: Statement[]; 45 | source?: string; 46 | } 47 | 48 | // --- The parsed property value (if defined by an attribute value) 49 | export type ParsedPropertyValue = { 50 | // --- We recognize this as a parsed property value 51 | __PARSED: true; 52 | 53 | // --- ID used for caching the parsed property value 54 | parseId: number; 55 | 56 | // --- The property segments 57 | segments?: PropertySegment[]; 58 | }; 59 | 60 | // --- The compliation result of a single property 61 | export type PropertySegment = { 62 | literal?: string; 63 | expr?: Expression; 64 | deps?: string[]; 65 | }; 66 | ``` -------------------------------------------------------------------------------- /docs/content/components/ProgressBar.md: -------------------------------------------------------------------------------- ```markdown 1 | # ProgressBar [#progressbar] 2 | 3 | `ProgressBar` provides a visual indicator showing the completion percentage of tasks, processes, or any measurable progress. It displays as a horizontal bar that fills from left to right based on the provided value between 0 (empty) and 1 (complete). 4 | 5 | **Key features:** 6 | - **Percentage visualization**: Displays progress as a filled portion of a horizontal bar 7 | - **Flexible value handling**: Accepts values from 0 to 1, with automatic bounds handling for out-of-range values 8 | - **Extensive theming**: Customizable background color, indicator color, thickness, and border radius 9 | - **Responsive design**: Adapts to container width while maintaining consistent height 10 | 11 | ## Properties [#properties] 12 | 13 | ### `value` (default: 0) [#value-default-0] 14 | 15 | This property defines the progress value with a number between 0 and 1. 16 | 17 | The following example demonstrates using it: 18 | 19 | ```xmlui-pg copy {2-6} display name="Example: value" height="200px" 20 | <App> 21 | <ProgressBar /> 22 | <ProgressBar value="0.2"/> 23 | <ProgressBar value="0.6"/> 24 | <ProgressBar value="1"/> 25 | <ProgressBar value="1.2"/> 26 | </App> 27 | ``` 28 | 29 | ## Events [#events] 30 | 31 | This component does not have any events. 32 | 33 | ## Exposed Methods [#exposed-methods] 34 | 35 | This component does not expose any methods. 36 | 37 | ## Styling [#styling] 38 | 39 | ### Theme Variables [#theme-variables] 40 | 41 | | Variable | Default Value (Light) | Default Value (Dark) | 42 | | --- | --- | --- | 43 | | [backgroundColor](../styles-and-themes/common-units/#color)-ProgressBar | $color-surface-200 | $color-surface-200 | 44 | | [borderRadius](../styles-and-themes/common-units/#border-rounding)-indicator-ProgressBar | 0px | 0px | 45 | | [borderRadius](../styles-and-themes/common-units/#border-rounding)-ProgressBar | $borderRadius | $borderRadius | 46 | | [color](../styles-and-themes/common-units/#color)-indicator-ProgressBar | $color-primary-500 | $color-primary-500 | 47 | | [thickness](../styles-and-themes/common-units/#size)-ProgressBar | $space-2 | $space-2 | 48 | ``` -------------------------------------------------------------------------------- /xmlui/src/components/Charts/utils/ChartProvider.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import type { ReactNode } from "react"; 2 | import { useState } from "react"; 3 | import { createContext, useContext } from "react"; 4 | 5 | type ChartContextType = { 6 | dataKey: string; 7 | dataKeys: string[]; 8 | nameKey: string; 9 | labelList: any; 10 | setLabelList: any; 11 | legend: any; 12 | setLegend: any; 13 | }; 14 | 15 | const ChartContext = createContext<ChartContextType | undefined>({ 16 | dataKey: "", 17 | nameKey: "", 18 | dataKeys: [], 19 | labelList: null, 20 | setLabelList: () => {}, 21 | legend: null, 22 | setLegend: () => {}, 23 | }); 24 | 25 | export function useChartContextValue({ 26 | dataKey = "", 27 | dataKeys = [], 28 | nameKey = "", 29 | }: { 30 | dataKey?: string; 31 | dataKeys?: string[]; 32 | nameKey: string; 33 | }) { 34 | const [dKey] = useState(dataKey); 35 | const [dKeys] = useState(dataKeys); 36 | const [nKey] = useState(nameKey); 37 | const [labelList, setLabelList] = useState(null); 38 | const [legend, setLegend] = useState(null); 39 | 40 | return { 41 | dataKey: dKey, 42 | nameKey: nKey, 43 | dataKeys: dKeys, 44 | labelList, 45 | setLabelList, 46 | legend, 47 | setLegend, 48 | }; 49 | } 50 | 51 | export function useChart() { 52 | const context = useContext(ChartContext); 53 | if (context === undefined) { 54 | throw new Error("useChart must be used within a ChartProvider"); 55 | } 56 | return context; 57 | } 58 | 59 | export function useLabelList() { 60 | const context = useContext(ChartContext); 61 | if (context === undefined) { 62 | throw new Error("LabelList must be used within a chart"); 63 | } 64 | 65 | const { setLabelList } = context; 66 | return { 67 | setLabelList, 68 | }; 69 | } 70 | 71 | export function useLegend() { 72 | const context = useContext(ChartContext); 73 | if (context === undefined) { 74 | throw new Error("Legend must be used within a chart"); 75 | } 76 | 77 | const { setLegend } = context; 78 | return { 79 | setLegend, 80 | }; 81 | } 82 | 83 | type ChartProviderProps = { 84 | value: ChartContextType; 85 | children: ReactNode; 86 | }; 87 | 88 | function ChartProvider({ value, children }: ChartProviderProps) { 89 | return <ChartContext.Provider value={value}>{children}</ChartContext.Provider>; 90 | } 91 | 92 | export default ChartProvider; 93 | ``` -------------------------------------------------------------------------------- /xmlui/src/components/TreeDisplay/TreeDisplay.module.scss: -------------------------------------------------------------------------------- ```scss 1 | @use "../../components-core/theming/themes" as t; 2 | 3 | // --- This code snippet is required to collect the theme variables used in this module 4 | $themeVars: (); 5 | @function createThemeVar($componentVariable) { 6 | $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global; 7 | @return t.getThemeVar($themeVars, $componentVariable); 8 | } 9 | 10 | $component: "TreeDisplay"; 11 | $fontSize-TreeDisplay: createThemeVar("fontSize-#{$component}"); 12 | $themeVars: t.composeBorderVars($themeVars, $component); 13 | 14 | @layer components { 15 | .treeDisplay { 16 | display: block; 17 | @include t.borderVars($themeVars, $component); 18 | background-color: createThemeVar("backgroundColor-#{$component}"); 19 | padding: createThemeVar("padding-#{$component}"); 20 | padding-left: createThemeVar("paddingLeft-#{$component}"); 21 | } 22 | 23 | .content { 24 | display: block; 25 | word-break: break-word; 26 | font-family: createThemeVar("fontFamily-#{$component}"); 27 | font-size: $fontSize-TreeDisplay; 28 | color: createThemeVar("color-#{$component}"); 29 | } 30 | 31 | .treeNode { 32 | position: relative; 33 | padding: 0; 34 | margin: 0; 35 | } 36 | 37 | .treeNodeRow { 38 | display: flex; 39 | align-items: center; 40 | position: relative; 41 | margin: 0; 42 | } 43 | 44 | .connectorArea { 45 | position: relative; 46 | flex-shrink: 0; 47 | } 48 | 49 | .connector { 50 | position: absolute; 51 | top: 0; 52 | left: 0; 53 | } 54 | 55 | .connectorLine { 56 | stroke: createThemeVar("color-connect-#{$component}"); 57 | fill: none; 58 | } 59 | 60 | .ancestorLine { 61 | position: absolute; 62 | top: 0; 63 | width: 1.5px; 64 | height: 100%; 65 | background-color: createThemeVar("color-connect-#{$component}"); 66 | z-index: 1; 67 | pointer-events: none; 68 | } 69 | 70 | .treeNodeContent { 71 | padding: 0 4px; 72 | display: inline-block; 73 | position: relative; 74 | z-index: 2; 75 | } 76 | 77 | .childrenContainer { 78 | position: relative; 79 | margin: 0; 80 | padding: 0; 81 | } 82 | } 83 | 84 | // --- We export the theme variables to add them to the component renderer 85 | :export { 86 | themeVars: t.json-stringify($themeVars); 87 | } 88 | ``` -------------------------------------------------------------------------------- /xmlui/src/components/Bookmark/BookmarkNative.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import type { ReactNode } from "react"; 2 | import { useCallback, useContext, useEffect, useLayoutEffect, useRef } from "react"; 3 | import { TableOfContentsContext } from "../../components-core/TableOfContentsContext"; 4 | import type { RegisterComponentApiFn } from "../../abstractions/RendererDefs"; 5 | import styles from "./Bookmark.module.scss"; 6 | 7 | type Props = { 8 | uid?: string; 9 | level?: number; 10 | title?: string; 11 | omitFromToc?: boolean; 12 | children: ReactNode; 13 | registerComponentApi?: RegisterComponentApiFn; 14 | }; 15 | 16 | export const defaultProps: Pick<Props, "level" | "omitFromToc"> = { 17 | level: 1, 18 | omitFromToc: false, 19 | }; 20 | 21 | export const Bookmark = ({ 22 | uid, 23 | level = defaultProps.level, 24 | children, 25 | title, 26 | omitFromToc = defaultProps.omitFromToc, 27 | registerComponentApi, 28 | ...rest 29 | }: Props) => { 30 | const elementRef = useRef<HTMLAnchorElement>(null); 31 | const tableOfContentsContext = useContext(TableOfContentsContext); 32 | const registerHeading = tableOfContentsContext?.registerHeading; 33 | const observeIntersection = tableOfContentsContext?.hasTableOfContents; 34 | 35 | const scrollIntoView = useCallback((options?: ScrollIntoViewOptions) => { 36 | if (elementRef.current) { 37 | elementRef.current.scrollIntoView({ 38 | behavior: "smooth", 39 | block: "start", 40 | ...options, 41 | }); 42 | } 43 | }, []); 44 | 45 | useEffect(() => { 46 | registerComponentApi?.({ 47 | scrollIntoView, 48 | }); 49 | }, [registerComponentApi, scrollIntoView]); 50 | 51 | useLayoutEffect(() => { 52 | if (observeIntersection && elementRef.current && uid && !omitFromToc) { 53 | return registerHeading?.({ 54 | id: uid, 55 | level, 56 | text: title || elementRef.current?.textContent?.trim()?.replace(/#$/, "") || uid, 57 | anchor: elementRef.current, 58 | }); 59 | } 60 | }, [uid, observeIntersection, registerHeading, level, title, omitFromToc]); 61 | 62 | return ( 63 | <span {...rest} ref={elementRef} id={uid} data-anchor={true} className={styles.anchorRef}> 64 | {children} 65 | </span> 66 | ); 67 | }; 68 | ``` -------------------------------------------------------------------------------- /.github/workflows/run-smoke-tests.yml: -------------------------------------------------------------------------------- ```yaml 1 | name: Smoke Tests (e2e smoke tests, unit tests) 2 | 3 | concurrency: # Concurrency ensures that only a single job or workflow using the same concurrency group will run at a time. 4 | group: component-e2e-testing-smoke 5 | cancel-in-progress: true 6 | 7 | on: 8 | workflow_dispatch: 9 | push: 10 | branches: [main] 11 | jobs: 12 | test: 13 | if: | 14 | github.event_name == 'workflow_dispatch' || 15 | ( 16 | github.event_name == 'push' && 17 | !contains(github.event.head_commit.message, 'Version Packages for Stable Release') && 18 | !contains(github.event.head_commit.message, 'chore: version packages for stable release') 19 | ) 20 | timeout-minutes: 60 21 | runs-on: ubuntu-latest-8-core 22 | env: 23 | NODE_OPTIONS: "--max-old-space-size=8192" 24 | steps: 25 | - uses: actions/checkout@v4 26 | - uses: actions/setup-node@v4 27 | with: 28 | node-version: 22 29 | cache: "npm" 30 | - name: Install node dependencies 31 | run: npm ci --prefer-offline 32 | - name: Cache for Turbo 33 | uses: rharkor/[email protected] 34 | - name: Store Playwright's Version 35 | run: | 36 | PLAYWRIGHT_VERSION=$(npm ls @playwright/test | grep @playwright | sed 's/.*@//' | sort | head -n 1) 37 | echo "Playwright's Version: $PLAYWRIGHT_VERSION" 38 | echo "PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION" >> $GITHUB_ENV 39 | - name: Cache Playwright Browsers for Playwright's Version 40 | id: cache-playwright-browsers 41 | uses: actions/cache@v4 42 | with: 43 | path: ~/.cache/ms-playwright 44 | key: playwright-browsers-${{ env.PLAYWRIGHT_VERSION }} 45 | - name: Install Playwright Browsers 46 | if: steps.cache-playwright-browsers.outputs.cache-hit != 'true' 47 | run: npx playwright install --with-deps 48 | - name: run some tests 49 | run: npm run test-xmlui-smoke 50 | - uses: actions/upload-artifact@v4 51 | with: 52 | name: playwright-report 53 | path: xmlui/playwright-report/ 54 | retention-days: 30 55 | ``` -------------------------------------------------------------------------------- /packages/xmlui-hello-world/src/HelloWorld.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import styles from "./HelloWorld.module.scss"; 2 | import { createComponentRenderer, parseScssVar, createMetadata } from "xmlui"; 3 | import { HelloWorld, defaultProps } from "./HelloWorldNative"; 4 | 5 | const HelloWorldMd = createMetadata({ 6 | description: 7 | "`HelloWorld` is a demonstration component that shows basic XMLUI patterns.", 8 | status: "experimental", 9 | props: { 10 | message: { 11 | description: "The greeting message to display.", 12 | isRequired: false, 13 | type: "string", 14 | defaultValue: defaultProps.message, 15 | }, 16 | }, 17 | events: { 18 | onClick: { 19 | description: 20 | "Triggered when the click button is pressed. " + "Receives the current click count.", 21 | type: "function", 22 | }, 23 | onReset: { 24 | description: 25 | "Triggered when the reset button is pressed. " + "Called when count is reset to 0.", 26 | type: "function", 27 | }, 28 | }, 29 | apis: { 30 | value: { 31 | description: "The current click count value.", 32 | type: "number", 33 | }, 34 | setValue: { 35 | description: "Set the click count to a specific value.", 36 | type: "function", 37 | }, 38 | }, 39 | themeVars: parseScssVar(styles.themeVars), 40 | defaultThemeVars: { 41 | [`backgroundColor-HelloWorld`]: "$color-surface-50", 42 | [`textColor-HelloWorld`]: "$color-content-primary", 43 | dark: { 44 | [`backgroundColor-HelloWorld`]: "$color-surface-800", 45 | // No textColor override needed - $color-content-primary should auto-adapt 46 | }, 47 | }, 48 | }); 49 | 50 | export const helloWorldComponentRenderer = createComponentRenderer( 51 | "HelloWorld", 52 | HelloWorldMd, 53 | 54 | ({ node, extractValue, lookupEventHandler, className, registerComponentApi }) => { 55 | return ( 56 | <HelloWorld 57 | id={extractValue.asOptionalString(node.props?.id)} 58 | message={extractValue.asOptionalString(node.props?.message)} 59 | onClick={lookupEventHandler("onClick")} 60 | onReset={lookupEventHandler("onReset")} 61 | className={className} 62 | registerComponentApi={registerComponentApi} 63 | /> 64 | ); 65 | }, 66 | ); 67 | ``` -------------------------------------------------------------------------------- /xmlui/src/components/Accordion/AccordionItem.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import { createComponentRenderer } from "../../components-core/renderers"; 2 | import { createMetadata, dComponent } from "../../components/metadata-helpers"; 3 | import { MemoizedItem } from "../../components/container-helpers"; 4 | import { 5 | AccordionItemComponent, 6 | defaultProps, 7 | } from "../../components/Accordion/AccordionItemNative"; 8 | 9 | const COMP = "AccordionItem"; 10 | 11 | export const AccordionItemMd = createMetadata({ 12 | status: "in progress", 13 | description: 14 | `\`${COMP}\` is a non-visual component describing a tab. Tabs component may use nested ` + 15 | `${COMP} instances from which the user can select.`, 16 | props: { 17 | header: { 18 | description: "This property declares the text used in the component's header. If not provided, the header will be empty.", 19 | valueType: "string" 20 | }, 21 | headerTemplate: dComponent( 22 | "This property describes the template to use as the component's header.", 23 | ), 24 | initiallyExpanded: { 25 | description: `This property indicates if the ${COMP} is expanded (\`true\`) or collapsed (\`false\`).`, 26 | valueType: "boolean", 27 | defaultValue: defaultProps.initiallyExpanded 28 | }, 29 | }, 30 | }); 31 | 32 | export const accordionItemComponentRenderer = createComponentRenderer( 33 | COMP, 34 | AccordionItemMd, 35 | (rendererContext) => { 36 | const { node, renderChild, extractValue, className } = rendererContext; 37 | return ( 38 | <AccordionItemComponent 39 | className={className} 40 | id={extractValue(node.uid)} 41 | header={extractValue(node.props.header)} 42 | initiallyExpanded={extractValue.asOptionalBoolean(node.props.initiallyExpanded)} 43 | headerRenderer={ 44 | node.props.headerTemplate 45 | ? (item) => ( 46 | <MemoizedItem 47 | node={node.props.headerTemplate ?? ({ type: "Fragment" } as any)} 48 | item={item} 49 | renderChild={renderChild} 50 | /> 51 | ) 52 | : undefined 53 | } 54 | content={renderChild(node.children)} 55 | /> 56 | ); 57 | }, 58 | ); 59 | ``` -------------------------------------------------------------------------------- /packages/xmlui-hello-world/src/HelloWorldNative.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import React, { useState, useEffect } from "react"; 2 | import styles from "./HelloWorld.module.scss"; 3 | import type { RegisterComponentApiFn } from "xmlui"; 4 | 5 | type Props = { 6 | id?: string; 7 | message?: string; 8 | className?: string; 9 | onClick?: (event: React.MouseEvent) => void; 10 | onReset?: (event: React.MouseEvent) => void; 11 | registerComponentApi?: RegisterComponentApiFn; 12 | }; 13 | 14 | export const defaultProps = { 15 | message: "Hello, World!", 16 | }; 17 | 18 | export const HelloWorld = React.forwardRef<HTMLDivElement, Props>( 19 | function HelloWorld( 20 | { 21 | id, 22 | message = defaultProps.message, 23 | className, 24 | onClick, 25 | onReset, 26 | registerComponentApi 27 | }, 28 | ref 29 | ) { 30 | const [clickCount, setClickCount] = useState(0); 31 | 32 | // Create setValue method for external API access 33 | const setValue = (newCount: number) => { 34 | setClickCount(newCount); 35 | }; 36 | 37 | // Register component API 38 | useEffect(() => { 39 | registerComponentApi?.({ 40 | setValue, 41 | value: clickCount, 42 | }); 43 | }, [registerComponentApi, setValue, clickCount]); 44 | 45 | const handleClick = (event: React.MouseEvent) => { 46 | const newCount = clickCount + 1; 47 | setClickCount(newCount); 48 | onClick?.(event); 49 | }; 50 | 51 | const handleReset = (event: React.MouseEvent) => { 52 | setClickCount(0); 53 | onReset?.(event); 54 | }; 55 | 56 | return ( 57 | <div className={`${styles.container} ${className || ''}`} id={id} ref={ref}> 58 | <h2 className={styles.message}>{message}</h2> 59 | <button 60 | className={styles.button} 61 | onClick={handleClick} 62 | > 63 | Click me! 64 | </button> 65 | <div className={styles.counter}> 66 | Clicks: <span className={styles.count}>{clickCount}</span> 67 | </div> 68 | 69 | {clickCount > 0 && ( 70 | <button 71 | className={styles.button} 72 | onClick={handleReset} 73 | > 74 | Reset 75 | </button> 76 | )} 77 | </div> 78 | ); 79 | } 80 | ); 81 | ``` -------------------------------------------------------------------------------- /.github/workflows/deploy-blog-optimized.yml: -------------------------------------------------------------------------------- ```yaml 1 | name: Deploy blog (optimized) 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | permissions: 7 | contents: write 8 | pull-requests: write 9 | 10 | jobs: 11 | build-and-deploy: 12 | runs-on: ubuntu-latest 13 | env: 14 | NODE_OPTIONS: "--max-old-space-size=8192" 15 | TURBO_UI: "false" 16 | 17 | steps: 18 | - name: 1. Generate a token from the GitHub App 🤖 19 | id: generate_token 20 | uses: tibdex/github-app-token@v2 21 | with: 22 | app_id: ${{ secrets.APP_ID }} 23 | private_key: ${{ secrets.APP_PRIVATE_KEY }} 24 | - uses: actions/checkout@v4 25 | - uses: actions/setup-node@v4 26 | with: 27 | node-version: 22 28 | cache: "npm" 29 | registry-url: https://registry.npmjs.org/ 30 | 31 | - name: Cache for Turbo 32 | uses: rharkor/[email protected] 33 | 34 | - run: npm ci --prefer-offline 35 | 36 | - name: 4. Configure Git to use the App's token 🔑 37 | # The token generated in the first step is used here for authentication. 38 | run: git config --global url."https://x-access-token:${{ steps.generate_token.outputs.token }}@github.com/".insteadOf "https://github.com/" 39 | - name: 5. DEBUG - Attempt to clone the private repo 40 | # This step will give a clear error if authentication is the problem 41 | run: git clone https://github.com/xmlui-org/xmlui-optimizer.git 42 | - name: 5. Install optimizer's dependencies 43 | run: npm install 44 | working-directory: ./xmlui-optimizer 45 | - name: DEBUG - install xmlui-optimizer 46 | # This step will give a clear error if authentication is the problem 47 | run: npm install ./xmlui-optimizer 48 | 49 | - run: cd blog && npm run build-optimized 50 | env: 51 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 52 | 53 | - name: Deploy to Netlify 54 | uses: nwtgck/[email protected] 55 | with: 56 | publish-dir: ./blog/xmlui-optimized-output 57 | production-deploy: true 58 | env: 59 | NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_ACCESS_TOKEN }} 60 | NETLIFY_SITE_ID: ${{ secrets.XMLUI_BLOG_NETLIFY_SITE_ID }} 61 | ``` -------------------------------------------------------------------------------- /docs/public/pages/howto/filter-and-transform-data-from-an-api.md: -------------------------------------------------------------------------------- ```markdown 1 | # Filter and transform data from an API 2 | 3 | ```xmlui-pg noHeader 4 | ---app 5 | <App> 6 | <Test /> 7 | </App> 8 | ---api display 9 | { 10 | "apiUrl": "/api", 11 | "initialize": "$state.people = [ 12 | { id: 1, name: 'Alice', active: true, group: 'A' }, 13 | { id: 2, name: 'Bob', active: false, group: 'B' }, 14 | { id: 3, name: 'Carol', active: true, group: 'A' }, 15 | { id: 4, name: 'Dave', active: true, group: 'B' } 16 | ]", 17 | "operations": { 18 | "get-people": { 19 | "url": "/people", 20 | "method": "get", 21 | "handler": "return { status: 'ok', data: { items: $state.people } }" 22 | } 23 | } 24 | } 25 | ---comp display 26 | <Component name="Test"> 27 | 28 | <!-- 29 | { 30 | items: 31 | [ 32 | { id: 1, name: 'Alice', active: true, group: 'A' }, 33 | { id: 2, name: 'Bob', active: false, group: 'B' }, 34 | { id: 3, name: 'Carol', active: true, group: 'A' }, 35 | { id: 4, name: 'Dave', active: true, group: 'B' } 36 | ] 37 | } 38 | --> 39 | 40 | <!-- Use resultSelector to select the items array --> 41 | <DataSource 42 | id="allPeople" 43 | url="/api/people" 44 | resultSelector="data.items" 45 | /> 46 | 47 | <!-- Use resultSelector to filter the items array --> 48 | <DataSource 49 | id="activePeople" 50 | url="/api/people" 51 | resultSelector="data.items.filter(p => p.active)" 52 | /> 53 | 54 | <!-- Use transformResult --> 55 | 56 | <script> 57 | function transformPeople (data) { 58 | console.log(data); 59 | const items = data.data.items; 60 | const itemMap = { 61 | A: 'Austin', 62 | B: 'Boston' 63 | }; 64 | return items.map(item => ({ 65 | ...item, 66 | city: itemMap[item.group] 67 | })); 68 | }; 69 | 70 | </script> 71 | 72 | <DataSource 73 | id="transformedPeople" 74 | url="/api/people" 75 | transformResult="{transformPeople}" 76 | /> 77 | 78 | <Text>All people:</Text> 79 | <List data="{allPeople}"> 80 | <Text>{$item.name} ({$item.group})</Text> 81 | </List> 82 | 83 | <Text>Active people:</Text> 84 | <List data="{activePeople}"> 85 | <Text>{$item.name} ({$item.group})</Text> 86 | </List> 87 | 88 | <Text>Transformed people:</Text> 89 | <List data="{transformedPeople}"> 90 | <Text>{$item.name} ({$item.city})</Text> 91 | </List> 92 | 93 | 94 | </Component> 95 | ``` 96 | ``` -------------------------------------------------------------------------------- /xmlui/src/components/Image/Image.md: -------------------------------------------------------------------------------- ```markdown 1 | %-PROP-START alt 2 | 3 | This is useful in two cases: 4 | 1. Accessibility: screen readers read the prop value to users so they know what the image is about. 5 | 2. The text is also displayed when the image can't be loaded for some reason (network errors, content blocking, etc.). 6 | 7 | ```xmlui-pg copy display name="Example: alt" 8 | <App> 9 | <Image 10 | src="cantFindIt.jpg" 11 | alt="This image depicts a wonderful scene not for human eyes" /> 12 | </App> 13 | ``` 14 | 15 | %-PROP-END 16 | 17 | %-PROP-START fit 18 | 19 | | Name | Value | 20 | | --------- | ----- | 21 | | `contain` | The replaced content is scaled to maintain its aspect ratio while fitting within the image's container. The entire image is made to fill the container. | 22 | | `cover` | The image is sized to maintain its aspect ratio while filling the element's entire content box. If the image's aspect ratio does not match the aspect ratio of its container, then the image will be clipped to fit. | 23 | 24 | ```xmlui-pg copy display name="Example: fit" {5,9} 25 | <App> 26 | <HStack padding="1rem" height="280px" gap="1rem"> 27 | <Image 28 | src="/resources/images/components/image/breakfast.jpg" 29 | fit="contain" 30 | width="240px" /> 31 | <Image 32 | src="/resources/images/components/image/breakfast.jpg" 33 | fit="cover" 34 | width="240px" /> 35 | </HStack> 36 | </App> 37 | ``` 38 | 39 | %-PROP-END 40 | 41 | %-PROP-START lazyLoad 42 | 43 | Lazy loading instructs the browser to load the image only when it is imminently needed (e.g. user scrolls to it). 44 | The default value is eager (\`false\`). 45 | 46 | %-PROP-END 47 | 48 | %-PROP-START aspectRatio 49 | 50 | ```xmlui-pg copy display name="Example: aspectRatio" 51 | <App> 52 | <Image 53 | src="/resources/images/components/image/breakfast.jpg" 54 | aspectRatio="200 / 150" /> 55 | </App> 56 | ``` 57 | 58 | %-PROP-END 59 | 60 | %-EVENT-START click 61 | 62 | This event is triggered when the image is clicked. 63 | 64 | ```xmlui-pg copy {6} display name="Example: click" 65 | <App> 66 | <Stack height="280px" width="400px"> 67 | <Image 68 | src="/resources/images/components/image/breakfast.jpg" 69 | fit="cover" 70 | onClick="toast('Image clicked')" 71 | /> 72 | </Stack> 73 | </App> 74 | ``` 75 | 76 | %-EVENT-END 77 | ``` -------------------------------------------------------------------------------- /xmlui/tests/components-core/scripts-runner/process-event.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { describe, expect, it, assert } from "vitest"; 2 | 3 | import { processStatementQueueAsync } from "../../../src/components-core/script-runner/process-statement-async"; 4 | import { createEvalContext, parseStatements } from "./test-helpers"; 5 | import { ArrowExpressionStatement, T_ARROW_EXPRESSION, T_ARROW_EXPRESSION_STATEMENT, T_EXPRESSION_STATEMENT } from "../../../src/components-core/script-runner/ScriptingSourceTree"; 6 | 7 | describe("Process statements (exp)", () => { 8 | it("Event with arrow function", async () => { 9 | // --- Arrange 10 | const source = "(x, y) => 2 * x + y"; 11 | const evalContext = createEvalContext({ 12 | localContext: {}, 13 | eventArgs: [123, 1] 14 | }); 15 | const statements = parseStatements(source); 16 | if ( 17 | statements?.length !== 1 || 18 | statements[0].type !== T_EXPRESSION_STATEMENT || 19 | statements[0].expr.type !== T_ARROW_EXPRESSION 20 | ) { 21 | assert.fail("Arrow expression expected"); 22 | } 23 | 24 | // --- Act 25 | const arrowStmt = { 26 | type: T_ARROW_EXPRESSION_STATEMENT, 27 | expr: statements[0].expr 28 | } as ArrowExpressionStatement; 29 | const diag = await processStatementQueueAsync([arrowStmt], evalContext); 30 | 31 | // --- Assert 32 | const thread = evalContext.mainThread; 33 | expect(thread!.blocks!.length).equal(1); 34 | expect(thread!.blocks![0].returnValue).equal(247); 35 | 36 | expect(diag.processedStatements).equal(1); 37 | expect(diag.maxLoops).equal(0); 38 | expect(diag.maxBlocks).equal(1); 39 | expect(diag.maxQueueLength).equal(1); 40 | expect(diag.clearToLabels).equal(0); 41 | expect(diag.unshiftedItems).equal(0); 42 | }); 43 | 44 | it("Event issue", async () => { 45 | // --- Arrange 46 | const source = "(() => {let z = 0; while(z < 3) {console.log(z); z++}})()"; 47 | const evalContext = createEvalContext({ 48 | localContext: {}, 49 | eventArgs: [123, 1] 50 | }); 51 | const statements = parseStatements(source); 52 | await processStatementQueueAsync(statements, evalContext); 53 | 54 | // --- Assert 55 | expect(evalContext.mainThread!.blocks![0].returnValue).equal(undefined); 56 | }); 57 | }); 58 | ``` -------------------------------------------------------------------------------- /xmlui/src/components/ContentSeparator/ContentSeparator.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import styles from "./ContentSeparator.module.scss"; 2 | 3 | import { createComponentRenderer } from "../../components-core/renderers"; 4 | import { parseScssVar } from "../../components-core/theming/themeVars"; 5 | import { orientationOptionMd } from "../abstractions"; 6 | import { ContentSeparator, defaultProps } from "./ContentSeparatorNative"; 7 | import { createMetadata } from "../metadata-helpers"; 8 | 9 | const COMP = "ContentSeparator"; 10 | 11 | export const ContentSeparatorMd = createMetadata({ 12 | status: "stable", 13 | description: 14 | "`ContentSeparator` creates visual dividers between content sections using " + 15 | "horizontal or vertical lines. It's essential for improving readability by " + 16 | "breaking up dense content, separating list items, or creating clear boundaries " + 17 | "between different UI sections.", 18 | props: { 19 | size: { 20 | description: 21 | "This property defines the component's height (if the \`orientation\` is horizontal) " + 22 | "or the width (if the \`orientation\` is vertical). " + 23 | "If not defined, the component uses the entire available width or height.", 24 | valueType: "any", 25 | }, 26 | orientation: { 27 | description: "Sets the main axis of the component", 28 | availableValues: orientationOptionMd, 29 | defaultValue: defaultProps.orientation, 30 | valueType: "string", 31 | }, 32 | }, 33 | themeVars: parseScssVar(styles.themeVars), 34 | defaultThemeVars: { 35 | [`backgroundColor-${COMP}`]: "$color-surface-200", 36 | [`size-${COMP}`]: "1px", 37 | [`marginVertical-${COMP}`]: "0", 38 | [`marginHorizontal-${COMP}`]: "0", 39 | light: { 40 | // --- No light-specific theme vars 41 | }, 42 | dark: { 43 | // --- No dark-specific theme vars 44 | }, 45 | }, 46 | }); 47 | 48 | export const contentSeparatorComponentRenderer = createComponentRenderer( 49 | COMP, 50 | ContentSeparatorMd, 51 | ({ node, className, extractValue }) => { 52 | return ( 53 | <ContentSeparator 54 | orientation={extractValue(node.props.orientation)} 55 | size={extractValue.asSize(node.props.size)} 56 | className={className} 57 | /> 58 | ); 59 | }, 60 | ); 61 | ``` -------------------------------------------------------------------------------- /xmlui/src/components/NestedApp/Tooltip.module.scss: -------------------------------------------------------------------------------- ```scss 1 | @use "../../components-core/theming/themes" as t; 2 | 3 | // --- This code snippet is required to collect the theme variables used in this module 4 | $themeVars: (); 5 | @function createThemeVar($componentVariable) { 6 | $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global; 7 | @return t.getThemeVar($themeVars, $componentVariable); 8 | } 9 | 10 | @layer components { 11 | .TooltipContent { 12 | border-radius: 4px; 13 | padding: 10px 15px; 14 | font-family: sans-serif; 15 | font-size: 15px; 16 | line-height: 1; 17 | background-color: #ffffff; 18 | box-shadow: 0px 10px 38px -10px rgba(22, 23, 24, 0.35), 0px 10px 20px -15px rgba(22, 23, 24, 0.2); 19 | user-select: none; 20 | animation-duration: 400ms; 21 | animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); 22 | will-change: transform, opacity; 23 | } 24 | .TooltipContent[data-state='delayed-open'][data-side='top'] { 25 | animation-name: slideDownAndFade; 26 | } 27 | .TooltipContent[data-state='delayed-open'][data-side='right'] { 28 | animation-name: slideLeftAndFade; 29 | } 30 | .TooltipContent[data-state='delayed-open'][data-side='bottom'] { 31 | animation-name: slideUpAndFade; 32 | } 33 | .TooltipContent[data-state='delayed-open'][data-side='left'] { 34 | animation-name: slideRightAndFade; 35 | } 36 | 37 | @keyframes slideUpAndFade { 38 | from { 39 | opacity: 0; 40 | transform: translateY(2px); 41 | } 42 | to { 43 | opacity: 1; 44 | transform: translateY(0); 45 | } 46 | } 47 | 48 | @keyframes slideRightAndFade { 49 | from { 50 | opacity: 0; 51 | transform: translateX(-2px); 52 | } 53 | to { 54 | opacity: 1; 55 | transform: translateX(0); 56 | } 57 | } 58 | 59 | @keyframes slideDownAndFade { 60 | from { 61 | opacity: 0; 62 | transform: translateY(-2px); 63 | } 64 | to { 65 | opacity: 1; 66 | transform: translateY(0); 67 | } 68 | } 69 | 70 | @keyframes slideLeftAndFade { 71 | from { 72 | opacity: 0; 73 | transform: translateX(2px); 74 | } 75 | to { 76 | opacity: 1; 77 | transform: translateX(0); 78 | } 79 | } 80 | } 81 | 82 | // --- We export the theme variables to add them to the component renderer 83 | :export { 84 | themeVars: t.json-stringify($themeVars); 85 | } 86 | ``` -------------------------------------------------------------------------------- /packages/xmlui-devtools/src/editor/Editor.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import { Editor as MonacoEditor, useMonaco } from "@monaco-editor/react"; 2 | import type { CSSProperties } from "react"; 3 | import { useEffect } from "react"; 4 | import { 5 | xmluiThemeLight, 6 | xmluiThemeDark, 7 | xmluiGrammar, 8 | xmluiScriptGrammar, 9 | } from "xmlui/syntax/monaco"; 10 | 11 | export type EditorProps = { 12 | readOnly?: boolean; 13 | language?: string; 14 | style?: CSSProperties; 15 | value?: string; 16 | saveViewState?: boolean; 17 | onChange?: any; 18 | onMount?: any; 19 | activeThemeTone?: string; 20 | }; 21 | 22 | export const Editor = ({ 23 | readOnly = true, 24 | language = "xmlui", 25 | value, 26 | onChange = () => {}, 27 | onMount = () => {}, 28 | saveViewState = false, 29 | activeThemeTone = "light", 30 | }: EditorProps) => { 31 | const monaco = useMonaco(); 32 | 33 | useEffect(() => { 34 | if (monaco) { 35 | //xmlui markup 36 | monaco.languages.register({ id: xmluiGrammar.id }); 37 | monaco.languages.setMonarchTokensProvider(xmluiGrammar.id, xmluiGrammar.language); 38 | monaco.languages.setLanguageConfiguration(xmluiGrammar.id, xmluiGrammar.config); 39 | monaco.editor.defineTheme("xmlui-light", xmluiThemeLight); 40 | monaco.editor.defineTheme("xmlui-dark", xmluiThemeDark); 41 | if (language === "xmlui") { 42 | monaco.editor.setTheme(activeThemeTone === "dark" ? "xmlui-dark" : "xmlui-light"); 43 | } 44 | //xmluiscript 45 | monaco.languages.register({ id: xmluiScriptGrammar.id }); 46 | monaco.languages.setMonarchTokensProvider(xmluiScriptGrammar.id, xmluiScriptGrammar.language); 47 | monaco.languages.setLanguageConfiguration(xmluiScriptGrammar.id, xmluiScriptGrammar.config); 48 | } 49 | }, [monaco, language, activeThemeTone]); 50 | 51 | return ( 52 | <MonacoEditor 53 | saveViewState={saveViewState} 54 | onChange={onChange} 55 | onMount={onMount} 56 | key={"devtools"} 57 | options={{ 58 | readOnly: readOnly, 59 | scrollBeyondLastLine: false, 60 | minimap: { enabled: false }, 61 | overviewRulerLanes: 0, 62 | hideCursorInOverviewRuler: true, 63 | stickyScroll: { enabled: false }, 64 | }} 65 | language={language} 66 | value={value} 67 | /> 68 | ); 69 | }; 70 | ```