This is page 137 of 179. Use http://codebase.md/xmlui-org/xmlui/xmlui-standalone.umd.js?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .changeset │ ├── cold-items-taste.md │ ├── config.json │ ├── empty-spiders-dress.md │ ├── shy-windows-allow.md │ ├── sour-coins-read.md │ ├── tame-zebras-invite.md │ ├── three-ideas-invent.md │ ├── twenty-jeans-watch.md │ ├── warm-spies-melt.md │ └── whole-ways-cry.md ├── .eslintrc.cjs ├── .github │ ├── build-checklist.png │ ├── ISSUE_TEMPLATE │ │ ├── bug_report.md │ │ └── feature_request.md │ └── workflows │ ├── 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 │ ├── actions.md │ ├── AppRoot.md │ ├── component-apis.md │ ├── component-rendering.md │ ├── component-review-checklist.md │ ├── containers.md │ ├── data-sources.md │ ├── e2e-summary.md │ ├── expression-evaluation.md │ ├── glossary.md │ ├── helper-components.md │ ├── index.md │ ├── loaders.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 │ ├── rendering-fundamentals.md │ ├── reusable-components.md │ ├── standalone-apps.md │ ├── state-management.md │ └── xmlui-extensibility.xlsx ├── 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 -------------------------------------------------------------------------------- /docs/public/pages/build-editor-component.md: -------------------------------------------------------------------------------- ```markdown 1 | # Build a TableEditor Component 2 | 3 | 4 | This guide walks you through building a `TableEditor` component for XMLUI, using the [Tiptap](https://tiptap.dev) editor as a foundation. It will provide a visual and Markdown-friendly way to create and edit tables for use in XMLUI-powered documentation. 5 | 6 | > [!INFO] 7 | > If you operate in the [XMLUI](https://github.com/xmlui-org/xmlui) repo you can test your work live. Follow the instructions in `dev-docs/next/generating-component-reference.md` to build the XMLUI docs site, then load localhost:5173. When you edit `.tsx` files they will automatically recompile, so you can iterate rapidly as you develop your component. And you can add a test page to the site in order to use your evolving component 8 | 9 | 10 | ## Step 1: Create the folder. 11 | 12 | ```bash 13 | mkdir -p xmlui/src/components/TableEditor 14 | ``` 15 | 16 | ## Step 2: Create a minimal renderer 17 | 18 | Add `TableEditor.tsx` in that folder. 19 | 20 | ```xmlui 21 | export function TableEditor() { 22 | return ( 23 | <table> 24 | <thead> 25 | <tr> 26 | <th>Fruit</th> 27 | <th>Color</th> 28 | </tr> 29 | </thead> 30 | <tbody> 31 | <tr> 32 | <td>Apple</td> 33 | <td>Red</td> 34 | </tr> 35 | <tr> 36 | <td>Banana</td> 37 | <td>Yellow</td> 38 | </tr> 39 | </tbody> 40 | </table> 41 | ); 42 | } 43 | 44 | export const editorComponentRenderer = { 45 | type: "TableEditor", 46 | renderer: (props: any) => <TableEditor {...props} /> 47 | }; 48 | ``` 49 | 50 | Register it in `ComponentProvider.tsx`. 51 | 52 | ```xmlui 53 | import { editorComponentRenderer as TableEditorRenderer } 54 | from "./components/TableEditor/TableEditor"; 55 | 56 | if (process.env.VITE_USED_COMPONENTS_TableEditor !== "false") { 57 | this.registerCoreComponent(TableEditorRenderer); 58 | } 59 | ``` 60 | 61 | You can now use this XMLUI markup. 62 | 63 | ```xmlui 64 | <App> 65 | <TableEditor /> 66 | </App> 67 | ``` 68 | 69 |  70 | 71 | We are just rendering an HTML table at this point, with no XMLUI theme support and no editing capability. 72 | 73 | ## Step 3: Make the table editable with Tiptap 74 | 75 | Install Tiptap dependencies. 76 | 77 | ``` 78 | npm install @tiptap/react @tiptap/starter-kit \ 79 | @tiptap/extension-table @tiptap/extension-table-row \ 80 | @tiptap/extension-table-cell @tiptap/extension-table-header 81 | ``` 82 | 83 | Update `TableEditor.tsx`. 84 | 85 | ```js filename="TableEditor.tsx" copy 86 | import { useEditor, EditorContent } from "@tiptap/react"; 87 | import StarterKit from "@tiptap/starter-kit"; 88 | import Table from "@tiptap/extension-table"; 89 | import TableRow from "@tiptap/extension-table-row"; 90 | import TableCell from "@tiptap/extension-table-cell"; 91 | import TableHeader from "@tiptap/extension-table-header"; 92 | 93 | export function TableEditor() { 94 | const editor = useEditor({ 95 | extensions: [ 96 | StarterKit, 97 | Table.configure({ resizable: true }), 98 | TableRow, 99 | TableHeader, 100 | TableCell, 101 | ], 102 | content: ` 103 | <table> 104 | <thead> 105 | <tr> 106 | <th>Fruit</th> 107 | <th>Color</th> 108 | </tr> 109 | </thead> 110 | <tbody> 111 | <tr> 112 | <td>Apple</td> 113 | <td>Red</td> 114 | </tr> 115 | <tr> 116 | <td>Banana</td> 117 | <td>Yellow</td> 118 | </tr> 119 | </tbody> 120 | </table> 121 | `, 122 | }); 123 | 124 | return <EditorContent editor={editor} />; 125 | } 126 | 127 | export const editorComponentRenderer = { 128 | type: "TableEditor", 129 | renderer: (props: any) => <TableEditor {...props} /> 130 | }; 131 | ``` 132 | 133 | Use the same XMLUI markup. 134 | 135 | ```xmlui 136 | <App> 137 | <TableEditor /> 138 | </App> 139 | ``` 140 | 141 | You can now edit the cells in the table. 142 | 143 | 144 |  145 | 146 | 147 | ## Step 4: Add an Insert Row button 148 | 149 | ```js {41-46} copy filename="TableEditor.tsx" 150 | import { useEditor, EditorContent } from "@tiptap/react"; 151 | import StarterKit from "@tiptap/starter-kit"; 152 | import Table from "@tiptap/extension-table"; 153 | import TableRow from "@tiptap/extension-table-row"; 154 | import TableCell from "@tiptap/extension-table-cell"; 155 | import TableHeader from "@tiptap/extension-table-header"; 156 | 157 | export function TableEditor() { 158 | const editor = useEditor({ 159 | extensions: [ 160 | StarterKit, 161 | Table.configure({ resizable: true }), 162 | TableRow, 163 | TableHeader, 164 | TableCell, 165 | ], 166 | content: ` 167 | <table> 168 | <thead> 169 | <tr> 170 | <th>Fruit</th> 171 | <th>Color</th> 172 | </tr> 173 | </thead> 174 | <tbody> 175 | <tr> 176 | <td>Apple</td> 177 | <td>Red</td> 178 | </tr> 179 | <tr> 180 | <td>Banana</td> 181 | <td>Yellow</td> 182 | </tr> 183 | </tbody> 184 | </table> 185 | `, 186 | }); 187 | 188 | return ( 189 | <div> 190 | <button 191 | onClick={() => editor && editor.commands.addRowAfter()} 192 | disabled={!editor} 193 | > 194 | Insert Row 195 | </button> 196 | <EditorContent editor={editor} /> 197 | </div> 198 | ); 199 | } 200 | 201 | export const editorComponentRenderer = { 202 | type: "TableEditor", 203 | renderer: (props: any) => <TableEditor {...props} /> 204 | }; 205 | ``` 206 | 207 | Use the same XMLUI markup. 208 | 209 | ```xmlui 210 | <App> 211 | <TableEditor /> 212 | </App> 213 | ``` 214 | 215 | You can now insert rows. 216 | 217 |  218 | 219 | ## Step 5: Use XMLUI's Button 220 | 221 | Import and use `Button`. 222 | 223 | ```js /Button/ copy filename="TableEditor.tsx" 224 | import { useEditor, EditorContent } from "@tiptap/react"; 225 | import StarterKit from "@tiptap/starter-kit"; 226 | import Table from "@tiptap/extension-table"; 227 | import TableRow from "@tiptap/extension-table-row"; 228 | import TableCell from "@tiptap/extension-table-cell"; 229 | import TableHeader from "@tiptap/extension-table-header"; 230 | import { Button } from "../Button/ButtonNative"; 231 | 232 | export function TableEditor() { 233 | const editor = useEditor({ 234 | extensions: [ 235 | StarterKit, 236 | Table.configure({ resizable: true }), 237 | TableRow, 238 | TableHeader, 239 | TableCell, 240 | ], 241 | content: ` 242 | <table> 243 | <thead> 244 | <tr> 245 | <th>Fruit</th> 246 | <th>Color</th> 247 | </tr> 248 | </thead> 249 | <tbody> 250 | <tr> 251 | <td>Apple</td> 252 | <td>Red</td> 253 | </tr> 254 | <tr> 255 | <td>Banana</td> 256 | <td>Yellow</td> 257 | </tr> 258 | </tbody> 259 | </table> 260 | `, 261 | }); 262 | 263 | return ( 264 | <div> 265 | <Button 266 | onClick={() => editor && editor.commands.addRowAfter()} 267 | disabled={!editor} 268 | > 269 | Insert Row 270 | </Button> 271 | <EditorContent editor={editor} /> 272 | </div> 273 | ); 274 | } 275 | 276 | export const editorComponentRenderer = { 277 | type: "TableEditor", 278 | renderer: (props: any) => <TableEditor {...props} /> 279 | }; 280 | ``` 281 | 282 | You now have a proper themed XMLUI button. 283 | 284 |  285 | 286 | ## Step 6: Show the HTML 287 | 288 | Tiptap works natively with HTML, not Markdown. We'll eventually show Markdown but first let's add a method to show the HTML. 289 | 290 | To keep our code modular, we'll separate the editor's rendering logic into a minimal presentational component. This makes it easy to reuse the editor UI in different contexts. 291 | 292 | Create `TableEditorNative.tsx` alongside `TableEditor.tsx`. 293 | 294 | ```js filename="TableEditorNative.tsx" copy 295 | import { EditorContent } from "@tiptap/react"; 296 | 297 | export function TableEditorNative({ editor }: { editor: any }) { 298 | return <EditorContent editor={editor} />; 299 | } 300 | ``` 301 | 302 | This component simply renders the Tiptap editor UI for a given editor instance. 303 | 304 | Now, let's expose a method to get the current HTML from the editor. Add this to `TableEditor.tsx`. 305 | 306 | ```js filename="TableEditorNative.tsx" copy 307 | React.useEffect(() => { 308 | if (registerComponentApi && editor) { 309 | registerComponentApi({ 310 | getHtmlSource: () => editor.getHTML(), 311 | }); 312 | } 313 | }, [registerComponentApi, editor]); 314 | ``` 315 | 316 | Now we can show the HTML using this markup. 317 | 318 | ```xmlui 319 | <App> 320 | 321 | <TableEditor id="tableEditor" /> 322 | 323 | <Text> { tableEditor.getHtmlSource() } </Text> 324 | 325 | </App> 326 | ``` 327 | 328 |  329 | 330 | 331 | ## Step 7: Show the Markdown 332 | 333 | Tiptap does not provide an HTML-to-Markdown converter. We'll start with a basic one called `turndown`. First install it. 334 | 335 | ``` 336 | npm install turndown 337 | ``` 338 | 339 | Then update `TableEditor.tsx` to use it. 340 | 341 | ```js /turndown/ {42-60} filename="TableEditor.tsx" 342 | import React from "react"; 343 | import { useEditor, EditorContent } from "@tiptap/react"; 344 | import StarterKit from "@tiptap/starter-kit"; 345 | import Table from "@tiptap/extension-table"; 346 | import TableRow from "@tiptap/extension-table-row"; 347 | import TableCell from "@tiptap/extension-table-cell"; 348 | import TableHeader from "@tiptap/extension-table-header"; 349 | import { Button } from "../Button/ButtonNative"; 350 | import TurndownService from "turndown"; 351 | 352 | export function TableEditor({ registerComponentApi }: { registerComponentApi?: (api: any) => void }) { 353 | const editor = useEditor({ 354 | extensions: [ 355 | StarterKit, 356 | Table.configure({ resizable: true }), 357 | TableRow, 358 | TableHeader, 359 | TableCell, 360 | ], 361 | content: ` 362 | <table> 363 | <thead> 364 | <tr> 365 | <th>Fruit</th> 366 | <th>Color</th> 367 | </tr> 368 | </thead> 369 | <tbody> 370 | <tr> 371 | <td>Apple</td> 372 | <td>Red</td> 373 | </tr> 374 | <tr> 375 | <td>Banana</td> 376 | <td>Yellow</td> 377 | </tr> 378 | </tbody> 379 | </table> 380 | `, 381 | }); 382 | 383 | React.useEffect(() => { 384 | if (registerComponentApi && editor) { 385 | const turndownService = new TurndownService(); 386 | turndownService.addRule('table', { 387 | filter: 'table', 388 | replacement: function (content, node) { 389 | let rows = []; 390 | for (let row of node.querySelectorAll('tr')) { 391 | let cells = Array.from(row.children).map(cell => cell.textContent.trim()); 392 | rows.push('| ' + cells.join(' | ') + ' |'); 393 | } 394 | if (rows.length > 1) { 395 | // Add a separator after the header row 396 | const headerSep = '| ' + rows[0].split('|').slice(1, -1).map(() => '---').join(' | ') + ' |'; 397 | rows.splice(1, 0, headerSep); 398 | } 399 | return rows.join('\n') + '\n'; 400 | } 401 | }); 402 | registerComponentApi({ 403 | getHtmlSource: () => editor.getHTML(), 404 | getMarkdownSource: () => turndownService.turndown(editor.getHTML()), 405 | }); 406 | } 407 | }, [registerComponentApi, editor]); 408 | 409 | return ( 410 | <div> 411 | <Button 412 | onClick={() => editor && editor.commands.addRowAfter()} 413 | disabled={!editor} 414 | > 415 | Insert Row 416 | </Button> 417 | <EditorContent editor={editor} /> 418 | </div> 419 | ); 420 | } 421 | 422 | export const editorComponentRenderer2 = { 423 | type: "TableEditor", 424 | renderer: ({ registerComponentApi, ...props }: any) => ( 425 | <TableEditor {...props} registerComponentApi={registerComponentApi} /> 426 | ), 427 | }; 428 | ``` 429 | 430 | Now we can show the Markdown. 431 | 432 | ```xmlui 433 | <App> 434 | 435 | <TableEditor id="tableEditor" /> 436 | 437 | <Text variant="codefence" preserveLinebreaks="{true}"> 438 | { tableEditor.getMarkdownSource() } 439 | </Text> 440 | 441 | </App> 442 | ``` 443 | 444 |  445 | 446 | Rendered in XMLUI Markdown: 447 | 448 | <Image src="/resources/devdocs/xmlui-rendering-of-tiptap-markdown.png" width="200px"/> 449 | 450 | 451 | ## Step 8: Add controls 452 | 453 | We can improve the TableEditor by adding more table editing controls like Insert Column, Delete Row, and Delete Column. But where should these controls live? 454 | 455 | 1. **TableEditorNative.tsx** (lowest level) 456 | - Pros: Closest to the editor instance 457 | - Cons: Not idiomatic for XMLUI; the "native" component should be minimal 458 | 2. **TableEditor.tsx** (middle level) 459 | - Pros: Provides a good default "batteries included" experience; keeps the native component minimal; still allows advanced users to build custom controls if needed 460 | - Cons: Slightly less flexible than fully user-defined controls 461 | 3. **User-defined component** (highest level) 462 | - Pros: Maximum flexibility for users 463 | - Cons: Every user has to reimplement the same basic controls; not user-friendly 464 | 465 | We chose to implement the controls in `TableEditor.tsx` because it provides the best balance of usability and flexibility. Users get a working table editor with sensible controls out of the box, while advanced users can still build custom UIs using the exposed API if needed. 466 | 467 | ```js {71-87} filename="TableEditorNative.tsx" 468 | import React from "react"; 469 | import { useEditor } from "@tiptap/react"; 470 | import StarterKit from "@tiptap/starter-kit"; 471 | import Table from "@tiptap/extension-table"; 472 | import TableRow from "@tiptap/extension-table-row"; 473 | import TableCell from "@tiptap/extension-table-cell"; 474 | import TableHeader from "@tiptap/extension-table-header"; 475 | import { Button } from "../Button/ButtonNative"; 476 | import { Stack } from "../Stack/StackNative"; 477 | import TurndownService from "turndown"; 478 | import { TableEditorNative } from "./TableEditorNative"; 479 | 480 | export function TableEditor({ registerComponentApi }: { registerComponentApi?: (api: any) => void }) { 481 | const editor = useEditor({ 482 | extensions: [ 483 | StarterKit, 484 | Table.configure({ resizable: true }), 485 | TableRow, 486 | TableHeader, 487 | TableCell, 488 | ], 489 | content: ` 490 | <table> 491 | <thead> 492 | <tr> 493 | <th>Fruit</th> 494 | <th>Color</th> 495 | </tr> 496 | </thead> 497 | <tbody> 498 | <tr> 499 | <td>Apple</td> 500 | <td>Red</td> 501 | </tr> 502 | <tr> 503 | <td>Banana</td> 504 | <td>Yellow</td> 505 | </tr> 506 | </tbody> 507 | </table> 508 | `, 509 | }); 510 | 511 | React.useEffect(() => { 512 | if (registerComponentApi && editor) { 513 | const turndownService = new TurndownService(); 514 | turndownService.addRule('table', { 515 | filter: 'table', 516 | replacement: function (content, node) { 517 | let rows = []; 518 | for (let row of node.querySelectorAll('tr')) { 519 | let cells = Array.from(row.children).map(cell => (cell as HTMLElement).textContent?.trim() ?? ""); 520 | rows.push('| ' + cells.join(' | ') + ' |'); 521 | } 522 | if (rows.length > 1) { 523 | // Add a separator after the header row 524 | const headerSep = '| ' + rows[0].split('|').slice(1, -1).map(() => '---').join(' | ') + ' |'; 525 | rows.splice(1, 0, headerSep); 526 | } 527 | return rows.join('\n') + '\n'; 528 | } 529 | }); 530 | registerComponentApi({ 531 | getHtmlSource: () => editor.getHTML(), 532 | getMarkdownSource: () => turndownService.turndown(editor.getHTML()), 533 | }); 534 | } 535 | }, [registerComponentApi, editor]); 536 | 537 | return ( 538 | <> 539 | <Stack orientation="horizontal"> 540 | <Button onClick={() => editor && editor.commands.addRowAfter()} disabled={!editor}> 541 | Insert Row 542 | </Button> 543 | <Button onClick={() => editor && editor.commands.deleteRow()} disabled={!editor}> 544 | Delete Row 545 | </Button> 546 | <Button onClick={() => editor && editor.commands.addColumnAfter()} disabled={!editor}> 547 | Insert Column 548 | </Button> 549 | <Button onClick={() => editor && editor.commands.deleteColumn()} disabled={!editor}> 550 | Delete Column 551 | </Button> 552 | </Stack> 553 | <TableEditorNative editor={editor} /> 554 | </> 555 | ); 556 | } 557 | 558 | export const editorComponentRenderer = { 559 | type: "TableEditor", 560 | renderer: ({ registerComponentApi, ...props }: any) => ( 561 | <TableEditor {...props} registerComponentApi={registerComponentApi} /> 562 | ), 563 | }; 564 | ``` 565 | 566 | We import and use `Stack` from `../Stack/StackNative`. Note that `HStack` is not available as a native component, so we can't use that shortcut. 567 | 568 | A component must return a single element. We use a React Fragment (`<>...</>`) to group the buttons into an anonymous container. 569 | 570 | Now we have this result. 571 | 572 | ```xmlui 573 | <App> 574 | 575 | <TableEditor id="tableEditor" /> 576 | 577 | <Text variant="codefence" preserveLinebreaks="{true}"> 578 | { tableEditor.getMarkdownSource() } 579 | </Text> 580 | 581 | </App> 582 | ``` 583 | 584 |  585 | 586 | ## Step 9: Add custom icons 587 | 588 | The table editor buttons currently use only text labels. Let's add custom SVG icons. 589 | 590 | ```js copy 591 | <Button 592 | onClick={() => editor && editor.commands.addRowAfter()} 593 | disabled={!editor} 594 | themeColor={themeColor} 595 | variant={variant} 596 | size={size} 597 | orientation="horizontal" 598 | > 599 | <svg 600 | viewBox="0 0 24 16" 601 | stroke="currentColor" 602 | fill="none" 603 | strokeWidth="1.5" 604 | strokeLinecap="round" 605 | strokeLinejoin="round" 606 | > 607 | <rect x="1.5" y="1.5" width="13" height="11" rx="1" /> 608 | <line x1="1.5" y1="5.5" x2="14.5" y2="5.5" /> 609 | <line x1="1.5" y1="9.5" x2="14.5" y2="9.5" /> 610 | <line x1="19" y1="6" x2="19" y2="10" /> 611 | <line x1="17" y1="8" x2="21" y2="8" /> 612 | </svg> 613 | Insert Row 614 | </Button> 615 | ``` 616 | 617 | We'll repeat this pattern for the other buttons. 618 | 619 | <Image src="/resources/devdocs/table-editor-07.png" width="600px"/> 620 | 621 | ## Step 10: Theme the buttons 622 | 623 | Our TableEditor component currently uses hardcoded button styling. To make TableEditor behave like a proper XMLUI component, we need to add theme support. 624 | 625 | Try adding different theme props to your TableEditor: 626 | 627 | ```xmlui 628 | <TableEditor themeColor="secondary" variant="outlined" size="lg" /> 629 | ``` 630 | 631 | The buttons remain blue and solid. This happens because XMLUI doesn't know which props are valid for custom components. Without proper metadata, theme attributes are filtered out. 632 | 633 | XMLUI components need metadata to define their allowed props. 634 | 635 | ```js {12-15} /TableEditorMd/ filename="TableEditorNative.tsx" 636 | import React from "react"; 637 | import { useEditor } from "@tiptap/react"; 638 | import StarterKit from "@tiptap/starter-kit"; 639 | import Table from "@tiptap/extension-table"; 640 | import TableRow from "@tiptap/extension-table-row"; 641 | import TableCell from "@tiptap/extension-table-cell"; 642 | import TableHeader from "@tiptap/extension-table-header"; 643 | import { Button } from "../Button/ButtonNative"; 644 | import { Stack } from "../Stack/StackNative"; 645 | import TurndownService from "turndown"; 646 | import { TableEditorNative } from "./TableEditorNative"; 647 | import { createMetadata } from "../metadata-helpers"; 648 | import { createComponentRenderer } from "../../components-core/renderers"; 649 | import { buttonThemeMd, buttonVariantMd, sizeMd } from "../abstractions"; 650 | 651 | export function TableEditor({ 652 | registerComponentApi, 653 | themeColor = "primary", 654 | variant = "solid", 655 | size = "sm", 656 | }: { 657 | registerComponentApi?: (api: any) => void; 658 | themeColor?: "primary" | "secondary" | "attention"; 659 | variant?: "solid" | "outlined" | "ghost"; 660 | size?: "xs" | "sm" | "md" | "lg"; 661 | }) { 662 | const editor = useEditor({ 663 | extensions: [ 664 | StarterKit, 665 | Table.configure({ resizable: true }), 666 | TableRow, 667 | TableHeader, 668 | TableCell, 669 | ], 670 | content: ` 671 | <table> 672 | <thead> 673 | <tr> 674 | <th>Fruit</th> 675 | <th>Color</th> 676 | </tr> 677 | </thead> 678 | <tbody> 679 | <tr> 680 | <td>Apple</td> 681 | <td>Red</td> 682 | </tr> 683 | <tr> 684 | <td>Banana</td> 685 | <td>Yellow</td> 686 | </tr> 687 | </tbody> 688 | </table> 689 | `, 690 | }); 691 | 692 | React.useEffect(() => { 693 | if (registerComponentApi && editor) { 694 | const turndownService = new TurndownService(); 695 | turndownService.addRule('table', { 696 | filter: 'table', 697 | replacement: function (content, node) { 698 | let rows = []; 699 | for (let row of node.querySelectorAll('tr')) { 700 | let cells = Array.from(row.children).map(cell => (cell as HTMLElement).textContent?.trim() ?? ""); 701 | rows.push('| ' + cells.join(' | ') + ' |'); 702 | } 703 | if (rows.length > 1) { 704 | // Add a separator after the header row 705 | const headerSep = '| ' + rows[0].split('|').slice(1, -1).map(() => '---').join(' | ') + ' |'; 706 | rows.splice(1, 0, headerSep); 707 | } 708 | return rows.join('\n') + '\n'; 709 | } 710 | }); 711 | registerComponentApi({ 712 | getHtmlSource: () => editor.getHTML(), 713 | getMarkdownSource: () => turndownService.turndown(editor.getHTML()), 714 | }); 715 | } 716 | }, [registerComponentApi, editor]); 717 | 718 | return ( 719 | <> 720 | <Stack orientation="horizontal"> 721 | <Button onClick={() => editor && editor.commands.addRowAfter()} disabled={!editor} themeColor={themeColor} variant={variant} size={size}> 722 | <svg 723 | viewBox="0 0 24 16" 724 | stroke="currentColor" 725 | fill="none" 726 | strokeWidth="1.5" 727 | strokeLinecap="round" 728 | strokeLinejoin="round" 729 | width="20" 730 | height="16" 731 | style={{ marginRight: 4 }} 732 | > 733 | <rect x="1.5" y="1.5" width="13" height="11" rx="1" /> 734 | <line x1="1.5" y1="5.5" x2="14.5" y2="5.5" /> 735 | <line x1="1.5" y1="9.5" x2="14.5" y2="9.5" /> 736 | <line x1="19" y1="6" x2="19" y2="10" /> 737 | <line x1="17" y1="8" x2="21" y2="8" /> 738 | </svg> 739 | Insert Row 740 | </Button> 741 | <Button onClick={() => editor && editor.commands.deleteRow()} disabled={!editor} themeColor={themeColor} variant={variant} size={size}> 742 | <svg 743 | viewBox="0 0 24 16" 744 | stroke="currentColor" 745 | fill="none" 746 | strokeWidth="1.5" 747 | strokeLinecap="round" 748 | strokeLinejoin="round" 749 | width="20" 750 | height="16" 751 | style={{ marginRight: 4 }} 752 | > 753 | <rect x="1.5" y="1.5" width="13" height="11" rx="1" /> 754 | <line x1="1.5" y1="5.5" x2="14.5" y2="5.5" /> 755 | <line x1="1.5" y1="9.5" x2="14.5" y2="9.5" /> 756 | <line x1="17" y1="8" x2="21" y2="8" /> 757 | </svg> 758 | Delete Row 759 | </Button> 760 | <Button onClick={() => editor && editor.commands.addColumnAfter()} disabled={!editor} themeColor={themeColor} variant={variant} size={size}> 761 | <svg 762 | viewBox="0 0 24 16" 763 | stroke="currentColor" 764 | fill="none" 765 | strokeWidth="1.5" 766 | strokeLinecap="round" 767 | strokeLinejoin="round" 768 | width="24" 769 | height="16" 770 | style={{ marginRight: 4 }} 771 | > 772 | <rect x="1.5" y="1.5" width="13" height="11" rx="1" /> 773 | <line x1="5.5" y1="3.5" x2="5.5" y2="11.5" /> 774 | <line x1="9" y1="3.5" x2="9" y2="11.5" /> 775 | <line x1="19" y1="6.5" x2="19" y2="9.5" /> 776 | <line x1="17.5" y1="8" x2="20.5" y2="8" /> 777 | </svg> 778 | Insert Column 779 | </Button> 780 | <Button onClick={() => editor && editor.commands.deleteColumn()} disabled={!editor} themeColor={themeColor} variant={variant} size={size}> 781 | <svg 782 | viewBox="0 0 24 16" 783 | stroke="currentColor" 784 | fill="none" 785 | strokeWidth="1.5" 786 | strokeLinecap="round" 787 | strokeLinejoin="round" 788 | width="24" 789 | height="16" 790 | style={{ marginRight: 4 }} 791 | > 792 | <rect x="1.5" y="1.5" width="13" height="11" rx="1" /> 793 | <line x1="5.5" y1="3.5" x2="5.5" y2="11.5" /> 794 | <line x1="9" y1="3.5" x2="9" y2="11.5" /> 795 | {/* Minus sign for delete */} 796 | <line x1="17" y1="8" x2="21" y2="8" /> 797 | </svg> 798 | Delete Column 799 | </Button> 800 | </Stack> 801 | <TableEditorNative editor={editor} /> 802 | </> 803 | ); 804 | } 805 | 806 | export const TableEditorMd = createMetadata({ 807 | description: 808 | "`TableEditor` provides an interactive table editing interface with controls for adding and deleting rows and columns. It supports theme customization and exports table data in HTML and Markdown formats.", 809 | status: "experimental", 810 | props: { 811 | themeColor: { 812 | description: "Sets the color scheme for all editor buttons.", 813 | isRequired: false, 814 | type: "string", 815 | availableValues: buttonThemeMd, 816 | defaultValue: "primary", 817 | }, 818 | variant: { 819 | description: "Sets the visual style for all editor buttons.", 820 | isRequired: false, 821 | type: "string", 822 | availableValues: buttonVariantMd, 823 | defaultValue: "solid", 824 | }, 825 | size: { 826 | description: "Sets the size for all editor buttons.", 827 | isRequired: false, 828 | type: "string", 829 | availableValues: sizeMd, 830 | defaultValue: "sm", 831 | }, 832 | }, 833 | events: {}, 834 | }); 835 | 836 | export const editorComponentRenderer = createComponentRenderer( 837 | "TableEditor", 838 | TableEditorMd, 839 | ({ node, extractValue, registerComponentApi }) => ( 840 | <TableEditor 841 | themeColor={extractValue.asOptionalString(node.props.themeColor)} 842 | variant={extractValue.asOptionalString(node.props.variant)} 843 | size={extractValue.asOptionalString(node.props.size)} 844 | registerComponentApi={registerComponentApi} 845 | /> 846 | ), 847 | ); 848 | ``` 849 | 850 | We add the necessary imports to `TableEditor.tsx`. 851 | 852 | ```js filename="TableEditor.tsx" copy 853 | import { createMetadata } from "../metadata-helpers"; 854 | import { createComponentRenderer } from "../../components-core/renderers"; 855 | import { buttonThemeMd, buttonVariantMd, sizeMd } from "../abstractions"; 856 | ``` 857 | 858 | We modify the TableEditor function to accept theme props. 859 | 860 | ```js filename="TableEditor.tsx" copy 861 | export function TableEditor({ 862 | registerComponentApi, 863 | themeColor = "primary", 864 | variant = "solid", 865 | size = "sm", 866 | }: { 867 | registerComponentApi?: (api: any) => void; 868 | themeColor?: "primary" | "secondary" | "attention"; 869 | variant?: "solid" | "outlined" | "ghost"; 870 | size?: "xs" | "sm" | "md" | "lg"; 871 | }) { 872 | // ... existing logic 873 | } 874 | ``` 875 | 876 | We forward these props to all Button components. 877 | 878 | ```js copy 879 | <Button 880 | onClick={() => editor && editor.commands.addRowAfter()} 881 | disabled={!editor} 882 | themeColor={themeColor} 883 | variant={variant} 884 | size={size} 885 | > 886 | Insert Row 887 | </Button> 888 | ``` 889 | 890 | We add metadata that defines the allowed props: 891 | 892 | ```js copy filename="TableEditor.tsx" 893 | export const TableEditorMd = createMetadata({ 894 | description: 895 | "`TableEditor` provides an interactive table editing interface with controls for adding and deleting rows and columns. It supports theme customization and exports table data in HTML and Markdown formats.", 896 | status: "stable", 897 | props: { 898 | themeColor: { 899 | description: "Sets the color scheme for all editor buttons.", 900 | isRequired: false, 901 | type: "string", 902 | availableValues: buttonThemeMd, 903 | defaultValue: "primary", 904 | }, 905 | variant: { 906 | description: "Sets the visual style for all editor buttons.", 907 | isRequired: false, 908 | type: "string", 909 | availableValues: buttonVariantMd, 910 | defaultValue: "solid", 911 | }, 912 | size: { 913 | description: "Sets the size for all editor buttons.", 914 | isRequired: false, 915 | type: "string", 916 | availableValues: sizeMd, 917 | defaultValue: "sm", 918 | }, 919 | }, 920 | events: {}, 921 | }); 922 | ``` 923 | 924 | We replace the simple renderer with a proper one that uses metadata: 925 | 926 | ```js filename="TableEditor.tsx" copy 927 | export const editorComponentRenderer = createComponentRenderer( 928 | "TableEditor", 929 | TableEditorMd, 930 | ({ node, extractValue, registerComponentApi }) => { 931 | return ( 932 | <TableEditor 933 | themeColor={extractValue.asOptionalString(node.props.themeColor)} 934 | variant={extractValue.asOptionalString(node.props.variant)} 935 | size={extractValue.asOptionalString(node.props.size)} 936 | registerComponentApi={registerComponentApi} 937 | /> 938 | ); 939 | }, 940 | ); 941 | ``` 942 | 943 | Now TableEditor supports full theme customization. 944 | 945 | ```xmlui copy 946 | <TableEditor 947 | id="tableEditor" 948 | /> 949 | 950 | <TableEditor 951 | id="tableEditor2" 952 | themeColor="primary" 953 | size="lg" 954 | variant="outlined" 955 | /> 956 | 957 | <TableEditor 958 | id="tableEditor3" 959 | themeColor="attention" 960 | variant="ghost" 961 | size="sm" 962 | /> 963 | ``` 964 | 965 | <Image src="/resources/devdocs/table_editor_08.png" /> 966 | 967 | ## Step 11: Register the icons 968 | 969 | > [!INFO] 970 | > Registering icons is optional if you only use them inline within your component, but it is strongly recommended for reusability, maintainability, and accessibility. 971 | 972 | **Global usage.** Registered icons can be referenced anywhere in your app or docs by name (e.g., `<Icon name="table-insert-row" />` or as a Button's `icon` prop). 973 | 974 | **Accessibility.** Using the `<Icon />` component or Button's `icon` prop helps ensure ARIA attributes and accessible labeling are handled consistently. 975 | 976 | To make your custom icon usable in XMLUI, wrap your SVG markup in a React component. This allows the icon to inherit color, size, and accessibility props from its parent. 977 | 978 | ```js copy 979 | // File: xmlui/src/components/Icon/TableInsertRowIcon.tsx 980 | import React from "react"; 981 | 982 | export default function TableInsertRowIcon(props) { 983 | return ( 984 | <svg 985 | viewBox="0 0 24 16" 986 | stroke="currentColor" 987 | fill="none" 988 | strokeWidth="1.5" 989 | strokeLinecap="round" 990 | strokeLinejoin="round" 991 | {...props} // Enables theming, sizing, and accessibility 992 | > 993 | <rect x="1.5" y="1.5" width="13" height="11" rx="1" /> 994 | <line x1="1.5" y1="5.5" x2="14.5" y2="5.5" /> 995 | <line x1="1.5" y1="9.5" x2="14.5" y2="9.5" /> 996 | <line x1="19" y1="6" x2="19" y2="10" /> 997 | <line x1="17" y1="8" x2="21" y2="8" /> 998 | </svg> 999 | ); 1000 | } 1001 | ``` 1002 | 1003 | > [!INFO] 1004 | > The `{...props}` spread is essential—it lets XMLUI pass color, size, and ARIA attributes to your icon automatically. 1005 | 1006 | Register the icon in `IconProvider.tsx`. 1007 | 1008 | ```js filename="IconProvider.tsx" 1009 | import TableInsertRowIcon from "./Icon/TableInsertRowIcon"; 1010 | 1011 | registerIconRenderer("table-insert-row", (props) => <TableInsertRowIcon {...props} />); 1012 | ``` 1013 | 1014 | Update `TableEditor.tsx`. 1015 | 1016 | ```js /<Icon/ filename="TableEditor.tsx" 1017 | import React from "react"; 1018 | import { useEditor } from "@tiptap/react"; 1019 | import StarterKit from "@tiptap/starter-kit"; 1020 | import Table from "@tiptap/extension-table"; 1021 | import TableRow from "@tiptap/extension-table-row"; 1022 | import TableCell from "@tiptap/extension-table-cell"; 1023 | import TableHeader from "@tiptap/extension-table-header"; 1024 | import { Button } from "../Button/ButtonNative"; 1025 | import { Stack } from "../Stack/StackNative"; 1026 | import TurndownService from "turndown"; 1027 | import { TableEditorNative2 } from "./TableEditorNative2"; 1028 | import { createMetadata } from "../metadata-helpers"; 1029 | import { createComponentRenderer } from "../../components-core/renderers"; 1030 | import { buttonThemeMd, buttonVariantMd, sizeMd } from "../abstractions"; 1031 | import Icon from "../Icon/IconNative"; 1032 | 1033 | export function TableEditor({ 1034 | registerComponentApi, 1035 | themeColor = "primary", 1036 | variant = "solid", 1037 | size = "sm", 1038 | }: { 1039 | registerComponentApi?: (api: any) => void; 1040 | themeColor?: "primary" | "secondary" | "attention"; 1041 | variant?: "solid" | "outlined" | "ghost"; 1042 | size?: "xs" | "sm" | "md" | "lg"; 1043 | }) { 1044 | const editor = useEditor({ 1045 | extensions: [ 1046 | StarterKit, 1047 | Table.configure({ resizable: true }), 1048 | TableRow, 1049 | TableHeader, 1050 | TableCell, 1051 | ], 1052 | content: ` 1053 | <table> 1054 | <thead> 1055 | <tr> 1056 | <th>Fruit</th> 1057 | <th>Color</th> 1058 | </tr> 1059 | </thead> 1060 | <tbody> 1061 | <tr> 1062 | <td>Apple</td> 1063 | <td>Red</td> 1064 | </tr> 1065 | <tr> 1066 | <td>Banana</td> 1067 | <td>Yellow</td> 1068 | </tr> 1069 | </tbody> 1070 | </table> 1071 | `, 1072 | }); 1073 | 1074 | React.useEffect(() => { 1075 | if (registerComponentApi && editor) { 1076 | const turndownService = new TurndownService(); 1077 | turndownService.addRule('table', { 1078 | filter: 'table', 1079 | replacement: function (content, node) { 1080 | let rows = []; 1081 | for (let row of node.querySelectorAll('tr')) { 1082 | let cells = Array.from(row.children).map(cell => (cell as HTMLElement).textContent?.trim() ?? ""); 1083 | rows.push('| ' + cells.join(' | ') + ' |'); 1084 | } 1085 | if (rows.length > 1) { 1086 | // Add a separator after the header row 1087 | const headerSep = '| ' + rows[0].split('|').slice(1, -1).map(() => '---').join(' | ') + ' |'; 1088 | rows.splice(1, 0, headerSep); 1089 | } 1090 | return rows.join('\n') + '\n'; 1091 | } 1092 | }); 1093 | registerComponentApi({ 1094 | getHtmlSource: () => editor.getHTML(), 1095 | getMarkdownSource: () => turndownService.turndown(editor.getHTML()), 1096 | }); 1097 | } 1098 | }, [registerComponentApi, editor]); 1099 | 1100 | return ( 1101 | <> 1102 | <Stack orientation="horizontal"> 1103 | <Button onClick={() => editor && editor.commands.addRowAfter()} disabled={!editor} 1104 | themeColor={themeColor} variant={variant} size={size} 1105 | icon={<Icon name="table-insert-row" aria-hidden />} contextualLabel="Insert Row" 1106 | > 1107 | Insert Row 1108 | </Button> 1109 | <Button onClick={() => editor && editor.commands.deleteRow()} disabled={!editor} 1110 | themeColor={themeColor} variant={variant} size={size} 1111 | icon={<Icon name="table-delete-row" aria-hidden />} contextualLabel="Delete Row"> 1112 | Delete Row 1113 | </Button> 1114 | <Button onClick={() => editor && editor.commands.addColumnAfter()} disabled={!editor} 1115 | themeColor={themeColor} variant={variant} size={size} 1116 | icon={<Icon name="table-insert-column" aria-hidden />} contextualLabel="Insert Column"> 1117 | Insert Column 1118 | </Button> 1119 | <Button onClick={() => editor && editor.commands.deleteColumn()} disabled={!editor} 1120 | themeColor={themeColor} variant={variant} size={size} 1121 | icon={<Icon name="table-delete-column" aria-hidden />} contextualLabel="Delete Column"> 1122 | Delete Column 1123 | </Button> 1124 | </Stack> 1125 | <TableEditorNative2 editor={editor} /> 1126 | </> 1127 | ); 1128 | } 1129 | 1130 | export const TableEditorMd = createMetadata({ 1131 | description: 1132 | "`TableEditor` provides an interactive table editing interface with controls for adding and deleting rows and columns. It supports theme customization and exports table data in HTML and Markdown formats.", 1133 | status: "experimental", 1134 | props: { 1135 | themeColor: { 1136 | description: "Sets the color scheme for all editor buttons.", 1137 | isRequired: false, 1138 | type: "string", 1139 | availableValues: buttonThemeMd, 1140 | defaultValue: "primary", 1141 | }, 1142 | variant: { 1143 | description: "Sets the visual style for all editor buttons.", 1144 | isRequired: false, 1145 | type: "string", 1146 | availableValues: buttonVariantMd, 1147 | defaultValue: "solid", 1148 | }, 1149 | size: { 1150 | description: "Sets the size for all editor buttons.", 1151 | isRequired: false, 1152 | type: "string", 1153 | availableValues: sizeMd, 1154 | defaultValue: "sm", 1155 | }, 1156 | }, 1157 | events: {}, 1158 | }); 1159 | 1160 | export const editorComponentRenderer = createComponentRenderer( 1161 | "TableEditor", 1162 | TableEditorMd, 1163 | ({ node, extractValue, registerComponentApi }) => ( 1164 | <TableEditor2 1165 | themeColor={extractValue.asOptionalString(node.props.themeColor)} 1166 | variant={extractValue.asOptionalString(node.props.variant)} 1167 | size={extractValue.asOptionalString(node.props.size)} 1168 | registerComponentApi={registerComponentApi} 1169 | /> 1170 | ), 1171 | ); 1172 | ``` 1173 | 1174 | For icon-only buttons, use the `contextualLabel` prop to provide an accessible name. 1175 | 1176 | ```xmlui 1177 | <Button icon="table-insert-row" contextualLabel="Insert a new row" /> 1178 | ``` 1179 | 1180 | This ensures screen readers announce the button's purpose, even if only the icon is visible. 1181 | 1182 | For decorative icons, use `aria-hidden="true"` to hide the icon from assistive technology. 1183 | 1184 | ```xmlui 1185 | <Icon name="table-insert-row" aria-hidden="true" /> 1186 | ``` 1187 | 1188 | ## Step 12: Make the Markdown reactive 1189 | 1190 | We want the Markdown display to update when changes happen in the editor. Make this change in `TableEditor.tsx`. 1191 | 1192 | ```js {6} {8-13} filename="TableEditor.tsx" 1193 | React.useEffect(() => { 1194 | if (!editor) return; 1195 | const handler = () => { 1196 | const html = editor.getHTML(); 1197 | const markdown = turndownService.turndown(html); 1198 | onDidChange({ html, markdown }); 1199 | }; 1200 | editor.on("update", handler); 1201 | // Emit once on mount 1202 | handler(); 1203 | return () => { 1204 | editor.off("update", handler); 1205 | }; 1206 | }, [editor, onDidChange]); 1207 | ``` 1208 | 1209 | Replace `events: {}` with this snippet. 1210 | 1211 | ```js 1212 | events: { 1213 | didChange: { 1214 | description: "Fired whenever the table content changes. Payload: { html, markdown }.", 1215 | isRequired: false, 1216 | type: "function", 1217 | }, 1218 | }, 1219 | ``` 1220 | 1221 | And do this. 1222 | 1223 | ```js {4-5} {12} copy 1224 | export const editorComponentRenderer = createComponentRenderer( 1225 | "TableEditor", 1226 | TableEditorMd, 1227 | ({ node, extractValue, registerComponentApi, lookupEventHandler }) => { 1228 | const handler = lookupEventHandler?.("didChange"); 1229 | return ( 1230 | <TableEditor 1231 | themeColor={extractValue.asOptionalString(node.props.themeColor)} 1232 | variant={extractValue.asOptionalString(node.props.variant)} 1233 | size={extractValue.asOptionalString(node.props.size)} 1234 | registerComponentApi={registerComponentApi} 1235 | onDidChange={handler} 1236 | /> 1237 | ); 1238 | }, 1239 | ) 1240 | ``` 1241 | 1242 | Now the Markdown updates as you change the table. 1243 | 1244 | <Image src="/resources/devdocs/table_editor_09.png" /> 1245 | 1246 | ## Step 13: Style the editor 1247 | 1248 | We'll want to control the editor's style with XMLUI theme variables, but first let's add basic styling in `TableEditor.module.scss`. 1249 | 1250 | ```css filename="TableEditor.module.css" copy 1251 | .table-editor-root { 1252 | padding: 8px; 1253 | overflow-x: auto; 1254 | } 1255 | 1256 | .ProseMirror { 1257 | outline: none; 1258 | white-space: pre-wrap; 1259 | word-break: break-word; 1260 | min-height: 100px; 1261 | } 1262 | 1263 | .ProseMirror table { 1264 | border-collapse: collapse; 1265 | } 1266 | .ProseMirror th, 1267 | .ProseMirror td { 1268 | border: 1px solid #ccc; 1269 | padding: 4px; 1270 | } 1271 | 1272 | .button-stack { 1273 | margin-bottom: 16px; 1274 | } 1275 | ``` 1276 | 1277 | Use the styles in `TableEditor.tsx`. 1278 | 1279 | ```js filename="TableEditor.tsx" 1280 | return ( 1281 | <div className="table-editor-root"> 1282 | <div className="button-stack"> 1283 | <Stack orientation="horizontal"> 1284 | ... 1285 | </Stack> 1286 | </div> 1287 | <TableEditorNative editor={editor} /> 1288 | </div> 1289 | ); 1290 | ``` 1291 | 1292 | Use this XMLUI markup. 1293 | 1294 | ```xmlui 1295 | <App var.markdown=""> 1296 | 1297 | <Card> 1298 | <TableEditor 1299 | id="tableEditor" 1300 | size="xs" 1301 | onDidChange="{(e) => { markdown = e.markdown }}" 1302 | /> 1303 | </Card> 1304 | 1305 | <Card> 1306 | <Text variant="codefence" preserverLinebreaks="{true}"> 1307 | { markdown } 1308 | </Text> 1309 | </Card> 1310 | </App> 1311 | ``` 1312 | 1313 | Here is the result. 1314 | 1315 | <Image src="/resources/devdocs/table_editor_10.png" /> 1316 | 1317 | ## Step 14: Theme the editor 1318 | 1319 | Now let's make `TableEditor` visually consistent with [Table](/components/Table) by reusing XMLUI theme variables for header, cell, and spacing styles. Here is the new `TableEditor.module.css`. 1320 | 1321 | ```scss filename="TableEditor.module.css" copy 1322 | @use "../../components-core/theming/themes" as t; 1323 | $componentName: "TableEditor"; 1324 | 1325 | $themeVars: (); 1326 | @function createThemeVar($componentVariable) { 1327 | $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global; 1328 | @return t.getThemeVar($themeVars, $componentVariable); 1329 | } 1330 | 1331 | // Reuse Table theme variables 1332 | $backgroundColorHeadingTable: createThemeVar("backgroundColor-heading-Table"); 1333 | $borderCellTable: createThemeVar("border-cell-Table"); 1334 | $fontSizeHeadingTable: createThemeVar("fontSize-heading-Table"); 1335 | $fontWeightHeadingTable: createThemeVar("fontWeight-heading-Table"); 1336 | $fontSizeRowTable: createThemeVar("fontSize-row-Table"); 1337 | $fontWeightRowTable: createThemeVar("fontWeight-row-Table"); 1338 | $paddingCellTable: createThemeVar("padding-cell-Table"); 1339 | $paddingHeadingTable: createThemeVar("padding-heading-Table"); 1340 | $textColorHeadingTable: createThemeVar("textColor-heading-Table"); 1341 | $textColorPaginationTable: createThemeVar("textColor-pagination-Table"); 1342 | $buttonStackSpacing: createThemeVar("spacing-buttonStack-Table"); 1343 | $textTransformHeadingTable: createThemeVar("textTransform-heading-Table"); 1344 | $tableMarginTop: t.$space-4; 1345 | $paddingHtmlTd: createThemeVar("padding-HtmlTd"); 1346 | $paddingHtmlTh: createThemeVar("padding-HtmlTh"); 1347 | 1348 | .editor { 1349 | :global(.ProseMirror) { 1350 | outline: none; 1351 | white-space: pre-wrap; 1352 | word-break: break-word; 1353 | min-height: 100px; 1354 | table { 1355 | border-collapse: collapse; 1356 | margin-top: $tableMarginTop; 1357 | } 1358 | th { 1359 | background-color: $backgroundColorHeadingTable; 1360 | font-size: $fontSizeHeadingTable; 1361 | font-weight: $fontWeightHeadingTable; 1362 | padding: $paddingHtmlTh; 1363 | color: $textColorHeadingTable; 1364 | text-transform: $textTransformHeadingTable; 1365 | } 1366 | td { 1367 | font-size: $fontSizeRowTable; 1368 | font-weight: $fontWeightRowTable; 1369 | padding: $paddingHtmlTd; 1370 | border: $borderCellTable; 1371 | } 1372 | } 1373 | } 1374 | 1375 | .button-stack { 1376 | margin-bottom: $buttonStackSpacing; 1377 | } 1378 | 1379 | .table-editor-root { 1380 | padding: 8px; 1381 | overflow-x: auto; 1382 | } 1383 | 1384 | .pagination { 1385 | color: $textColorPaginationTable; 1386 | } 1387 | 1388 | :export { 1389 | themeVars: t.json-stringify($themeVars); 1390 | } 1391 | ``` 1392 | 1393 | Now the editor matches the look of XMLUI tables rendered directly using `Table` or indirectly when tables appear in a XMLUI `Markdown` context. Try it! 1394 | 1395 | ```xmlui-pg display noHeader 1396 | <App var.markdown=""> 1397 | 1398 | <Card> 1399 | <TableEditor 1400 | id="tableEditor" 1401 | size="xs" 1402 | onDidChange="{(e) => { markdown = e.markdown }}" 1403 | /> 1404 | </Card> 1405 | 1406 | <Card> 1407 | <HStack> 1408 | <Text variant="codefence" preserveLinebreaks="{true}"> 1409 | { markdown } 1410 | </Text> 1411 | <SpaceFiller /> 1412 | <Button 1413 | icon="copy" 1414 | variant="ghost" 1415 | size="xs" 1416 | onClick="navigator.clipboard.writeText(markdown)" 1417 | /> 1418 | </HStack> 1419 | </Card> 1420 | 1421 | </App> 1422 | ``` 1423 | 1424 | <!-- 1425 | <Image src="/resources/devdocs/table_editor_11.png" /> 1426 | --> 1427 | 1428 | 1429 | ```