This is page 144 of 179. Use http://codebase.md/xmlui-org/xmlui?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 -------------------------------------------------------------------------------- /xmlui/src/components/Table/TableNative.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import type { CSSProperties, ReactNode } from "react"; 2 | import { 3 | forwardRef, 4 | useCallback, 5 | useEffect, 6 | useLayoutEffect, 7 | useMemo, 8 | useRef, 9 | useState, 10 | } from "react"; 11 | import { flushSync } from "react-dom"; 12 | import type { 13 | CellContext, 14 | Column, 15 | ColumnDef, 16 | HeaderContext, 17 | PaginationState, 18 | RowData, 19 | } from "@tanstack/react-table"; 20 | import { 21 | flexRender, 22 | getCoreRowModel, 23 | getPaginationRowModel, 24 | useReactTable, 25 | } from "@tanstack/react-table"; 26 | import { composeRefs } from "@radix-ui/react-compose-refs"; 27 | import { useVirtualizer } from "@tanstack/react-virtual"; 28 | import { orderBy } from "lodash-es"; 29 | import classnames from "classnames"; 30 | 31 | import styles from "./Table.module.scss"; 32 | 33 | import "./react-table-config.d.ts"; 34 | import type { RegisterComponentApiFn } from "../../abstractions/RendererDefs"; 35 | import type { AsyncFunction } from "../../abstractions/FunctionDefs"; 36 | import { EMPTY_ARRAY } from "../../components-core/constants"; 37 | import { useEvent } from "../../components-core/utils/misc"; 38 | import { 39 | useHasExplicitHeight, 40 | useIsomorphicLayoutEffect, 41 | usePrevious, 42 | useResizeObserver, 43 | useScrollParent, 44 | useStartMargin, 45 | } from "../../components-core/utils/hooks"; 46 | import { useTheme } from "../../components-core/theming/ThemeContext"; 47 | import { isThemeVarName } from "../../components-core/theming/transformThemeVars"; 48 | import { Spinner } from "../Spinner/SpinnerNative"; 49 | import { Toggle } from "../Toggle/Toggle"; 50 | import { Icon } from "../Icon/IconNative"; 51 | import { type OurColumnMetadata } from "../Column/TableContext"; 52 | import useRowSelection from "./useRowSelection"; 53 | import { PaginationNative, type Position } from "../Pagination/PaginationNative"; 54 | 55 | // ===================================================================================================================== 56 | // Helper types 57 | 58 | // --- Declaration merging, see here: https://tanstack.com/table/v8/docs/api/core/table#meta 59 | declare module "@tanstack/table-core" { 60 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 61 | interface TableMeta<TData extends RowData> { 62 | cellRenderer: (...args: any[]) => any; 63 | } 64 | 65 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 66 | interface ColumnMeta<TData extends RowData, TValue> { 67 | style?: CSSProperties; 68 | starSizedWidth?: string; 69 | accessorKey?: string; 70 | pinTo?: string; 71 | cellRenderer?: (row: any, rowIdx: number, colIdx: number, value?: any) => ReactNode; 72 | } 73 | } 74 | 75 | /** 76 | * This type describes an arbitraty table row that has an integer identifier and an order index. 77 | */ 78 | type RowWithOrder = { 79 | /** 80 | * Order index; we use it with paging. 81 | */ 82 | order: number; 83 | 84 | [x: string | number | symbol]: unknown; 85 | }; 86 | 87 | type SortingDirection = "ascending" | "descending"; 88 | export const TablePaginationControlsLocationValues = ["top", "bottom", "both"] as const; 89 | export type TablePaginationControlsLocation = 90 | (typeof TablePaginationControlsLocationValues)[number]; 91 | 92 | export const CheckboxToleranceValues = ["none", "compact", "comfortable", "spacious"] as const; 93 | export type CheckboxTolerance = (typeof CheckboxToleranceValues)[number]; 94 | 95 | // ===================================================================================================================== 96 | // React Table component implementation 97 | 98 | type CellVerticalAlign = "top" | "center" | "bottom"; 99 | 100 | type TableProps = { 101 | data: any[]; 102 | columns?: OurColumnMetadata[]; 103 | isPaginated?: boolean; 104 | loading?: boolean; 105 | headerHeight?: string | number; 106 | rowsSelectable?: boolean; 107 | enableMultiRowSelection?: boolean; 108 | initiallySelected?: string[]; 109 | syncWithAppState?: any; 110 | pageSizeOptions?: number[]; 111 | currentPageIndex?: number; 112 | pageSize?: number; 113 | paginationControlsLocation?: TablePaginationControlsLocation; 114 | rowDisabledPredicate?: (item: any) => boolean; 115 | sortBy?: string; 116 | sortingDirection?: SortingDirection; 117 | iconSortAsc?: string; 118 | iconSortDesc?: string; 119 | iconNoSort?: string; 120 | sortingDidChange?: AsyncFunction; 121 | onSelectionDidChange?: AsyncFunction; 122 | willSort?: AsyncFunction; 123 | style?: CSSProperties; 124 | className?: string; 125 | uid?: string; 126 | noDataRenderer?: () => ReactNode; 127 | autoFocus?: boolean; 128 | hideHeader?: boolean; 129 | hideNoDataView?: boolean; 130 | alwaysShowSelectionHeader?: boolean; 131 | registerComponentApi: RegisterComponentApiFn; 132 | noBottomBorder?: boolean; 133 | cellVerticalAlign?: CellVerticalAlign; 134 | showPageInfo?: boolean; 135 | showPageSizeSelector?: boolean; 136 | showCurrentPage?: boolean; 137 | buttonRowPosition?: Position; 138 | pageSizeSelectorPosition?: Position; 139 | pageInfoPosition?: Position; 140 | checkboxTolerance?: CheckboxTolerance; 141 | }; 142 | 143 | function defaultIsRowDisabled(_: any) { 144 | return false; 145 | } 146 | 147 | const SELECT_COLUMN_WIDTH = 42; 148 | 149 | const DEFAULT_PAGE_SIZES = [10]; 150 | 151 | /** 152 | * Maps checkbox tolerance values to pixel values 153 | * @param tolerance - The tolerance level 154 | * @returns The number of pixels for the tolerance 155 | */ 156 | const getCheckboxTolerancePixels = (tolerance: CheckboxTolerance): number => { 157 | switch (tolerance) { 158 | case "none": 159 | return 0; 160 | case "compact": 161 | return 8; 162 | case "comfortable": 163 | return 12; 164 | case "spacious": 165 | return 16; 166 | default: 167 | return 8; // fallback to compact 168 | } 169 | }; 170 | 171 | /** 172 | * Helper function to check if a point is within the checkbox boundary 173 | * @param pointX - X coordinate of the point to check 174 | * @param pointY - Y coordinate of the point to check 175 | * @param checkboxRect - Bounding rectangle of the checkbox 176 | * @param tolerancePixels - Number of pixels to extend the boundary 177 | * @returns true if the point is within the checkbox or within tolerancePixels of its boundary 178 | */ 179 | const isWithinCheckboxBoundary = ( 180 | pointX: number, 181 | pointY: number, 182 | checkboxRect: DOMRect, 183 | tolerancePixels: number 184 | ): boolean => { 185 | // Calculate distance from point to checkbox boundaries 186 | const distanceToLeft = Math.abs(pointX - checkboxRect.left); 187 | const distanceToRight = Math.abs(pointX - checkboxRect.right); 188 | const distanceToTop = Math.abs(pointY - checkboxRect.top); 189 | const distanceToBottom = Math.abs(pointY - checkboxRect.bottom); 190 | 191 | // Check if point is within the checkbox bounds or within boundary pixels of any edge 192 | const withinHorizontalBounds = pointX >= checkboxRect.left && pointX <= checkboxRect.right; 193 | const withinVerticalBounds = pointY >= checkboxRect.top && pointY <= checkboxRect.bottom; 194 | const withinCheckbox = withinHorizontalBounds && withinVerticalBounds; 195 | 196 | const nearHorizontalBoundary = (withinVerticalBounds && (distanceToLeft <= tolerancePixels || distanceToRight <= tolerancePixels)); 197 | const nearVerticalBoundary = (withinHorizontalBounds && (distanceToTop <= tolerancePixels || distanceToBottom <= tolerancePixels)); 198 | 199 | return withinCheckbox || nearHorizontalBoundary || nearVerticalBoundary; 200 | }; 201 | 202 | //These are the important styles to make sticky column pinning work! 203 | //Apply styles like this using your CSS strategy of choice with this kind of logic to head cells, data cells, footer cells, etc. 204 | //View the index.css file for more needed styles such as border-collapse: separate 205 | const getCommonPinningStyles = (column: Column<RowWithOrder>): CSSProperties => { 206 | const isPinned = column.getIsPinned(); 207 | // const isLastLeftPinnedColumn = isPinned === "left" && column.getIsLastColumn("left"); 208 | // const isFirstRightPinnedColumn = isPinned === "right" && column.getIsFirstColumn("right"); 209 | 210 | return { 211 | // boxShadow: isLastLeftPinnedColumn 212 | // ? "-4px 0 4px -4px gray inset" 213 | // : isFirstRightPinnedColumn 214 | // ? "4px 0 4px -4px gray inset" 215 | // : undefined, 216 | left: isPinned === "left" ? `${column.getStart("left")}px` : undefined, 217 | right: isPinned === "right" ? `${column.getAfter("right")}px` : undefined, 218 | opacity: isPinned ? 0.95 : undefined, 219 | position: isPinned ? "sticky" : "relative", 220 | backgroundColor: isPinned ? "inherit" : undefined, 221 | zIndex: isPinned ? 1 : undefined, 222 | }; 223 | }; 224 | 225 | // eslint-disable-next-line react/display-name 226 | export const Table = forwardRef( 227 | ( 228 | { 229 | data = defaultProps.data, 230 | columns = defaultProps.columns, 231 | isPaginated = defaultProps.isPaginated, 232 | loading = defaultProps.loading, 233 | headerHeight, 234 | rowsSelectable = defaultProps.rowsSelectable, 235 | enableMultiRowSelection = defaultProps.enableMultiRowSelection, 236 | initiallySelected = defaultProps.initiallySelected, 237 | syncWithAppState, 238 | pageSizeOptions = defaultProps.pageSizeOptions, 239 | pageSize = pageSizeOptions?.[0] || DEFAULT_PAGE_SIZES[0], 240 | currentPageIndex = 0, 241 | rowDisabledPredicate = defaultIsRowDisabled, 242 | sortBy, 243 | sortingDirection = defaultProps.sortingDirection, 244 | iconSortAsc, 245 | iconSortDesc, 246 | iconNoSort, 247 | sortingDidChange, 248 | willSort, 249 | style, 250 | className, 251 | noDataRenderer, 252 | autoFocus = defaultProps.autoFocus, 253 | hideHeader = defaultProps.hideHeader, 254 | hideNoDataView = defaultProps.hideNoDataView, 255 | alwaysShowSelectionHeader = defaultProps.alwaysShowSelectionHeader, 256 | registerComponentApi, 257 | onSelectionDidChange, 258 | noBottomBorder = defaultProps.noBottomBorder, 259 | paginationControlsLocation = defaultProps.paginationControlsLocation, 260 | cellVerticalAlign = defaultProps.cellVerticalAlign, 261 | buttonRowPosition = defaultProps.buttonRowPosition, 262 | pageSizeSelectorPosition = defaultProps.pageSizeSelectorPosition, 263 | pageInfoPosition = defaultProps.pageInfoPosition, 264 | showCurrentPage = defaultProps.showCurrentPage, 265 | showPageInfo = defaultProps.showPageInfo, 266 | showPageSizeSelector = defaultProps.showPageSizeSelector, 267 | checkboxTolerance = defaultProps.checkboxTolerance, 268 | ...rest 269 | // cols 270 | }: TableProps, 271 | forwardedRef, 272 | ) => { 273 | const { getThemeVar } = useTheme(); 274 | const safeData = Array.isArray(data) ? data : EMPTY_ARRAY; 275 | const wrapperRef = useRef<HTMLDivElement>(null); 276 | const ref = forwardedRef ? composeRefs(wrapperRef, forwardedRef) : wrapperRef; 277 | const tableRef = useRef<HTMLTableElement>(null); 278 | const estimatedHeightRef = useRef<number | null>(null); 279 | 280 | const safeColumns: OurColumnMetadata[] = useMemo(() => { 281 | if (columns) { 282 | return columns; 283 | } 284 | if (!safeData.length) { 285 | return EMPTY_ARRAY; 286 | } 287 | return Object.keys(safeData[0]).map((key: string) => ({ header: key, accessorKey: key })); 288 | }, [columns, safeData]); 289 | 290 | useEffect(() => { 291 | if (autoFocus) { 292 | wrapperRef.current!.focus(); 293 | } 294 | }, [autoFocus]); 295 | 296 | // --- Keep track of visible table rows 297 | const [visibleItems, setVisibleItems] = useState<any[]>(EMPTY_ARRAY); 298 | 299 | // --- Track which row should show forced hover for checkbox 300 | const [hoveredRowId, setHoveredRowId] = useState<string | null>(null); 301 | 302 | // --- Track if the header checkbox should show forced hover 303 | const [headerCheckboxHovered, setHeaderCheckboxHovered] = useState<boolean>(false); 304 | 305 | // --- Calculate tolerance pixels from the prop 306 | const tolerancePixels = getCheckboxTolerancePixels(checkboxTolerance); 307 | 308 | // --- Get the operations to manage selected rows in a table 309 | const { 310 | toggleRow, 311 | checkAllRows, 312 | focusedIndex, 313 | onKeyDown, 314 | selectedRowIdMap, 315 | idKey, 316 | selectionApi, 317 | } = useRowSelection({ 318 | items: safeData, 319 | visibleItems, 320 | rowsSelectable, 321 | enableMultiRowSelection, 322 | rowDisabledPredicate, 323 | onSelectionDidChange, 324 | initiallySelected, 325 | syncWithAppState, 326 | }); 327 | 328 | // --- Create data with order information whenever the items in the table change 329 | const dataWithOrder = useMemo(() => { 330 | return safeData.map((item, index) => { 331 | return { 332 | ...item, 333 | order: index + 1, 334 | }; 335 | }); 336 | }, [safeData]); 337 | 338 | // --- Local or external sorting of data 339 | const [_sortBy, _setSortBy] = useState(sortBy); 340 | const [_sortingDirection, _setSortingDirection] = useState(sortingDirection); 341 | 342 | useLayoutEffect(() => { 343 | _setSortBy(sortBy); 344 | }, [sortBy]); 345 | 346 | useLayoutEffect(() => { 347 | _setSortingDirection(sortingDirection); 348 | }, [sortingDirection]); 349 | 350 | const sortedData = useMemo(() => { 351 | if (!_sortBy) { 352 | return dataWithOrder; 353 | } 354 | return orderBy(dataWithOrder, _sortBy, _sortingDirection === "ascending" ? "asc" : "desc"); 355 | }, [_sortBy, _sortingDirection, dataWithOrder]); 356 | 357 | const _updateSorting = useCallback( 358 | async (accessorKey: string) => { 359 | let newDirection: SortingDirection = "ascending"; 360 | let newSortBy = accessorKey; 361 | // The current key is the same as the last -> the user clicked on the same header twice 362 | if (_sortBy === accessorKey) { 363 | // The last sorting direction was ascending -> make it descending 364 | if (_sortingDirection === "ascending") { 365 | newDirection = "descending"; 366 | // The last sorting direction was descending -> remove the sorting from the current key 367 | } else { 368 | newSortBy = undefined; 369 | } 370 | } 371 | 372 | // --- Check if sorting is allowed 373 | const result = await willSort?.(newSortBy, newDirection); 374 | if (result === false) { 375 | return; 376 | } 377 | 378 | _setSortingDirection(newDirection); 379 | _setSortBy(newSortBy); 380 | 381 | // External callback function is always called. 382 | // Even if sorting is internal, we can notify other components through this callback 383 | sortingDidChange?.(newSortBy, newDirection); 384 | }, 385 | [_sortBy, willSort, sortingDidChange, _sortingDirection], 386 | ); 387 | 388 | // --- Prepare column renderers according to columns defined in the table 389 | const columnsWithCustomCell: ColumnDef<any>[] = useMemo(() => { 390 | return safeColumns.map((col, idx) => { 391 | // --- Obtain column width information 392 | const { width, starSizedWidth } = getColumnWidth(col.width, true, "width"); 393 | const { width: minWidth } = getColumnWidth(col.minWidth, false, "minWidth"); 394 | const { width: maxWidth } = getColumnWidth(col.maxWidth, false, "maxWidth"); 395 | 396 | const customColumn = { 397 | ...col, 398 | header: col.header ?? col.accessorKey ?? " ", 399 | id: "col_" + idx, 400 | size: width, 401 | minSize: minWidth, 402 | maxSize: maxWidth, 403 | enableResizing: col.canResize, 404 | enableSorting: col.canSort && !!col.accessorKey, 405 | enablePinning: col.pinTo !== undefined, 406 | meta: { 407 | starSizedWidth, 408 | pinTo: col.pinTo, 409 | style: col.style, 410 | className: col.className, 411 | accessorKey: col.accessorKey, 412 | cellRenderer: col.cellRenderer, 413 | }, 414 | }; 415 | return customColumn; 416 | 417 | function getColumnWidth( 418 | colWidth: any, 419 | allowStarSize: boolean, 420 | propName: string, 421 | ): { width?: number; starSizedWidth?: string } { 422 | let starSizedWidth: string; 423 | let width: number; 424 | const resolvedWidth = isThemeVarName(colWidth) ? getThemeVar(colWidth) : colWidth; 425 | if (typeof resolvedWidth === "number") { 426 | width = resolvedWidth; 427 | } else if (typeof resolvedWidth === "string") { 428 | const oneStarSizedWidthMatch = resolvedWidth.match(/^\s*\*\s*$/); 429 | if (allowStarSize && oneStarSizedWidthMatch) { 430 | starSizedWidth = "1*"; 431 | } else { 432 | const starSizedWidthMatch = resolvedWidth.match(/^\s*(\d+)\s*\*\s*$/); 433 | if (allowStarSize && starSizedWidthMatch) { 434 | starSizedWidth = starSizedWidthMatch[1] + "*"; 435 | } else { 436 | const pixelWidthMatch = resolvedWidth.match(/^\s*(\d+)\s*(px)?\s*$/); 437 | if (pixelWidthMatch) { 438 | width = Number(pixelWidthMatch[1]); 439 | } else { 440 | throw new Error(`Invalid TableColumnDef '${propName}' value: ${resolvedWidth}`); 441 | } 442 | } 443 | } 444 | } 445 | if (width === undefined && starSizedWidth === undefined && allowStarSize) { 446 | starSizedWidth = "1*"; 447 | } 448 | return { width, starSizedWidth }; 449 | } 450 | }); 451 | }, [getThemeVar, safeColumns]); 452 | 453 | // --- Prepare column renderers according to columns defined in the table supporting optional row selection 454 | const columnsWithSelectColumn: ColumnDef<any>[] = useMemo(() => { 455 | // --- Extend the columns with a selection checkbox (indeterminate) 456 | const selectColumn = { 457 | id: "select", 458 | size: SELECT_COLUMN_WIDTH, 459 | enableResizing: false, 460 | enablePinning: true, 461 | meta: { 462 | pinTo: "left", 463 | }, 464 | header: ({ table }: HeaderContext<any, unknown>) => 465 | enableMultiRowSelection ? ( 466 | <Toggle 467 | {...{ 468 | className: classnames(styles.checkBoxWrapper, { 469 | [styles.showInHeader]: alwaysShowSelectionHeader, 470 | [styles.forceHoverWrapper]: headerCheckboxHovered, 471 | }), 472 | value: table.getIsAllRowsSelected(), 473 | indeterminate: table.getIsSomeRowsSelected(), 474 | forceHover: headerCheckboxHovered, 475 | onDidChange: () => { 476 | const allSelected = table 477 | .getRowModel() 478 | .rows.every((row) => rowDisabledPredicate(row.original) || row.getIsSelected()); 479 | checkAllRows(!allSelected); 480 | }, 481 | }} 482 | /> 483 | ) : null, 484 | cell: ({ row }: CellContext<any, unknown>) => ( 485 | <Toggle 486 | {...{ 487 | className: classnames(styles.checkBoxWrapper, { 488 | [styles.forceHoverWrapper]: hoveredRowId === row.id, 489 | }), 490 | value: row.getIsSelected(), 491 | indeterminate: row.getIsSomeSelected(), 492 | forceHover: hoveredRowId === row.id, 493 | onDidChange: () => { 494 | toggleRow(row.original, { metaKey: true }); 495 | }, 496 | }} 497 | /> 498 | ), 499 | }; 500 | return rowsSelectable ? [selectColumn, ...columnsWithCustomCell] : columnsWithCustomCell; 501 | }, [ 502 | rowsSelectable, 503 | columnsWithCustomCell, 504 | enableMultiRowSelection, 505 | alwaysShowSelectionHeader, 506 | checkAllRows, 507 | toggleRow, 508 | rowDisabledPredicate, 509 | hoveredRowId, 510 | headerCheckboxHovered, 511 | ]); 512 | 513 | // --- Set up page information (using the first page size option) 514 | // const [pagination, setPagination] = useState<PaginationState>(); 515 | const [pagination, setPagination] = useState<PaginationState>({ 516 | pageSize: isPaginated ? pageSize : Number.MAX_VALUE, 517 | pageIndex: currentPageIndex, 518 | }); 519 | 520 | const prevIsPaginated = usePrevious(isPaginated); 521 | 522 | useEffect(() => { 523 | if (!prevIsPaginated && isPaginated) { 524 | setPagination((prev) => { 525 | return { 526 | ...prev, 527 | pageSize: pageSize, 528 | pageIndex: 0, 529 | }; 530 | }); 531 | } 532 | if (prevIsPaginated && !isPaginated) { 533 | setPagination(() => { 534 | return { 535 | pageIndex: 0, 536 | pageSize: Number.MAX_VALUE, 537 | }; 538 | }); 539 | } 540 | }, [isPaginated, pageSizeOptions, prevIsPaginated, pageSize]); 541 | 542 | const [columnSizing, setColumnSizing] = useState<Record<string, number>>({}); 543 | 544 | const columnPinning = useMemo(() => { 545 | const left: Array<string> = []; 546 | const right: Array<string> = []; 547 | columnsWithSelectColumn.forEach((col) => { 548 | if (col.meta?.pinTo === "right") { 549 | right.push(col.id!); 550 | } 551 | if (col.meta?.pinTo === "left") { 552 | left.push(col.id!); 553 | } 554 | }); 555 | return { 556 | left, 557 | right, 558 | }; 559 | }, [columnsWithSelectColumn]); 560 | 561 | // --- Use the @tanstack/core-table component that manages a table 562 | const table = useReactTable<RowWithOrder>({ 563 | columns: columnsWithSelectColumn, 564 | data: sortedData, 565 | getCoreRowModel: getCoreRowModel(), 566 | getPaginationRowModel: isPaginated ? getPaginationRowModel() : undefined, 567 | enableRowSelection: rowsSelectable, 568 | enableMultiRowSelection, 569 | columnResizeMode: "onChange", 570 | getRowId: useCallback( 571 | (originalRow: any) => { 572 | return originalRow[idKey] + ""; 573 | }, 574 | [idKey], 575 | ), 576 | state: useMemo( 577 | () => ({ 578 | pagination, 579 | rowSelection: selectedRowIdMap, 580 | columnSizing, 581 | columnPinning, 582 | }), 583 | [columnPinning, columnSizing, pagination, selectedRowIdMap], 584 | ), 585 | onColumnSizingChange: setColumnSizing, 586 | onPaginationChange: setPagination, 587 | }); 588 | 589 | // --- Select the set of visible rows whenever the table rows change 590 | const rows = table.getRowModel().rows; 591 | useEffect(() => { 592 | setVisibleItems(rows.map((row) => row.original)); 593 | }, [rows]); 594 | 595 | const scrollParent = useScrollParent(wrapperRef.current?.parentElement); 596 | const scrollRef = useRef(scrollParent); 597 | scrollRef.current = scrollParent; 598 | 599 | const hasHeight = useHasExplicitHeight(wrapperRef); 600 | 601 | const hasOutsideScroll = scrollRef.current && !hasHeight; 602 | 603 | const startMargin = useStartMargin(hasOutsideScroll, wrapperRef, scrollRef); 604 | 605 | const rowVirtualizer = useVirtualizer({ 606 | count: rows.length, 607 | getScrollElement: useCallback(() => { 608 | return hasOutsideScroll && scrollRef?.current ? scrollRef?.current : wrapperRef.current; 609 | }, [scrollRef, hasOutsideScroll]), 610 | scrollMargin: startMargin, 611 | estimateSize: useCallback(() => { 612 | return estimatedHeightRef.current || 30; 613 | }, []), 614 | overscan: 5, 615 | }); 616 | 617 | const paddingTop = 618 | rowVirtualizer.getVirtualItems().length > 0 619 | ? rowVirtualizer.getVirtualItems()?.[0]?.start - startMargin || 0 620 | : 0; 621 | const paddingBottom = 622 | rowVirtualizer.getVirtualItems().length > 0 623 | ? rowVirtualizer.getTotalSize() - 624 | (rowVirtualizer.getVirtualItems()?.[rowVirtualizer.getVirtualItems().length - 1]?.end - 625 | startMargin || 0) 626 | : 0; 627 | 628 | const hasData = safeData.length !== 0; 629 | 630 | const touchedSizesRef = useRef<Record<string, boolean>>({}); 631 | const columnSizeTouched = useCallback((id: string) => { 632 | touchedSizesRef.current[id] = true; 633 | }, []); 634 | 635 | const recalculateStarSizes = useEvent(() => { 636 | if (!tableRef.current) { 637 | return; 638 | } 639 | let availableWidth = tableRef.current.clientWidth - 1; 640 | // -1 to prevent horizontal scroll in scaled browsers (when you zoom in) 641 | const widths: Record<string, number> = {}; 642 | const columnsWithoutSize: Array<Column<RowWithOrder>> = []; 643 | const numberOfUnitsById: Record<string, number> = {}; 644 | 645 | table.getAllColumns().forEach((column) => { 646 | if (column.columnDef.size !== undefined || touchedSizesRef.current[column.id]) { 647 | availableWidth -= columnSizing[column.id] || column.columnDef.size || 0; 648 | } else { 649 | columnsWithoutSize.push(column); 650 | let units: number; 651 | if (column.columnDef.meta?.starSizedWidth) { 652 | units = Number(column.columnDef.meta?.starSizedWidth.replace("*", "").trim()) || 1; 653 | } else { 654 | units = 1; 655 | } 656 | numberOfUnitsById[column.id] = units; 657 | } 658 | }); 659 | const numberOfAllUnits = Object.values(numberOfUnitsById).reduce((acc, val) => acc + val, 0); 660 | columnsWithoutSize.forEach((column) => { 661 | widths[column.id] = Math.floor( 662 | availableWidth * (numberOfUnitsById[column.id] / numberOfAllUnits), 663 | ); 664 | }); 665 | flushSync(() => { 666 | setColumnSizing((prev) => { 667 | return { 668 | ...prev, 669 | ...widths, 670 | }; 671 | }); 672 | }); 673 | }); 674 | 675 | useResizeObserver(tableRef, recalculateStarSizes); 676 | 677 | useIsomorphicLayoutEffect(() => { 678 | queueMicrotask(() => { 679 | recalculateStarSizes(); 680 | }); 681 | }, [recalculateStarSizes, safeColumns]); 682 | 683 | useIsomorphicLayoutEffect(() => { 684 | registerComponentApi(selectionApi); 685 | }, [registerComponentApi, selectionApi]); 686 | 687 | const paginationControls = ( 688 | <PaginationNative 689 | pageIndex={pagination.pageIndex} 690 | pageSize={pagination.pageSize} 691 | itemCount={safeData.length} 692 | pageSizeOptions={pageSizeOptions} 693 | onPageDidChange={(page) => table.setPageIndex(page)} 694 | onPageSizeDidChange={(size) => table.setPageSize(size)} 695 | showCurrentPage={showCurrentPage} 696 | showPageInfo={showPageInfo} 697 | showPageSizeSelector={showPageSizeSelector} 698 | buttonRowPosition={buttonRowPosition} 699 | pageInfoPosition={pageInfoPosition} 700 | pageSizeSelectorPosition={pageSizeSelectorPosition} 701 | /> 702 | ); 703 | 704 | return ( 705 | <div 706 | {...rest} 707 | className={classnames(styles.wrapper, className, { [styles.noScroll]: hasOutsideScroll })} 708 | tabIndex={-1} 709 | onKeyDown={onKeyDown} 710 | ref={ref} 711 | style={style} 712 | > 713 | {isPaginated && 714 | hasData && 715 | rows.length > 0 && 716 | pagination && 717 | (paginationControlsLocation === "top" || paginationControlsLocation === "both") && 718 | paginationControls} 719 | 720 | <table 721 | className={styles.table} 722 | ref={tableRef} 723 | > 724 | {!hideHeader && ( 725 | <thead style={{ height: headerHeight }} className={styles.headerWrapper}> 726 | {table.getHeaderGroups().map((headerGroup, headerGroupIndex) => ( 727 | <tr 728 | key={`${headerGroup.id}-${headerGroupIndex}`} 729 | className={classnames(styles.headerRow, { 730 | [styles.allSelected]: table.getIsAllRowsSelected(), 731 | })} 732 | onClick={(event) => { 733 | const target = event.target as HTMLElement; 734 | const headerCell = target.closest('th'); 735 | 736 | // Only handle clicks for the select column header 737 | if (headerCell && rowsSelectable && enableMultiRowSelection) { 738 | const header = headerGroup.headers.find(h => { 739 | const headerElement = headerCell; 740 | return headerElement?.getAttribute('data-column-id') === h.id || h.id === 'select'; 741 | }); 742 | 743 | if (header && header.id === 'select') { 744 | const clickX = event.clientX; 745 | const clickY = event.clientY; 746 | const checkbox = headerCell.querySelector('input[type=\"checkbox\"]') as HTMLInputElement; 747 | 748 | if (checkbox) { 749 | const checkboxRect = checkbox.getBoundingClientRect(); 750 | 751 | if (isWithinCheckboxBoundary(clickX, clickY, checkboxRect, tolerancePixels)) { 752 | // Prevent the default click and manually trigger the checkbox 753 | event.preventDefault(); 754 | event.stopPropagation(); 755 | 756 | const allSelected = table 757 | .getRowModel() 758 | .rows.every((row) => rowDisabledPredicate(row.original) || row.getIsSelected()); 759 | checkAllRows(!allSelected); 760 | } 761 | } 762 | } 763 | } 764 | }} 765 | onMouseMove={(event) => { 766 | if (rowsSelectable && enableMultiRowSelection) { 767 | const target = event.target as HTMLElement; 768 | const headerCell = target.closest('th'); 769 | 770 | if (headerCell) { 771 | const header = headerGroup.headers.find(h => { 772 | const headerElement = headerCell; 773 | return headerElement?.getAttribute('data-column-id') === h.id || h.id === 'select'; 774 | }); 775 | 776 | if (header && header.id === 'select') { 777 | const mouseX = event.clientX; 778 | const mouseY = event.clientY; 779 | const checkbox = headerCell.querySelector('input[type=\"checkbox\"]') as HTMLInputElement; 780 | 781 | if (checkbox) { 782 | const checkboxRect = checkbox.getBoundingClientRect(); 783 | const shouldShowHover = isWithinCheckboxBoundary(mouseX, mouseY, checkboxRect, tolerancePixels); 784 | 785 | if (shouldShowHover && !headerCheckboxHovered) { 786 | setHeaderCheckboxHovered(true); 787 | event.currentTarget.style.cursor = 'pointer'; 788 | } else if (!shouldShowHover && headerCheckboxHovered) { 789 | setHeaderCheckboxHovered(false); 790 | event.currentTarget.style.cursor = ''; 791 | } 792 | } 793 | } else if (headerCheckboxHovered) { 794 | setHeaderCheckboxHovered(false); 795 | event.currentTarget.style.cursor = ''; 796 | } 797 | } 798 | } 799 | }} 800 | onMouseLeave={(event) => { 801 | if (headerCheckboxHovered) { 802 | setHeaderCheckboxHovered(false); 803 | event.currentTarget.style.cursor = ''; 804 | } 805 | }} 806 | > 807 | {headerGroup.headers.map((header, headerIndex) => { 808 | const { width, ...style } = header.column.columnDef.meta?.style || {}; 809 | const size = header.getSize(); 810 | const alignmentClass = 811 | cellVerticalAlign === "top" 812 | ? styles.alignTop 813 | : cellVerticalAlign === "bottom" 814 | ? styles.alignBottom 815 | : styles.alignCenter; 816 | return ( 817 | <th 818 | key={`${header.id}-${headerIndex}`} 819 | data-column-id={header.id} 820 | className={classnames(styles.columnCell, alignmentClass)} 821 | colSpan={header.colSpan} 822 | style={{ 823 | position: "relative", 824 | width: size, 825 | ...getCommonPinningStyles(header.column), 826 | }} 827 | > 828 | <ClickableHeader 829 | hasSorting={ 830 | header.column.columnDef.enableSorting && 831 | !!header.column.columnDef.meta?.accessorKey 832 | } 833 | updateSorting={() => 834 | _updateSorting(header.column.columnDef.meta?.accessorKey) 835 | } 836 | > 837 | <div className={styles.headerContent} style={style}> 838 | { 839 | flexRender( 840 | header.column.columnDef.header, 841 | header.getContext(), 842 | ) as ReactNode 843 | } 844 | {header.column.columnDef.enableSorting && ( 845 | <span style={{ display: "inline-flex", maxWidth: 16 }}> 846 | <ColumnOrderingIndicator 847 | iconSortAsc={iconSortAsc} 848 | iconSortDesc={iconSortDesc} 849 | iconNoSort={iconNoSort} 850 | direction={ 851 | header.column.columnDef.meta?.accessorKey === _sortBy 852 | ? _sortingDirection 853 | : undefined 854 | } 855 | /> 856 | </span> 857 | )} 858 | </div> 859 | </ClickableHeader> 860 | {header.column.getCanResize() && ( 861 | <div 862 | {...{ 863 | onDoubleClick: () => { 864 | touchedSizesRef.current[header.column.id] = false; 865 | if (header.column.columnDef.size !== undefined) { 866 | header.column.resetSize(); 867 | } else { 868 | recalculateStarSizes(); 869 | } 870 | }, 871 | onMouseDown: (event) => { 872 | columnSizeTouched(header.column.id); 873 | header.getResizeHandler()(event); 874 | }, 875 | onTouchStart: (event) => { 876 | columnSizeTouched(header.column.id); 877 | header.getResizeHandler()(event); 878 | }, 879 | className: classnames(styles.resizer, { 880 | [styles.isResizing]: header.column.getIsResizing(), 881 | }), 882 | }} 883 | /> 884 | )} 885 | </th> 886 | ); 887 | })} 888 | </tr> 889 | ))} 890 | </thead> 891 | )} 892 | {hasData && ( 893 | <tbody className={styles.tableBody}> 894 | {paddingTop > 0 && ( 895 | <tr> 896 | <td style={{ height: `${paddingTop}px` }} /> 897 | </tr> 898 | )} 899 | {rowVirtualizer.getVirtualItems().map((virtualRow) => { 900 | const rowIndex = virtualRow.index; 901 | const row = rows[rowIndex]; 902 | return ( 903 | <tr 904 | data-index={rowIndex} 905 | key={`${row.id}-${rowIndex}`} 906 | className={classnames(styles.row, { 907 | [styles.selected]: row.getIsSelected(), 908 | [styles.focused]: focusedIndex === rowIndex, 909 | [styles.disabled]: rowDisabledPredicate(row.original), 910 | [styles.noBottomBorder]: noBottomBorder, 911 | })} 912 | ref={(el) => { 913 | if (el && estimatedHeightRef.current === null) { 914 | estimatedHeightRef.current = Math.round(el.getBoundingClientRect().height); 915 | } 916 | rowVirtualizer.measureElement(el); 917 | }} 918 | onClick={(event) => { 919 | if (event?.defaultPrevented) { 920 | return; 921 | } 922 | const target = event.target as HTMLElement; 923 | if (target.tagName.toLowerCase() === "input") { 924 | return; 925 | } 926 | 927 | // Check if click is within checkbox boundary 928 | const currentRow = event.currentTarget as HTMLElement; 929 | const checkbox = currentRow.querySelector('input[type="checkbox"]') as HTMLInputElement; 930 | 931 | if (checkbox) { 932 | const checkboxRect = checkbox.getBoundingClientRect(); 933 | const clickX = event.clientX; 934 | const clickY = event.clientY; 935 | 936 | if (isWithinCheckboxBoundary(clickX, clickY, checkboxRect, tolerancePixels)) { 937 | // Toggle the checkbox when clicking within the boundary 938 | toggleRow(row.original, { metaKey: true }); 939 | return; 940 | } 941 | } 942 | 943 | toggleRow(row.original, event); 944 | }} 945 | onMouseMove={(event) => { 946 | // Change cursor and hover state when within checkbox boundary 947 | const currentRow = event.currentTarget as HTMLElement; 948 | const checkbox = currentRow.querySelector('input[type="checkbox"]') as HTMLInputElement; 949 | 950 | if (checkbox) { 951 | const checkboxRect = checkbox.getBoundingClientRect(); 952 | const mouseX = event.clientX; 953 | const mouseY = event.clientY; 954 | 955 | const shouldShowHover = isWithinCheckboxBoundary(mouseX, mouseY, checkboxRect, tolerancePixels); 956 | 957 | // Update hover state and cursor based on proximity to checkbox 958 | if (shouldShowHover) { 959 | setHoveredRowId(row.id); 960 | currentRow.style.cursor = 'pointer'; 961 | } else { 962 | setHoveredRowId(null); 963 | currentRow.style.cursor = ''; 964 | } 965 | } 966 | }} 967 | onMouseLeave={(event) => { 968 | // Reset cursor and hover state when leaving the row 969 | const currentRow = event.currentTarget as HTMLElement; 970 | currentRow.style.cursor = ''; 971 | setHoveredRowId(null); 972 | }} 973 | > 974 | {row.getVisibleCells().map((cell, i) => { 975 | const cellRenderer = cell.column.columnDef?.meta?.cellRenderer; 976 | const size = cell.column.getSize(); 977 | const alignmentClass = 978 | cellVerticalAlign === "top" 979 | ? styles.alignTop 980 | : cellVerticalAlign === "bottom" 981 | ? styles.alignBottom 982 | : styles.alignCenter; 983 | return ( 984 | <td 985 | className={classnames(styles.cell, alignmentClass)} 986 | key={`${cell.id}-${i}`} 987 | style={{ 988 | // width: size, 989 | width: size, 990 | ...getCommonPinningStyles(cell.column), 991 | }} 992 | > 993 | {/*we have to wrap it in a div, because tables... 994 | 995 | The "Last Cell" Problem: Why This Happens 996 | Even with box-sizing: border-box, the browser gives special treatment to the last column of a table. 997 | 998 | With table-layout: fixed, the browser calculates the widths for all columns. 999 | It strictly enforces the widths for columns 1, 2, 3, etc. 1000 | The last column is often used as a "catch-all" to ensure the total width of all columns exactly matches the width of the <table> element itself (e.g., 100%). 1001 | When you add padding-right to this very last cell, you create a conflict. The browser tries to simultaneously: 1002 | Keep the cell's right edge perfectly aligned with the table's right edge. 1003 | Render the padding inside that right edge. 1004 | 1005 | Solution: The Inner Wrapper <div> 1006 | The most robust and common solution is to decouple the cell's width from its content's padding. You do this by adding a wrapper <div> inside the cell. 1007 | The <td> will be responsible only for setting the rigid width. 1008 | The inner <div> will be responsible only for handling the padding and content. 1009 | */} 1010 | <div className={styles.cellContent}> 1011 | {cellRenderer 1012 | ? cellRenderer(cell.row.original, rowIndex, i, cell?.getValue()) 1013 | : (flexRender( 1014 | cell.column.columnDef.cell, 1015 | cell.getContext(), 1016 | ) as ReactNode)} 1017 | </div> 1018 | </td> 1019 | ); 1020 | })} 1021 | </tr> 1022 | ); 1023 | })} 1024 | {paddingBottom > 0 && ( 1025 | <tr> 1026 | <td style={{ height: `${paddingBottom}px` }} /> 1027 | </tr> 1028 | )} 1029 | </tbody> 1030 | )} 1031 | </table> 1032 | {loading && !hasData && ( 1033 | <div className={styles.loadingWrapper}> 1034 | <Spinner /> 1035 | </div> 1036 | )} 1037 | {!hideNoDataView && 1038 | !loading && 1039 | !hasData && 1040 | (noDataRenderer ? ( 1041 | noDataRenderer() 1042 | ) : ( 1043 | <div className={styles.noRows}>No data available</div> 1044 | ))} 1045 | 1046 | {isPaginated && 1047 | hasData && 1048 | rows.length > 0 && 1049 | pagination && 1050 | (paginationControlsLocation === "bottom" || paginationControlsLocation === "both") && 1051 | paginationControls} 1052 | </div> 1053 | ); 1054 | }, 1055 | ); 1056 | 1057 | type ClickableHeaderProps = { 1058 | hasSorting?: boolean; 1059 | updateSorting?: () => void; 1060 | children?: ReactNode; 1061 | }; 1062 | 1063 | function ClickableHeader({ hasSorting, updateSorting, children }: ClickableHeaderProps) { 1064 | return hasSorting ? ( 1065 | <button type="button" className={styles.clickableHeader} onClick={updateSorting}> 1066 | {children} 1067 | </button> 1068 | ) : ( 1069 | <>{children}</> 1070 | ); 1071 | } 1072 | 1073 | type ColumnOrderingIndicatorProps = { 1074 | direction?: SortingDirection; 1075 | iconSortAsc?: string; 1076 | iconSortDesc?: string; 1077 | iconNoSort?: string; 1078 | }; 1079 | 1080 | function ColumnOrderingIndicator({ 1081 | direction, 1082 | iconSortAsc = "sortasc:Table", 1083 | iconSortDesc = "sortdesc:Table", 1084 | iconNoSort = "nosort:Table", 1085 | }: ColumnOrderingIndicatorProps) { 1086 | if (direction === "ascending") { 1087 | return <Icon name={iconSortAsc} fallback="sortasc" size="12" />; //sortasc 1088 | } else if (direction === "descending") { 1089 | return <Icon name={iconSortDesc} fallback="sortdesc" size="12" />; //sortdesc 1090 | } 1091 | return iconNoSort !== "-" ? ( 1092 | <Icon name={iconNoSort} fallback="nosort" size="12" /> 1093 | ) : ( 1094 | <Icon name={iconNoSort} size="12" /> 1095 | ); //nosort 1096 | } 1097 | 1098 | export const defaultProps = { 1099 | data: EMPTY_ARRAY, 1100 | columns: EMPTY_ARRAY, 1101 | isPaginated: false, 1102 | loading: false, 1103 | rowsSelectable: false, 1104 | enableMultiRowSelection: true, 1105 | initiallySelected: EMPTY_ARRAY, 1106 | pageSizeOptions: [5, 10, 15], 1107 | sortingDirection: "ascending" as SortingDirection, 1108 | autoFocus: false, 1109 | hideHeader: false, 1110 | hideNoDataView: false, 1111 | alwaysShowSelectionHeader: false, 1112 | noBottomBorder: false, 1113 | paginationControlsLocation: "bottom" as TablePaginationControlsLocation, 1114 | cellVerticalAlign: "center" as CellVerticalAlign, 1115 | showPageInfo: true, 1116 | showPageSizeSelector: true, 1117 | showCurrentPage: true, 1118 | buttonRowPosition: "center" as Position, 1119 | pageSizeSelectorPosition: "start" as Position, 1120 | pageInfoPosition: "end" as Position, 1121 | checkboxTolerance: "compact" as CheckboxTolerance, 1122 | }; 1123 | ```