This is page 124 of 181. Use http://codebase.md/xmlui-org/xmlui/mockApiDef.js?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .changeset │ ├── config.json │ └── cool-queens-look.md ├── .eslintrc.cjs ├── .github │ ├── build-checklist.png │ ├── ISSUE_TEMPLATE │ │ ├── bug_report.md │ │ └── feature_request.md │ └── workflows │ ├── deploy-blog.yml │ ├── deploy-docs-optimized.yml │ ├── deploy-docs.yml │ ├── prepare-versions.yml │ ├── release-packages.yml │ ├── run-all-tests.yml │ └── run-smoke-tests.yml ├── .gitignore ├── .prettierrc.js ├── .vscode │ ├── launch.json │ └── settings.json ├── blog │ ├── .gitignore │ ├── .gitkeep │ ├── CHANGELOG.md │ ├── extensions.ts │ ├── index.html │ ├── index.ts │ ├── 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 │ │ ├── netlify.toml │ │ ├── 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 │ │ │ ├── Debug.xmlui │ │ │ └── PageNotFound.xmlui │ │ ├── config.ts │ │ ├── Main.xmlui │ │ ├── Main.xmlui.xs │ │ └── themes │ │ ├── docs-theme.ts │ │ ├── earthtone.ts │ │ ├── xmlui-gray-on-default.ts │ │ ├── xmlui-green-on-default.ts │ │ └── xmlui-orange-on-default.ts │ └── tsconfig.json ├── CONTRIBUTING.md ├── docs │ ├── .gitignore │ ├── CHANGELOG.md │ ├── ComponentRefLinks.txt │ ├── content │ │ ├── _meta.json │ │ ├── components │ │ │ ├── _meta.json │ │ │ ├── _overview.md │ │ │ ├── APICall.md │ │ │ ├── App.md │ │ │ ├── AppHeader.md │ │ │ ├── AppState.md │ │ │ ├── AutoComplete.md │ │ │ ├── Avatar.md │ │ │ ├── Backdrop.md │ │ │ ├── Badge.md │ │ │ ├── BarChart.md │ │ │ ├── Bookmark.md │ │ │ ├── Breakout.md │ │ │ ├── Button.md │ │ │ ├── Card.md │ │ │ ├── Carousel.md │ │ │ ├── ChangeListener.md │ │ │ ├── Checkbox.md │ │ │ ├── CHStack.md │ │ │ ├── ColorPicker.md │ │ │ ├── Column.md │ │ │ ├── ContentSeparator.md │ │ │ ├── CVStack.md │ │ │ ├── DataSource.md │ │ │ ├── DateInput.md │ │ │ ├── DatePicker.md │ │ │ ├── DonutChart.md │ │ │ ├── DropdownMenu.md │ │ │ ├── EmojiSelector.md │ │ │ ├── ExpandableItem.md │ │ │ ├── FileInput.md │ │ │ ├── FileUploadDropZone.md │ │ │ ├── FlowLayout.md │ │ │ ├── Footer.md │ │ │ ├── Form.md │ │ │ ├── FormItem.md │ │ │ ├── FormSection.md │ │ │ ├── Fragment.md │ │ │ ├── H1.md │ │ │ ├── H2.md │ │ │ ├── H3.md │ │ │ ├── H4.md │ │ │ ├── H5.md │ │ │ ├── H6.md │ │ │ ├── Heading.md │ │ │ ├── HSplitter.md │ │ │ ├── HStack.md │ │ │ ├── Icon.md │ │ │ ├── IFrame.md │ │ │ ├── Image.md │ │ │ ├── Items.md │ │ │ ├── LabelList.md │ │ │ ├── Legend.md │ │ │ ├── LineChart.md │ │ │ ├── Link.md │ │ │ ├── List.md │ │ │ ├── Logo.md │ │ │ ├── Markdown.md │ │ │ ├── MenuItem.md │ │ │ ├── MenuSeparator.md │ │ │ ├── ModalDialog.md │ │ │ ├── NavGroup.md │ │ │ ├── NavLink.md │ │ │ ├── NavPanel.md │ │ │ ├── NoResult.md │ │ │ ├── NumberBox.md │ │ │ ├── Option.md │ │ │ ├── Page.md │ │ │ ├── PageMetaTitle.md │ │ │ ├── Pages.md │ │ │ ├── Pagination.md │ │ │ ├── PasswordInput.md │ │ │ ├── PieChart.md │ │ │ ├── ProgressBar.md │ │ │ ├── Queue.md │ │ │ ├── RadioGroup.md │ │ │ ├── RealTimeAdapter.md │ │ │ ├── Redirect.md │ │ │ ├── Select.md │ │ │ ├── Slider.md │ │ │ ├── Slot.md │ │ │ ├── SpaceFiller.md │ │ │ ├── Spinner.md │ │ │ ├── Splitter.md │ │ │ ├── Stack.md │ │ │ ├── StickyBox.md │ │ │ ├── SubMenuItem.md │ │ │ ├── Switch.md │ │ │ ├── TabItem.md │ │ │ ├── Table.md │ │ │ ├── TableOfContents.md │ │ │ ├── Tabs.md │ │ │ ├── Text.md │ │ │ ├── TextArea.md │ │ │ ├── TextBox.md │ │ │ ├── Theme.md │ │ │ ├── TimeInput.md │ │ │ ├── Timer.md │ │ │ ├── ToneChangerButton.md │ │ │ ├── ToneSwitch.md │ │ │ ├── Tooltip.md │ │ │ ├── Tree.md │ │ │ ├── VSplitter.md │ │ │ ├── VStack.md │ │ │ ├── xmlui-animations │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ ├── Animation.md │ │ │ │ ├── FadeAnimation.md │ │ │ │ ├── FadeInAnimation.md │ │ │ │ ├── FadeOutAnimation.md │ │ │ │ ├── ScaleAnimation.md │ │ │ │ └── SlideInAnimation.md │ │ │ ├── xmlui-pdf │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ └── Pdf.md │ │ │ ├── xmlui-spreadsheet │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ └── Spreadsheet.md │ │ │ └── xmlui-website-blocks │ │ │ ├── _meta.json │ │ │ ├── _overview.md │ │ │ ├── Carousel.md │ │ │ ├── HelloMd.md │ │ │ ├── HeroSection.md │ │ │ └── ScrollToTop.md │ │ └── extensions │ │ ├── _meta.json │ │ ├── xmlui-animations │ │ │ ├── _meta.json │ │ │ ├── _overview.md │ │ │ ├── Animation.md │ │ │ ├── FadeAnimation.md │ │ │ ├── FadeInAnimation.md │ │ │ ├── FadeOutAnimation.md │ │ │ ├── ScaleAnimation.md │ │ │ └── SlideInAnimation.md │ │ └── xmlui-website-blocks │ │ ├── _meta.json │ │ ├── _overview.md │ │ ├── Carousel.md │ │ ├── HelloMd.md │ │ ├── HeroSection.md │ │ └── ScrollToTop.md │ ├── extensions.ts │ ├── index.html │ ├── index.ts │ ├── package.json │ ├── public │ │ ├── feed.rss │ │ ├── mockServiceWorker.js │ │ ├── pages │ │ │ ├── _meta.json │ │ │ ├── app-structure.md │ │ │ ├── build-editor-component.md │ │ │ ├── build-hello-world-component.md │ │ │ ├── components-intro.md │ │ │ ├── context-variables.md │ │ │ ├── forms.md │ │ │ ├── globals.md │ │ │ ├── glossary.md │ │ │ ├── helper-tags.md │ │ │ ├── hosted-deployment.md │ │ │ ├── howto │ │ │ │ ├── assign-a-complex-json-literal-to-a-component-variable.md │ │ │ │ ├── chain-a-refetch.md │ │ │ │ ├── debug-a-component.md │ │ │ │ ├── delay-a-datasource-until-another-datasource-is-ready.md │ │ │ │ ├── delegate-a-method.md │ │ │ │ ├── do-custom-form-validation.md │ │ │ │ ├── expose-a-method-from-a-component.md │ │ │ │ ├── filter-and-transform-data-from-an-api.md │ │ │ │ ├── group-items-in-list-by-a-property.md │ │ │ │ ├── handle-background-operations.md │ │ │ │ ├── hide-an-element-until-its-datasource-is-ready.md │ │ │ │ ├── make-a-set-of-equal-width-cards.md │ │ │ │ ├── make-a-table-responsive.md │ │ │ │ ├── modify-a-value-reported-in-a-column.md │ │ │ │ ├── paginate-a-list.md │ │ │ │ ├── pass-data-to-a-modal-dialog.md │ │ │ │ ├── react-to-button-click-not-keystrokes.md │ │ │ │ ├── set-the-initial-value-of-a-select-from-fetched-data.md │ │ │ │ ├── share-a-modaldialog-across-components.md │ │ │ │ ├── sync-selections-between-table-and-list-views.md │ │ │ │ ├── update-ui-optimistically.md │ │ │ │ ├── use-built-in-form-validation.md │ │ │ │ └── use-the-same-modaldialog-to-add-or-edit.md │ │ │ ├── howto.md │ │ │ ├── intro.md │ │ │ ├── layout.md │ │ │ ├── markup.md │ │ │ ├── mcp.md │ │ │ ├── modal-dialogs.md │ │ │ ├── news-and-reviews.md │ │ │ ├── reactive-intro.md │ │ │ ├── refactoring.md │ │ │ ├── routing-and-links.md │ │ │ ├── samples │ │ │ │ ├── color-palette.xmlui │ │ │ │ ├── color-values.xmlui │ │ │ │ ├── shadow-sizes.xmlui │ │ │ │ ├── spacing-sizes.xmlui │ │ │ │ ├── swatch.xmlui │ │ │ │ ├── theme-gallery-brief.xmlui │ │ │ │ └── theme-gallery.xmlui │ │ │ ├── scoping.md │ │ │ ├── scripting.md │ │ │ ├── styles-and-themes │ │ │ │ ├── common-units.md │ │ │ │ ├── layout-props.md │ │ │ │ ├── theme-variable-defaults.md │ │ │ │ ├── theme-variables.md │ │ │ │ └── themes.md │ │ │ ├── template-properties.md │ │ │ ├── test.md │ │ │ ├── tutorial-01.md │ │ │ ├── tutorial-02.md │ │ │ ├── tutorial-03.md │ │ │ ├── tutorial-04.md │ │ │ ├── tutorial-05.md │ │ │ ├── tutorial-06.md │ │ │ ├── tutorial-07.md │ │ │ ├── tutorial-08.md │ │ │ ├── tutorial-09.md │ │ │ ├── tutorial-10.md │ │ │ ├── tutorial-11.md │ │ │ ├── tutorial-12.md │ │ │ ├── universal-properties.md │ │ │ ├── user-defined-components.md │ │ │ ├── vscode.md │ │ │ ├── working-with-markdown.md │ │ │ ├── working-with-text.md │ │ │ ├── xmlui-animations │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ ├── Animation.md │ │ │ │ ├── FadeAnimation.md │ │ │ │ ├── FadeInAnimation.md │ │ │ │ ├── FadeOutAnimation.md │ │ │ │ ├── ScaleAnimation.md │ │ │ │ └── SlideInAnimation.md │ │ │ ├── xmlui-charts │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ ├── BarChart.md │ │ │ │ ├── DonutChart.md │ │ │ │ ├── LabelList.md │ │ │ │ ├── Legend.md │ │ │ │ ├── LineChart.md │ │ │ │ └── PieChart.md │ │ │ ├── xmlui-pdf │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ └── Pdf.md │ │ │ └── xmlui-spreadsheet │ │ │ ├── _meta.json │ │ │ ├── _overview.md │ │ │ └── Spreadsheet.md │ │ ├── resources │ │ │ ├── devdocs │ │ │ │ ├── debug-proxy-object-2.png │ │ │ │ ├── debug-proxy-object.png │ │ │ │ ├── table_editor_01.png │ │ │ │ ├── table_editor_02.png │ │ │ │ ├── table_editor_03.png │ │ │ │ ├── table_editor_04.png │ │ │ │ ├── table_editor_05.png │ │ │ │ ├── table_editor_06.png │ │ │ │ ├── table_editor_07.png │ │ │ │ ├── table_editor_08.png │ │ │ │ ├── table_editor_09.png │ │ │ │ ├── table_editor_10.png │ │ │ │ ├── table_editor_11.png │ │ │ │ ├── table-editor-01.png │ │ │ │ ├── table-editor-02.png │ │ │ │ ├── table-editor-03.png │ │ │ │ ├── table-editor-04.png │ │ │ │ ├── table-editor-06.png │ │ │ │ ├── table-editor-07.png │ │ │ │ ├── table-editor-08.png │ │ │ │ ├── table-editor-09.png │ │ │ │ └── xmlui-rendering-of-tiptap-markdown.png │ │ │ ├── favicon.ico │ │ │ ├── files │ │ │ │ ├── clients.json │ │ │ │ ├── daily-revenue.json │ │ │ │ ├── dashboard-stats.json │ │ │ │ ├── demo.xmlui │ │ │ │ ├── demo.xmlui.xs │ │ │ │ ├── downloads │ │ │ │ │ └── downloads.json │ │ │ │ ├── for-download │ │ │ │ │ ├── index-with-api.html │ │ │ │ │ ├── index.html │ │ │ │ │ ├── mockApi.js │ │ │ │ │ ├── start-darwin.sh │ │ │ │ │ ├── start-linux.sh │ │ │ │ │ ├── start.bat │ │ │ │ │ └── xmlui │ │ │ │ │ └── xmlui-standalone.umd.js │ │ │ │ ├── getting-started │ │ │ │ │ ├── cl-tutorial-final.zip │ │ │ │ │ ├── cl-tutorial.zip │ │ │ │ │ ├── cl-tutorial2.zip │ │ │ │ │ ├── cl-tutorial3.zip │ │ │ │ │ ├── cl-tutorial4.zip │ │ │ │ │ ├── cl-tutorial5.zip │ │ │ │ │ ├── cl-tutorial6.zip │ │ │ │ │ ├── getting-started.zip │ │ │ │ │ ├── hello-xmlui.zip │ │ │ │ │ ├── xmlui-empty.zip │ │ │ │ │ └── xmlui-starter.zip │ │ │ │ ├── howto │ │ │ │ │ └── component-icons │ │ │ │ │ └── up-arrow.svg │ │ │ │ ├── invoices.json │ │ │ │ ├── monthly-status.json │ │ │ │ ├── news-and-reviews.json │ │ │ │ ├── products.json │ │ │ │ ├── releases.json │ │ │ │ ├── tutorials │ │ │ │ │ ├── datasource │ │ │ │ │ │ └── api.ts │ │ │ │ │ └── p2do │ │ │ │ │ ├── api.ts │ │ │ │ │ └── todo-logo.svg │ │ │ │ └── xmlui.json │ │ │ ├── github.svg │ │ │ ├── images │ │ │ │ ├── apiaction-tutorial │ │ │ │ │ ├── add-success.png │ │ │ │ │ ├── apiaction-param.png │ │ │ │ │ ├── change-completed.png │ │ │ │ │ ├── change-in-progress.png │ │ │ │ │ ├── confirm-delete.png │ │ │ │ │ ├── data-error.png │ │ │ │ │ ├── data-progress.png │ │ │ │ │ ├── data-success.png │ │ │ │ │ ├── display-1.png │ │ │ │ │ ├── item-deleted.png │ │ │ │ │ ├── item-updated.png │ │ │ │ │ ├── missing-api-key.png │ │ │ │ │ ├── new-item-added.png │ │ │ │ │ └── test-message.png │ │ │ │ ├── chat-api │ │ │ │ │ └── domain-model.svg │ │ │ │ ├── components │ │ │ │ │ ├── image │ │ │ │ │ │ └── breakfast.jpg │ │ │ │ │ ├── markdown │ │ │ │ │ │ └── colors.png │ │ │ │ │ └── modal │ │ │ │ │ ├── deep_link_dialog_1.jpg │ │ │ │ │ └── deep_link_dialog_2.jpg │ │ │ │ ├── create-apps │ │ │ │ │ ├── collapsed-vertical.png │ │ │ │ │ ├── using-forms-warning-dialog.png │ │ │ │ │ └── using-forms.png │ │ │ │ ├── datasource-tutorial │ │ │ │ │ ├── data-with-header.png │ │ │ │ │ ├── filtered-data.png │ │ │ │ │ ├── filtered-items.png │ │ │ │ │ ├── initial-page-items.png │ │ │ │ │ ├── list-items.png │ │ │ │ │ ├── next-page-items.png │ │ │ │ │ ├── no-data.png │ │ │ │ │ ├── pagination-1.jpg │ │ │ │ │ ├── pagination-1.png │ │ │ │ │ ├── polling-1.png │ │ │ │ │ ├── refetch-data.png │ │ │ │ │ ├── slow-loading.png │ │ │ │ │ ├── test-message.png │ │ │ │ │ ├── Thumbs.db │ │ │ │ │ ├── unconventional-data.png │ │ │ │ │ └── unfiltered-items.png │ │ │ │ ├── flower.jpg │ │ │ │ ├── get-started │ │ │ │ │ ├── add-new-contact.png │ │ │ │ │ ├── app-modified.png │ │ │ │ │ ├── app-start.png │ │ │ │ │ ├── app-with-boxes.png │ │ │ │ │ ├── app-with-toast.png │ │ │ │ │ ├── boilerplate-structure.png │ │ │ │ │ ├── cl-initial.png │ │ │ │ │ ├── cl-start.png │ │ │ │ │ ├── contact-counts.png │ │ │ │ │ ├── contact-dialog-title.png │ │ │ │ │ ├── contact-dialog.png │ │ │ │ │ ├── contact-menus.png │ │ │ │ │ ├── contact-predicates.png │ │ │ │ │ ├── context-menu.png │ │ │ │ │ ├── dashboard-numbers.png │ │ │ │ │ ├── default-contact-list.png │ │ │ │ │ ├── delete-contact.png │ │ │ │ │ ├── delete-task.png │ │ │ │ │ ├── detailed-template.png │ │ │ │ │ ├── edit-contact-details.png │ │ │ │ │ ├── edited-contact-saved.png │ │ │ │ │ ├── empty-sections.png │ │ │ │ │ ├── filter-completed.png │ │ │ │ │ ├── fullwidth-desktop.png │ │ │ │ │ ├── fullwidth-mobile.png │ │ │ │ │ ├── initial-table.png │ │ │ │ │ ├── items-and-badges.png │ │ │ │ │ ├── loading-message.png │ │ │ │ │ ├── new-contact-button.png │ │ │ │ │ ├── new-contact-saved.png │ │ │ │ │ ├── no-empty-sections.png │ │ │ │ │ ├── personal-todo-initial.png │ │ │ │ │ ├── piechart.png │ │ │ │ │ ├── review-today.png │ │ │ │ │ ├── rudimentary-dashboard.png │ │ │ │ │ ├── section-collapsed.png │ │ │ │ │ ├── sectioned-items.png │ │ │ │ │ ├── sections-ordered.png │ │ │ │ │ ├── spacex-list-with-links.png │ │ │ │ │ ├── spacex-list.png │ │ │ │ │ ├── start-personal-todo-1.png │ │ │ │ │ ├── submit-new-contact.png │ │ │ │ │ ├── submit-new-task.png │ │ │ │ │ ├── syntax-highlighting.png │ │ │ │ │ ├── table-with-badge.png │ │ │ │ │ ├── template-with-card.png │ │ │ │ │ ├── test-emulated-api.png │ │ │ │ │ ├── Thumbs.db │ │ │ │ │ ├── todo-logo.png │ │ │ │ │ └── xmlui-tools.png │ │ │ │ ├── HelloApp.png │ │ │ │ ├── HelloApp2.png │ │ │ │ ├── logos │ │ │ │ │ ├── xmlui1.svg │ │ │ │ │ ├── xmlui2.svg │ │ │ │ │ ├── xmlui3.svg │ │ │ │ │ ├── xmlui4.svg │ │ │ │ │ ├── xmlui5.svg │ │ │ │ │ ├── xmlui6.svg │ │ │ │ │ └── xmlui7.svg │ │ │ │ ├── pdf │ │ │ │ │ └── dummy-pdf.jpg │ │ │ │ ├── rendering-engine │ │ │ │ │ ├── AppEngine-flow.svg │ │ │ │ │ ├── Component.svg │ │ │ │ │ ├── CompoundComponent.svg │ │ │ │ │ ├── RootComponent.svg │ │ │ │ │ └── tree-with-containers.svg │ │ │ │ ├── reviewers-guide │ │ │ │ │ ├── AppEngine-flow.svg │ │ │ │ │ └── incbutton-in-action.png │ │ │ │ ├── tools │ │ │ │ │ └── boilerplate-structure.png │ │ │ │ ├── try.svg │ │ │ │ ├── tutorial │ │ │ │ │ ├── app-chat-history.png │ │ │ │ │ ├── app-content-placeholder.png │ │ │ │ │ ├── app-header-and-content.png │ │ │ │ │ ├── app-links-channel-selected.png │ │ │ │ │ ├── app-links-click.png │ │ │ │ │ ├── app-navigation.png │ │ │ │ │ ├── finished-ex01.png │ │ │ │ │ ├── finished-ex02.png │ │ │ │ │ ├── hello.png │ │ │ │ │ ├── splash-screen-advanced.png │ │ │ │ │ ├── splash-screen-after-click.png │ │ │ │ │ ├── splash-screen-centered.png │ │ │ │ │ ├── splash-screen-events.png │ │ │ │ │ ├── splash-screen-expression.png │ │ │ │ │ ├── splash-screen-reuse-after.png │ │ │ │ │ ├── splash-screen-reuse-before.png │ │ │ │ │ └── splash-screen.png │ │ │ │ └── tutorial-01.png │ │ │ ├── llms.txt │ │ │ ├── logo-dark.svg │ │ │ ├── logo.svg │ │ │ ├── pg-popout.svg │ │ │ └── xmlui-logo.svg │ │ ├── serve.json │ │ └── web.config │ ├── scripts │ │ ├── download-latest-xmlui.js │ │ ├── generate-rss.js │ │ ├── get-releases.js │ │ └── utils.js │ ├── src │ │ ├── components │ │ │ ├── BlogOverview.xmlui │ │ │ ├── BlogPage.xmlui │ │ │ ├── Boxes.xmlui │ │ │ ├── Breadcrumb.xmlui │ │ │ ├── ChangeLog.xmlui │ │ │ ├── ColorPalette.xmlui │ │ │ ├── DocumentLinks.xmlui │ │ │ ├── DocumentPage.xmlui │ │ │ ├── DocumentPageNoTOC.xmlui │ │ │ ├── Icons.xmlui │ │ │ ├── IncButton.xmlui │ │ │ ├── IncButton2.xmlui │ │ │ ├── NameValue.xmlui │ │ │ ├── PageNotFound.xmlui │ │ │ ├── PaletteItem.xmlui │ │ │ ├── Palettes.xmlui │ │ │ ├── SectionHeader.xmlui │ │ │ ├── TBD.xmlui │ │ │ ├── Test.xmlui │ │ │ ├── ThemesIntro.xmlui │ │ │ ├── ThousandThemes.xmlui │ │ │ ├── TubeStops.xmlui │ │ │ ├── TubeStops.xmlui.xs │ │ │ └── TwoColumnCode.xmlui │ │ ├── config.ts │ │ ├── Main.xmlui │ │ └── themes │ │ ├── docs-theme.ts │ │ ├── earthtone.ts │ │ ├── xmlui-gray-on-default.ts │ │ ├── xmlui-green-on-default.ts │ │ └── xmlui-orange-on-default.ts │ └── tsconfig.json ├── LICENSE ├── package-lock.json ├── package.json ├── packages │ ├── xmlui-animations │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── Animation.tsx │ │ │ ├── AnimationNative.tsx │ │ │ ├── FadeAnimation.tsx │ │ │ ├── FadeInAnimation.tsx │ │ │ ├── FadeOutAnimation.tsx │ │ │ ├── index.tsx │ │ │ ├── ScaleAnimation.tsx │ │ │ └── SlideInAnimation.tsx │ │ └── tsconfig.json │ ├── xmlui-devtools │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── devtools │ │ │ │ ├── DevTools.tsx │ │ │ │ ├── DevToolsNative.module.scss │ │ │ │ ├── DevToolsNative.tsx │ │ │ │ ├── ModalDialog.module.scss │ │ │ │ ├── ModalDialog.tsx │ │ │ │ ├── ModalVisibilityContext.tsx │ │ │ │ ├── Tooltip.module.scss │ │ │ │ ├── Tooltip.tsx │ │ │ │ └── utils.ts │ │ │ ├── editor │ │ │ │ └── Editor.tsx │ │ │ └── index.tsx │ │ ├── tsconfig.json │ │ └── vite.config-overrides.ts │ ├── xmlui-hello-world │ │ ├── .gitignore │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── HelloWorld.module.scss │ │ │ ├── HelloWorld.tsx │ │ │ ├── HelloWorldNative.tsx │ │ │ └── index.tsx │ │ └── tsconfig.json │ ├── xmlui-os-frames │ │ ├── .gitignore │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── IPhoneFrame.module.scss │ │ │ ├── IPhoneFrame.tsx │ │ │ ├── MacOSAppFrame.module.scss │ │ │ ├── MacOSAppFrame.tsx │ │ │ ├── WindowsAppFrame.module.scss │ │ │ └── WindowsAppFrame.tsx │ │ └── tsconfig.json │ ├── xmlui-pdf │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ ├── components │ │ │ │ └── Pdf.xmlui │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── LazyPdfNative.tsx │ │ │ ├── Pdf.module.scss │ │ │ └── Pdf.tsx │ │ └── tsconfig.json │ ├── xmlui-playground │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── hooks │ │ │ │ ├── usePlayground.ts │ │ │ │ └── useToast.ts │ │ │ ├── index.tsx │ │ │ ├── playground │ │ │ │ ├── Box.module.scss │ │ │ │ ├── Box.tsx │ │ │ │ ├── CodeSelector.tsx │ │ │ │ ├── ConfirmationDialog.module.scss │ │ │ │ ├── ConfirmationDialog.tsx │ │ │ │ ├── Editor.tsx │ │ │ │ ├── Header.module.scss │ │ │ │ ├── Header.tsx │ │ │ │ ├── Playground.tsx │ │ │ │ ├── PlaygroundContent.module.scss │ │ │ │ ├── PlaygroundContent.tsx │ │ │ │ ├── PlaygroundNative.module.scss │ │ │ │ ├── PlaygroundNative.tsx │ │ │ │ ├── Preview.module.scss │ │ │ │ ├── Preview.tsx │ │ │ │ ├── Select.module.scss │ │ │ │ ├── StandalonePlayground.tsx │ │ │ │ ├── StandalonePlaygroundNative.module.scss │ │ │ │ ├── StandalonePlaygroundNative.tsx │ │ │ │ ├── ThemeSwitcher.module.scss │ │ │ │ ├── ThemeSwitcher.tsx │ │ │ │ ├── ToneSwitcher.tsx │ │ │ │ ├── Tooltip.module.scss │ │ │ │ ├── Tooltip.tsx │ │ │ │ └── utils.ts │ │ │ ├── providers │ │ │ │ ├── Toast.module.scss │ │ │ │ └── ToastProvider.tsx │ │ │ ├── state │ │ │ │ └── store.ts │ │ │ ├── themes │ │ │ │ └── theme.ts │ │ │ └── utils │ │ │ └── helpers.ts │ │ └── tsconfig.json │ ├── xmlui-search │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── Search.module.scss │ │ │ └── Search.tsx │ │ └── tsconfig.json │ ├── xmlui-spreadsheet │ │ ├── .gitignore │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── Spreadsheet.tsx │ │ │ └── SpreadsheetNative.tsx │ │ └── tsconfig.json │ └── xmlui-website-blocks │ ├── .gitignore │ ├── CHANGELOG.md │ ├── demo │ │ ├── components │ │ │ ├── HeroBackgroundBreakoutPage.xmlui │ │ │ ├── HeroBackgroundsPage.xmlui │ │ │ ├── HeroContentsPage.xmlui │ │ │ ├── HeroTextAlignPage.xmlui │ │ │ ├── HeroTextPage.xmlui │ │ │ └── HeroTonesPage.xmlui │ │ ├── Main.xmlui │ │ └── themes │ │ └── default.ts │ ├── index.html │ ├── index.ts │ ├── meta │ │ └── componentsMetadata.ts │ ├── package.json │ ├── public │ │ └── resources │ │ ├── building.jpg │ │ └── xmlui-logo.svg │ ├── src │ │ ├── Carousel │ │ │ ├── Carousel.module.scss │ │ │ ├── Carousel.tsx │ │ │ ├── CarouselContext.tsx │ │ │ └── CarouselNative.tsx │ │ ├── FancyButton │ │ │ ├── FancyButton.module.scss │ │ │ ├── FancyButton.tsx │ │ │ └── FancyButton.xmlui │ │ ├── Hello │ │ │ ├── Hello.tsx │ │ │ ├── Hello.xmlui │ │ │ └── Hello.xmlui.xs │ │ ├── HeroSection │ │ │ ├── HeroSection.module.scss │ │ │ ├── HeroSection.tsx │ │ │ └── HeroSectionNative.tsx │ │ ├── index.tsx │ │ ├── ScrollToTop │ │ │ ├── ScrollToTop.module.scss │ │ │ ├── ScrollToTop.tsx │ │ │ └── ScrollToTopNative.tsx │ │ └── vite-env.d.ts │ └── tsconfig.json ├── README.md ├── tools │ ├── codefence │ │ └── xmlui-code-fence-docs.md │ ├── create-app │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── create-app.ts │ │ ├── helpers │ │ │ ├── copy.ts │ │ │ ├── get-pkg-manager.ts │ │ │ ├── git.ts │ │ │ ├── install.ts │ │ │ ├── is-folder-empty.ts │ │ │ ├── is-writeable.ts │ │ │ ├── make-dir.ts │ │ │ └── validate-pkg.ts │ │ ├── index.ts │ │ ├── package.json │ │ ├── templates │ │ │ ├── default │ │ │ │ └── ts │ │ │ │ ├── gitignore │ │ │ │ ├── index.html │ │ │ │ ├── index.ts │ │ │ │ ├── public │ │ │ │ │ ├── mockServiceWorker.js │ │ │ │ │ ├── resources │ │ │ │ │ │ ├── favicon.ico │ │ │ │ │ │ └── xmlui-logo.svg │ │ │ │ │ └── serve.json │ │ │ │ └── src │ │ │ │ ├── components │ │ │ │ │ ├── ApiAware.xmlui │ │ │ │ │ ├── Home.xmlui │ │ │ │ │ ├── IncButton.xmlui │ │ │ │ │ └── PagePanel.xmlui │ │ │ │ ├── config.ts │ │ │ │ └── Main.xmlui │ │ │ ├── index.ts │ │ │ └── types.ts │ │ └── tsconfig.json │ ├── create-xmlui-hello-world │ │ ├── index.js │ │ └── package.json │ └── vscode │ ├── .gitignore │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ ├── .vscodeignore │ ├── build.sh │ ├── CHANGELOG.md │ ├── esbuild.js │ ├── eslint.config.mjs │ ├── formatter-docs.md │ ├── generate-test-sample.sh │ ├── LICENSE.md │ ├── package-lock.json │ ├── package.json │ ├── README.md │ ├── resources │ │ ├── xmlui-logo.png │ │ └── xmlui-markup-syntax-highlighting.png │ ├── src │ │ ├── extension.ts │ │ └── server.ts │ ├── syntaxes │ │ └── xmlui.tmLanguage.json │ ├── test-samples │ │ └── sample.xmlui │ ├── tsconfig.json │ └── tsconfig.tsbuildinfo ├── turbo.json └── xmlui ├── .gitignore ├── bin │ ├── bootstrap.js │ ├── build-lib.ts │ ├── build.ts │ ├── index.ts │ ├── preview.ts │ ├── start.ts │ ├── vite-xmlui-plugin.ts │ └── viteConfig.ts ├── CHANGELOG.md ├── conventions │ ├── component-qa-checklist.md │ ├── copilot-conventions.md │ ├── create-xmlui-components.md │ ├── mermaid.md │ ├── testing-conventions.md │ └── xmlui-in-a-nutshell.md ├── dev-docs │ ├── accessibility.md │ ├── build-system.md │ ├── build-xmlui.md │ ├── component-behaviors.md │ ├── containers.md │ ├── glossary.md │ ├── index.md │ ├── next │ │ ├── component-dev-guide.md │ │ ├── configuration-management-enhancement-summary.md │ │ ├── documentation-scripts-refactoring-complete-summary.md │ │ ├── documentation-scripts-refactoring-plan.md │ │ ├── duplicate-pattern-extraction-summary.md │ │ ├── error-handling-standardization-summary.md │ │ ├── generating-component-reference.md │ │ ├── index.md │ │ ├── logging-consistency-implementation-summary.md │ │ ├── project-build.md │ │ ├── project-structure.md │ │ ├── theme-context.md │ │ ├── tiptap-design-considerations.md │ │ ├── working-with-code.md │ │ ├── xmlui-runtime-architecture │ │ └── xmlui-wcag-accessibility-report.md │ ├── react-fundamentals.md │ ├── release-method.md │ ├── standalone-app.md │ ├── state-management.md │ └── xmlui-repo.md ├── package.json ├── playwright.config.ts ├── scripts │ ├── coverage-only.js │ ├── e2e-test-summary.js │ ├── generate-docs │ │ ├── build-downloads-map.mjs │ │ ├── build-pages-map.mjs │ │ ├── components-config.json │ │ ├── configuration-management.mjs │ │ ├── constants.mjs │ │ ├── create-theme-files.mjs │ │ ├── DocsGenerator.mjs │ │ ├── error-handling.mjs │ │ ├── extensions-config.json │ │ ├── folders.mjs │ │ ├── generate-summary-files.mjs │ │ ├── get-docs.mjs │ │ ├── input-handler.mjs │ │ ├── logger.mjs │ │ ├── logging-standards.mjs │ │ ├── MetadataProcessor.mjs │ │ ├── pattern-utilities.mjs │ │ └── utils.mjs │ ├── get-langserver-metadata.mjs │ ├── inline-links.mjs │ └── README-e2e-summary.md ├── src │ ├── abstractions │ │ ├── _conventions.md │ │ ├── ActionDefs.ts │ │ ├── AppContextDefs.ts │ │ ├── ComponentDefs.ts │ │ ├── ContainerDefs.ts │ │ ├── ExtensionDefs.ts │ │ ├── FunctionDefs.ts │ │ ├── RendererDefs.ts │ │ ├── scripting │ │ │ ├── BlockScope.ts │ │ │ ├── Compilation.ts │ │ │ ├── LogicalThread.ts │ │ │ ├── LoopScope.ts │ │ │ ├── modules.ts │ │ │ ├── ScriptParserError.ts │ │ │ ├── Token.ts │ │ │ ├── TryScope.ts │ │ │ └── TryScopeExp.ts │ │ └── ThemingDefs.ts │ ├── components │ │ ├── _conventions.md │ │ ├── abstractions.ts │ │ ├── Accordion │ │ │ ├── Accordion.md │ │ │ ├── Accordion.module.scss │ │ │ ├── Accordion.spec.ts │ │ │ ├── Accordion.tsx │ │ │ ├── AccordionContext.tsx │ │ │ ├── AccordionItem.tsx │ │ │ ├── AccordionItemNative.tsx │ │ │ └── AccordionNative.tsx │ │ ├── Animation │ │ │ └── AnimationNative.tsx │ │ ├── APICall │ │ │ ├── APICall.md │ │ │ ├── APICall.spec.ts │ │ │ ├── APICall.tsx │ │ │ └── APICallNative.tsx │ │ ├── App │ │ │ ├── App.md │ │ │ ├── App.module.scss │ │ │ ├── App.spec.ts │ │ │ ├── App.tsx │ │ │ ├── AppLayoutContext.ts │ │ │ ├── AppNative.tsx │ │ │ ├── AppStateContext.ts │ │ │ ├── doc-resources │ │ │ │ ├── condensed-sticky.xmlui │ │ │ │ ├── condensed.xmlui │ │ │ │ ├── horizontal-sticky.xmlui │ │ │ │ ├── horizontal.xmlui │ │ │ │ ├── vertical-full-header.xmlui │ │ │ │ ├── vertical-sticky.xmlui │ │ │ │ └── vertical.xmlui │ │ │ ├── IndexerContext.ts │ │ │ ├── LinkInfoContext.ts │ │ │ ├── SearchContext.tsx │ │ │ ├── Sheet.module.scss │ │ │ └── Sheet.tsx │ │ ├── AppHeader │ │ │ ├── AppHeader.md │ │ │ ├── AppHeader.module.scss │ │ │ ├── AppHeader.spec.ts │ │ │ ├── AppHeader.tsx │ │ │ └── AppHeaderNative.tsx │ │ ├── AppState │ │ │ ├── AppState.md │ │ │ ├── AppState.spec.ts │ │ │ ├── AppState.tsx │ │ │ └── AppStateNative.tsx │ │ ├── AutoComplete │ │ │ ├── AutoComplete.md │ │ │ ├── AutoComplete.module.scss │ │ │ ├── AutoComplete.spec.ts │ │ │ ├── AutoComplete.tsx │ │ │ ├── AutoCompleteContext.tsx │ │ │ └── AutoCompleteNative.tsx │ │ ├── Avatar │ │ │ ├── Avatar.md │ │ │ ├── Avatar.module.scss │ │ │ ├── Avatar.spec.ts │ │ │ ├── Avatar.tsx │ │ │ └── AvatarNative.tsx │ │ ├── Backdrop │ │ │ ├── Backdrop.md │ │ │ ├── Backdrop.module.scss │ │ │ ├── Backdrop.spec.ts │ │ │ ├── Backdrop.tsx │ │ │ └── BackdropNative.tsx │ │ ├── Badge │ │ │ ├── Badge.md │ │ │ ├── Badge.module.scss │ │ │ ├── Badge.spec.ts │ │ │ ├── Badge.tsx │ │ │ └── BadgeNative.tsx │ │ ├── Bookmark │ │ │ ├── Bookmark.md │ │ │ ├── Bookmark.module.scss │ │ │ ├── Bookmark.spec.ts │ │ │ ├── Bookmark.tsx │ │ │ └── BookmarkNative.tsx │ │ ├── Breakout │ │ │ ├── Breakout.module.scss │ │ │ ├── Breakout.spec.ts │ │ │ ├── Breakout.tsx │ │ │ └── BreakoutNative.tsx │ │ ├── Button │ │ │ ├── Button-style.spec.ts │ │ │ ├── Button.md │ │ │ ├── Button.module.scss │ │ │ ├── Button.spec.ts │ │ │ ├── Button.tsx │ │ │ └── ButtonNative.tsx │ │ ├── Card │ │ │ ├── Card.md │ │ │ ├── Card.module.scss │ │ │ ├── Card.spec.ts │ │ │ ├── Card.tsx │ │ │ └── CardNative.tsx │ │ ├── Carousel │ │ │ ├── Carousel.md │ │ │ ├── Carousel.module.scss │ │ │ ├── Carousel.spec.ts │ │ │ ├── Carousel.tsx │ │ │ ├── CarouselContext.tsx │ │ │ ├── CarouselItem.tsx │ │ │ ├── CarouselItemNative.tsx │ │ │ └── CarouselNative.tsx │ │ ├── ChangeListener │ │ │ ├── ChangeListener.md │ │ │ ├── ChangeListener.spec.ts │ │ │ ├── ChangeListener.tsx │ │ │ └── ChangeListenerNative.tsx │ │ ├── chart-color-schemes.ts │ │ ├── Charts │ │ │ ├── AreaChart │ │ │ │ ├── AreaChart.md │ │ │ │ ├── AreaChart.spec.ts │ │ │ │ ├── AreaChart.tsx │ │ │ │ └── AreaChartNative.tsx │ │ │ ├── BarChart │ │ │ │ ├── BarChart.md │ │ │ │ ├── BarChart.module.scss │ │ │ │ ├── BarChart.spec.ts │ │ │ │ ├── BarChart.tsx │ │ │ │ └── BarChartNative.tsx │ │ │ ├── DonutChart │ │ │ │ ├── DonutChart.spec.ts │ │ │ │ └── DonutChart.tsx │ │ │ ├── LabelList │ │ │ │ ├── LabelList.spec.ts │ │ │ │ ├── LabelList.tsx │ │ │ │ ├── LabelListNative.module.scss │ │ │ │ └── LabelListNative.tsx │ │ │ ├── Legend │ │ │ │ ├── Legend.spec.ts │ │ │ │ ├── Legend.tsx │ │ │ │ └── LegendNative.tsx │ │ │ ├── LineChart │ │ │ │ ├── LineChart.md │ │ │ │ ├── LineChart.module.scss │ │ │ │ ├── LineChart.spec.ts │ │ │ │ ├── LineChart.tsx │ │ │ │ └── LineChartNative.tsx │ │ │ ├── PieChart │ │ │ │ ├── PieChart.md │ │ │ │ ├── PieChart.spec.ts │ │ │ │ ├── PieChart.tsx │ │ │ │ ├── PieChartNative.module.scss │ │ │ │ └── PieChartNative.tsx │ │ │ ├── RadarChart │ │ │ │ ├── RadarChart.md │ │ │ │ ├── RadarChart.spec.ts │ │ │ │ ├── RadarChart.tsx │ │ │ │ └── RadarChartNative.tsx │ │ │ ├── Tooltip │ │ │ │ ├── TooltipContent.module.scss │ │ │ │ ├── TooltipContent.spec.ts │ │ │ │ └── TooltipContent.tsx │ │ │ └── utils │ │ │ ├── abstractions.ts │ │ │ └── ChartProvider.tsx │ │ ├── Checkbox │ │ │ ├── Checkbox.md │ │ │ ├── Checkbox.spec.ts │ │ │ └── Checkbox.tsx │ │ ├── CodeBlock │ │ │ ├── CodeBlock.module.scss │ │ │ ├── CodeBlock.spec.ts │ │ │ ├── CodeBlock.tsx │ │ │ ├── CodeBlockNative.tsx │ │ │ └── highlight-code.ts │ │ ├── collectedComponentMetadata.ts │ │ ├── ColorPicker │ │ │ ├── ColorPicker.md │ │ │ ├── ColorPicker.module.scss │ │ │ ├── ColorPicker.spec.ts │ │ │ ├── ColorPicker.tsx │ │ │ └── ColorPickerNative.tsx │ │ ├── Column │ │ │ ├── Column.md │ │ │ ├── Column.tsx │ │ │ ├── ColumnNative.tsx │ │ │ ├── doc-resources │ │ │ │ └── list-component-data.js │ │ │ └── TableContext.tsx │ │ ├── component-utils.ts │ │ ├── ComponentProvider.tsx │ │ ├── ComponentRegistryContext.tsx │ │ ├── container-helpers.tsx │ │ ├── ContentSeparator │ │ │ ├── ContentSeparator.md │ │ │ ├── ContentSeparator.module.scss │ │ │ ├── ContentSeparator.spec.ts │ │ │ ├── ContentSeparator.tsx │ │ │ └── ContentSeparatorNative.tsx │ │ ├── DataSource │ │ │ ├── DataSource.md │ │ │ └── DataSource.tsx │ │ ├── DateInput │ │ │ ├── DateInput.md │ │ │ ├── DateInput.module.scss │ │ │ ├── DateInput.spec.ts │ │ │ ├── DateInput.tsx │ │ │ └── DateInputNative.tsx │ │ ├── DatePicker │ │ │ ├── DatePicker.md │ │ │ ├── DatePicker.module.scss │ │ │ ├── DatePicker.spec.ts │ │ │ ├── DatePicker.tsx │ │ │ └── DatePickerNative.tsx │ │ ├── DropdownMenu │ │ │ ├── DropdownMenu.md │ │ │ ├── DropdownMenu.module.scss │ │ │ ├── DropdownMenu.spec.ts │ │ │ ├── DropdownMenu.tsx │ │ │ ├── DropdownMenuNative.tsx │ │ │ ├── MenuItem.md │ │ │ └── SubMenuItem.md │ │ ├── EmojiSelector │ │ │ ├── EmojiSelector.md │ │ │ ├── EmojiSelector.spec.ts │ │ │ ├── EmojiSelector.tsx │ │ │ └── EmojiSelectorNative.tsx │ │ ├── ExpandableItem │ │ │ ├── ExpandableItem.module.scss │ │ │ ├── ExpandableItem.spec.ts │ │ │ ├── ExpandableItem.tsx │ │ │ └── ExpandableItemNative.tsx │ │ ├── FileInput │ │ │ ├── FileInput.md │ │ │ ├── FileInput.module.scss │ │ │ ├── FileInput.spec.ts │ │ │ ├── FileInput.tsx │ │ │ └── FileInputNative.tsx │ │ ├── FileUploadDropZone │ │ │ ├── FileUploadDropZone.md │ │ │ ├── FileUploadDropZone.module.scss │ │ │ ├── FileUploadDropZone.spec.ts │ │ │ ├── FileUploadDropZone.tsx │ │ │ └── FileUploadDropZoneNative.tsx │ │ ├── FlowLayout │ │ │ ├── FlowLayout.md │ │ │ ├── FlowLayout.module.scss │ │ │ ├── FlowLayout.spec.ts │ │ │ ├── FlowLayout.spec.ts-snapshots │ │ │ │ └── Edge-cases-boxShadow-is-not-clipped-1-non-smoke-darwin.png │ │ │ ├── FlowLayout.tsx │ │ │ └── FlowLayoutNative.tsx │ │ ├── Footer │ │ │ ├── Footer.md │ │ │ ├── Footer.module.scss │ │ │ ├── Footer.spec.ts │ │ │ ├── Footer.tsx │ │ │ └── FooterNative.tsx │ │ ├── Form │ │ │ ├── Form.md │ │ │ ├── Form.module.scss │ │ │ ├── Form.spec.ts │ │ │ ├── Form.tsx │ │ │ ├── formActions.ts │ │ │ ├── FormContext.ts │ │ │ └── FormNative.tsx │ │ ├── FormItem │ │ │ ├── FormItem.md │ │ │ ├── FormItem.module.scss │ │ │ ├── FormItem.spec.ts │ │ │ ├── FormItem.tsx │ │ │ ├── FormItemNative.tsx │ │ │ ├── HelperText.module.scss │ │ │ ├── HelperText.tsx │ │ │ ├── ItemWithLabel.tsx │ │ │ └── Validations.ts │ │ ├── FormSection │ │ │ ├── FormSection.md │ │ │ ├── FormSection.ts │ │ │ └── FormSection.xmlui │ │ ├── Fragment │ │ │ ├── Fragment.spec.ts │ │ │ └── Fragment.tsx │ │ ├── Heading │ │ │ ├── abstractions.ts │ │ │ ├── H1.md │ │ │ ├── H1.spec.ts │ │ │ ├── H2.md │ │ │ ├── H2.spec.ts │ │ │ ├── H3.md │ │ │ ├── H3.spec.ts │ │ │ ├── H4.md │ │ │ ├── H4.spec.ts │ │ │ ├── H5.md │ │ │ ├── H5.spec.ts │ │ │ ├── H6.md │ │ │ ├── H6.spec.ts │ │ │ ├── Heading.md │ │ │ ├── Heading.module.scss │ │ │ ├── Heading.spec.ts │ │ │ ├── Heading.tsx │ │ │ └── HeadingNative.tsx │ │ ├── HoverCard │ │ │ ├── HoverCard.tsx │ │ │ └── HovercardNative.tsx │ │ ├── HtmlTags │ │ │ ├── HtmlTags.module.scss │ │ │ ├── HtmlTags.spec.ts │ │ │ └── HtmlTags.tsx │ │ ├── Icon │ │ │ ├── AdmonitionDanger.tsx │ │ │ ├── AdmonitionInfo.tsx │ │ │ ├── AdmonitionNote.tsx │ │ │ ├── AdmonitionTip.tsx │ │ │ ├── AdmonitionWarning.tsx │ │ │ ├── ApiIcon.tsx │ │ │ ├── ArrowDropDown.module.scss │ │ │ ├── ArrowDropDown.tsx │ │ │ ├── ArrowDropUp.module.scss │ │ │ ├── ArrowDropUp.tsx │ │ │ ├── ArrowLeft.module.scss │ │ │ ├── ArrowLeft.tsx │ │ │ ├── ArrowRight.module.scss │ │ │ ├── ArrowRight.tsx │ │ │ ├── Attach.tsx │ │ │ ├── Binding.module.scss │ │ │ ├── Binding.tsx │ │ │ ├── BoardIcon.tsx │ │ │ ├── BoxIcon.tsx │ │ │ ├── CheckIcon.tsx │ │ │ ├── ChevronDownIcon.tsx │ │ │ ├── ChevronLeft.tsx │ │ │ ├── ChevronRight.tsx │ │ │ ├── ChevronUpIcon.tsx │ │ │ ├── CodeFileIcon.tsx │ │ │ ├── CodeSandbox.tsx │ │ │ ├── CompactListIcon.tsx │ │ │ ├── ContentCopyIcon.tsx │ │ │ ├── DarkToLightIcon.tsx │ │ │ ├── DatabaseIcon.module.scss │ │ │ ├── DatabaseIcon.tsx │ │ │ ├── DocFileIcon.tsx │ │ │ ├── DocIcon.tsx │ │ │ ├── DotMenuHorizontalIcon.tsx │ │ │ ├── DotMenuIcon.tsx │ │ │ ├── EmailIcon.tsx │ │ │ ├── EmptyFolderIcon.tsx │ │ │ ├── ErrorIcon.tsx │ │ │ ├── ExpressionIcon.tsx │ │ │ ├── FillPlusCricleIcon.tsx │ │ │ ├── FilterIcon.tsx │ │ │ ├── FolderIcon.tsx │ │ │ ├── GlobeIcon.tsx │ │ │ ├── HomeIcon.tsx │ │ │ ├── HyperLinkIcon.tsx │ │ │ ├── Icon.md │ │ │ ├── Icon.module.scss │ │ │ ├── Icon.spec.ts │ │ │ ├── Icon.tsx │ │ │ ├── IconNative.tsx │ │ │ ├── ImageFileIcon.tsx │ │ │ ├── Inspect.tsx │ │ │ ├── LightToDark.tsx │ │ │ ├── LinkIcon.tsx │ │ │ ├── ListIcon.tsx │ │ │ ├── LooseListIcon.tsx │ │ │ ├── MoonIcon.tsx │ │ │ ├── MoreOptionsIcon.tsx │ │ │ ├── NoSortIcon.tsx │ │ │ ├── PDFIcon.tsx │ │ │ ├── PenIcon.tsx │ │ │ ├── PhoneIcon.tsx │ │ │ ├── PhotoIcon.tsx │ │ │ ├── PlusIcon.tsx │ │ │ ├── SearchIcon.tsx │ │ │ ├── ShareIcon.tsx │ │ │ ├── SortAscendingIcon.tsx │ │ │ ├── SortDescendingIcon.tsx │ │ │ ├── StarsIcon.tsx │ │ │ ├── SunIcon.tsx │ │ │ ├── svg │ │ │ │ ├── admonition_danger.svg │ │ │ │ ├── admonition_info.svg │ │ │ │ ├── admonition_note.svg │ │ │ │ ├── admonition_tip.svg │ │ │ │ ├── admonition_warning.svg │ │ │ │ ├── api.svg │ │ │ │ ├── arrow-dropdown.svg │ │ │ │ ├── arrow-left.svg │ │ │ │ ├── arrow-right.svg │ │ │ │ ├── arrow-up.svg │ │ │ │ ├── attach.svg │ │ │ │ ├── binding.svg │ │ │ │ ├── box.svg │ │ │ │ ├── bulb.svg │ │ │ │ ├── code-file.svg │ │ │ │ ├── code-sandbox.svg │ │ │ │ ├── dark_to_light.svg │ │ │ │ ├── database.svg │ │ │ │ ├── doc.svg │ │ │ │ ├── empty-folder.svg │ │ │ │ ├── expression.svg │ │ │ │ ├── eye-closed.svg │ │ │ │ ├── eye-dark.svg │ │ │ │ ├── eye.svg │ │ │ │ ├── file-text.svg │ │ │ │ ├── filter.svg │ │ │ │ ├── folder.svg │ │ │ │ ├── img.svg │ │ │ │ ├── inspect.svg │ │ │ │ ├── light_to_dark.svg │ │ │ │ ├── moon.svg │ │ │ │ ├── pdf.svg │ │ │ │ ├── photo.svg │ │ │ │ ├── share.svg │ │ │ │ ├── stars.svg │ │ │ │ ├── sun.svg │ │ │ │ ├── trending-down.svg │ │ │ │ ├── trending-level.svg │ │ │ │ ├── trending-up.svg │ │ │ │ ├── txt.svg │ │ │ │ ├── unknown-file.svg │ │ │ │ ├── unlink.svg │ │ │ │ └── xls.svg │ │ │ ├── TableDeleteColumnIcon.tsx │ │ │ ├── TableDeleteRowIcon.tsx │ │ │ ├── TableInsertColumnIcon.tsx │ │ │ ├── TableInsertRowIcon.tsx │ │ │ ├── TrashIcon.tsx │ │ │ ├── TrendingDownIcon.tsx │ │ │ ├── TrendingLevelIcon.tsx │ │ │ ├── TrendingUpIcon.tsx │ │ │ ├── TxtIcon.tsx │ │ │ ├── UnknownFileIcon.tsx │ │ │ ├── UnlinkIcon.tsx │ │ │ ├── UserIcon.tsx │ │ │ ├── WarningIcon.tsx │ │ │ └── XlsIcon.tsx │ │ ├── IconProvider.tsx │ │ ├── IconRegistryContext.tsx │ │ ├── IFrame │ │ │ ├── IFrame.md │ │ │ ├── IFrame.module.scss │ │ │ ├── IFrame.spec.ts │ │ │ ├── IFrame.tsx │ │ │ └── IFrameNative.tsx │ │ ├── Image │ │ │ ├── Image.md │ │ │ ├── Image.module.scss │ │ │ ├── Image.spec.ts │ │ │ ├── Image.tsx │ │ │ └── ImageNative.tsx │ │ ├── Input │ │ │ ├── index.ts │ │ │ ├── InputAdornment.module.scss │ │ │ ├── InputAdornment.tsx │ │ │ ├── InputDivider.module.scss │ │ │ ├── InputDivider.tsx │ │ │ ├── InputLabel.module.scss │ │ │ ├── InputLabel.tsx │ │ │ ├── PartialInput.module.scss │ │ │ └── PartialInput.tsx │ │ ├── InspectButton │ │ │ ├── InspectButton.module.scss │ │ │ └── InspectButton.tsx │ │ ├── Items │ │ │ ├── Items.md │ │ │ ├── Items.spec.ts │ │ │ ├── Items.tsx │ │ │ └── ItemsNative.tsx │ │ ├── Link │ │ │ ├── Link.md │ │ │ ├── Link.module.scss │ │ │ ├── Link.spec.ts │ │ │ ├── Link.tsx │ │ │ └── LinkNative.tsx │ │ ├── List │ │ │ ├── doc-resources │ │ │ │ └── list-component-data.js │ │ │ ├── List.md │ │ │ ├── List.module.scss │ │ │ ├── List.spec.ts │ │ │ ├── List.tsx │ │ │ └── ListNative.tsx │ │ ├── Logo │ │ │ ├── doc-resources │ │ │ │ └── xmlui-logo.svg │ │ │ ├── Logo.md │ │ │ ├── Logo.tsx │ │ │ └── LogoNative.tsx │ │ ├── Markdown │ │ │ ├── CodeText.module.scss │ │ │ ├── CodeText.tsx │ │ │ ├── Markdown.md │ │ │ ├── Markdown.module.scss │ │ │ ├── Markdown.spec.ts │ │ │ ├── Markdown.tsx │ │ │ ├── MarkdownNative.tsx │ │ │ ├── parse-binding-expr.ts │ │ │ └── utils.ts │ │ ├── metadata-helpers.ts │ │ ├── ModalDialog │ │ │ ├── ConfirmationModalContextProvider.tsx │ │ │ ├── Dialog.module.scss │ │ │ ├── Dialog.tsx │ │ │ ├── ModalDialog.md │ │ │ ├── ModalDialog.module.scss │ │ │ ├── ModalDialog.spec.ts │ │ │ ├── ModalDialog.tsx │ │ │ ├── ModalDialogNative.tsx │ │ │ └── ModalVisibilityContext.tsx │ │ ├── NavGroup │ │ │ ├── NavGroup.md │ │ │ ├── NavGroup.module.scss │ │ │ ├── NavGroup.spec.ts │ │ │ ├── NavGroup.tsx │ │ │ ├── NavGroupContext.ts │ │ │ └── NavGroupNative.tsx │ │ ├── NavLink │ │ │ ├── NavLink.md │ │ │ ├── NavLink.module.scss │ │ │ ├── NavLink.spec.ts │ │ │ ├── NavLink.tsx │ │ │ └── NavLinkNative.tsx │ │ ├── NavPanel │ │ │ ├── NavPanel.md │ │ │ ├── NavPanel.module.scss │ │ │ ├── NavPanel.spec.ts │ │ │ ├── NavPanel.tsx │ │ │ └── NavPanelNative.tsx │ │ ├── NestedApp │ │ │ ├── AppWithCodeView.module.scss │ │ │ ├── AppWithCodeView.tsx │ │ │ ├── AppWithCodeViewNative.tsx │ │ │ ├── defaultProps.tsx │ │ │ ├── logo.svg │ │ │ ├── NestedApp.module.scss │ │ │ ├── NestedApp.tsx │ │ │ ├── NestedAppNative.tsx │ │ │ ├── Tooltip.module.scss │ │ │ ├── Tooltip.tsx │ │ │ └── utils.ts │ │ ├── NoResult │ │ │ ├── NoResult.md │ │ │ ├── NoResult.module.scss │ │ │ ├── NoResult.spec.ts │ │ │ ├── NoResult.tsx │ │ │ └── NoResultNative.tsx │ │ ├── NumberBox │ │ │ ├── numberbox-abstractions.ts │ │ │ ├── NumberBox.md │ │ │ ├── NumberBox.module.scss │ │ │ ├── NumberBox.spec.ts │ │ │ ├── NumberBox.tsx │ │ │ └── NumberBoxNative.tsx │ │ ├── Option │ │ │ ├── Option.md │ │ │ ├── Option.spec.ts │ │ │ ├── Option.tsx │ │ │ ├── OptionNative.tsx │ │ │ └── OptionTypeProvider.tsx │ │ ├── PageMetaTitle │ │ │ ├── PageMetaTilteNative.tsx │ │ │ ├── PageMetaTitle.md │ │ │ ├── PageMetaTitle.spec.ts │ │ │ └── PageMetaTitle.tsx │ │ ├── Pages │ │ │ ├── Page.md │ │ │ ├── Pages.md │ │ │ ├── Pages.module.scss │ │ │ ├── Pages.tsx │ │ │ └── PagesNative.tsx │ │ ├── Pagination │ │ │ ├── Pagination.md │ │ │ ├── Pagination.module.scss │ │ │ ├── Pagination.spec.ts │ │ │ ├── Pagination.tsx │ │ │ └── PaginationNative.tsx │ │ ├── PositionedContainer │ │ │ ├── PositionedContainer.module.scss │ │ │ ├── PositionedContainer.tsx │ │ │ └── PositionedContainerNative.tsx │ │ ├── ProfileMenu │ │ │ ├── ProfileMenu.module.scss │ │ │ └── ProfileMenu.tsx │ │ ├── ProgressBar │ │ │ ├── ProgressBar.md │ │ │ ├── ProgressBar.module.scss │ │ │ ├── ProgressBar.spec.ts │ │ │ ├── ProgressBar.tsx │ │ │ └── ProgressBarNative.tsx │ │ ├── Queue │ │ │ ├── Queue.md │ │ │ ├── Queue.spec.ts │ │ │ ├── Queue.tsx │ │ │ ├── queueActions.ts │ │ │ └── QueueNative.tsx │ │ ├── RadioGroup │ │ │ ├── RadioGroup.md │ │ │ ├── RadioGroup.module.scss │ │ │ ├── RadioGroup.spec.ts │ │ │ ├── RadioGroup.tsx │ │ │ ├── RadioGroupNative.tsx │ │ │ ├── RadioItem.tsx │ │ │ └── RadioItemNative.tsx │ │ ├── RealTimeAdapter │ │ │ ├── RealTimeAdapter.tsx │ │ │ └── RealTimeAdapterNative.tsx │ │ ├── Redirect │ │ │ ├── Redirect.md │ │ │ ├── Redirect.spec.ts │ │ │ └── Redirect.tsx │ │ ├── ResponsiveBar │ │ │ ├── README.md │ │ │ ├── ResponsiveBar.md │ │ │ ├── ResponsiveBar.module.scss │ │ │ ├── ResponsiveBar.spec.ts │ │ │ ├── ResponsiveBar.tsx │ │ │ └── ResponsiveBarNative.tsx │ │ ├── Select │ │ │ ├── HiddenOption.tsx │ │ │ ├── MultiSelectOption.tsx │ │ │ ├── OptionContext.ts │ │ │ ├── Select.md │ │ │ ├── Select.module.scss │ │ │ ├── Select.spec.ts │ │ │ ├── Select.tsx │ │ │ ├── SelectContext.tsx │ │ │ ├── SelectNative.tsx │ │ │ ├── SelectOption.tsx │ │ │ └── SimpleSelect.tsx │ │ ├── SelectionStore │ │ │ ├── SelectionStore.md │ │ │ ├── SelectionStore.tsx │ │ │ └── SelectionStoreNative.tsx │ │ ├── Slider │ │ │ ├── Slider.md │ │ │ ├── Slider.module.scss │ │ │ ├── Slider.spec.ts │ │ │ ├── Slider.tsx │ │ │ └── SliderNative.tsx │ │ ├── Slot │ │ │ ├── Slot.md │ │ │ ├── Slot.spec.ts │ │ │ └── Slot.ts │ │ ├── SlotItem.tsx │ │ ├── SpaceFiller │ │ │ ├── SpaceFiller.md │ │ │ ├── SpaceFiller.module.scss │ │ │ ├── SpaceFiller.spec.ts │ │ │ ├── SpaceFiller.tsx │ │ │ └── SpaceFillerNative.tsx │ │ ├── Spinner │ │ │ ├── Spinner.md │ │ │ ├── Spinner.module.scss │ │ │ ├── Spinner.spec.ts │ │ │ ├── Spinner.tsx │ │ │ └── SpinnerNative.tsx │ │ ├── Splitter │ │ │ ├── HSplitter.md │ │ │ ├── HSplitter.spec.ts │ │ │ ├── Splitter.md │ │ │ ├── Splitter.module.scss │ │ │ ├── Splitter.spec.ts │ │ │ ├── Splitter.tsx │ │ │ ├── SplitterNative.tsx │ │ │ ├── utils.ts │ │ │ ├── VSplitter.md │ │ │ └── VSplitter.spec.ts │ │ ├── Stack │ │ │ ├── CHStack.md │ │ │ ├── CHStack.spec.ts │ │ │ ├── CVStack.md │ │ │ ├── CVStack.spec.ts │ │ │ ├── HStack.md │ │ │ ├── HStack.spec.ts │ │ │ ├── Stack.md │ │ │ ├── Stack.module.scss │ │ │ ├── Stack.spec.ts │ │ │ ├── Stack.tsx │ │ │ ├── StackNative.tsx │ │ │ ├── VStack.md │ │ │ └── VStack.spec.ts │ │ ├── StickyBox │ │ │ ├── StickyBox.md │ │ │ ├── StickyBox.module.scss │ │ │ ├── StickyBox.tsx │ │ │ └── StickyBoxNative.tsx │ │ ├── Switch │ │ │ ├── Switch.md │ │ │ ├── Switch.spec.ts │ │ │ └── Switch.tsx │ │ ├── Table │ │ │ ├── doc-resources │ │ │ │ └── list-component-data.js │ │ │ ├── react-table-config.d.ts │ │ │ ├── Table.md │ │ │ ├── Table.module.scss │ │ │ ├── Table.spec.ts │ │ │ ├── Table.tsx │ │ │ ├── TableNative.tsx │ │ │ └── useRowSelection.tsx │ │ ├── TableOfContents │ │ │ ├── TableOfContents.module.scss │ │ │ ├── TableOfContents.spec.ts │ │ │ ├── TableOfContents.tsx │ │ │ └── TableOfContentsNative.tsx │ │ ├── Tabs │ │ │ ├── TabContext.tsx │ │ │ ├── TabItem.md │ │ │ ├── TabItem.tsx │ │ │ ├── TabItemNative.tsx │ │ │ ├── Tabs.md │ │ │ ├── Tabs.module.scss │ │ │ ├── Tabs.spec.ts │ │ │ ├── Tabs.tsx │ │ │ └── TabsNative.tsx │ │ ├── Text │ │ │ ├── Text.md │ │ │ ├── Text.module.scss │ │ │ ├── Text.spec.ts │ │ │ ├── Text.tsx │ │ │ └── TextNative.tsx │ │ ├── TextArea │ │ │ ├── TextArea.md │ │ │ ├── TextArea.module.scss │ │ │ ├── TextArea.spec.ts │ │ │ ├── TextArea.tsx │ │ │ ├── TextAreaNative.tsx │ │ │ ├── TextAreaResizable.tsx │ │ │ └── useComposedRef.ts │ │ ├── TextBox │ │ │ ├── TextBox.md │ │ │ ├── TextBox.module.scss │ │ │ ├── TextBox.spec.ts │ │ │ ├── TextBox.tsx │ │ │ └── TextBoxNative.tsx │ │ ├── Theme │ │ │ ├── NotificationToast.tsx │ │ │ ├── Theme.md │ │ │ ├── Theme.module.scss │ │ │ ├── Theme.spec.ts │ │ │ ├── Theme.tsx │ │ │ └── ThemeNative.tsx │ │ ├── TimeInput │ │ │ ├── TimeInput.md │ │ │ ├── TimeInput.module.scss │ │ │ ├── TimeInput.spec.ts │ │ │ ├── TimeInput.tsx │ │ │ ├── TimeInputNative.tsx │ │ │ └── utils.ts │ │ ├── Timer │ │ │ ├── Timer.md │ │ │ ├── Timer.spec.ts │ │ │ ├── Timer.tsx │ │ │ └── TimerNative.tsx │ │ ├── Toggle │ │ │ ├── Toggle.module.scss │ │ │ └── Toggle.tsx │ │ ├── ToneChangerButton │ │ │ ├── ToneChangerButton.md │ │ │ ├── ToneChangerButton.spec.ts │ │ │ └── ToneChangerButton.tsx │ │ ├── ToneSwitch │ │ │ ├── ToneSwitch.md │ │ │ ├── ToneSwitch.module.scss │ │ │ ├── ToneSwitch.spec.ts │ │ │ ├── ToneSwitch.tsx │ │ │ └── ToneSwitchNative.tsx │ │ ├── Tooltip │ │ │ ├── Tooltip.md │ │ │ ├── Tooltip.module.scss │ │ │ ├── Tooltip.spec.ts │ │ │ ├── Tooltip.tsx │ │ │ └── TooltipNative.tsx │ │ ├── Tree │ │ │ ├── testData.ts │ │ │ ├── Tree-dynamic.spec.ts │ │ │ ├── Tree-icons.spec.ts │ │ │ ├── Tree.md │ │ │ ├── Tree.spec.ts │ │ │ ├── TreeComponent.module.scss │ │ │ ├── TreeComponent.tsx │ │ │ └── TreeNative.tsx │ │ ├── TreeDisplay │ │ │ ├── TreeDisplay.md │ │ │ ├── TreeDisplay.module.scss │ │ │ ├── TreeDisplay.tsx │ │ │ └── TreeDisplayNative.tsx │ │ ├── ValidationSummary │ │ │ ├── ValidationSummary.module.scss │ │ │ └── ValidationSummary.tsx │ │ └── VisuallyHidden.tsx │ ├── components-core │ │ ├── abstractions │ │ │ ├── ComponentRenderer.ts │ │ │ ├── LoaderRenderer.ts │ │ │ ├── standalone.ts │ │ │ └── treeAbstractions.ts │ │ ├── action │ │ │ ├── actions.ts │ │ │ ├── APICall.tsx │ │ │ ├── FileDownloadAction.tsx │ │ │ ├── FileUploadAction.tsx │ │ │ ├── NavigateAction.tsx │ │ │ └── TimedAction.tsx │ │ ├── ApiBoundComponent.tsx │ │ ├── appContext │ │ │ ├── date-functions.ts │ │ │ ├── math-function.ts │ │ │ └── misc-utils.ts │ │ ├── AppContext.tsx │ │ ├── behaviors │ │ │ ├── Behavior.tsx │ │ │ └── CoreBehaviors.tsx │ │ ├── component-hooks.ts │ │ ├── ComponentDecorator.tsx │ │ ├── ComponentViewer.tsx │ │ ├── CompoundComponent.tsx │ │ ├── constants.ts │ │ ├── DebugViewProvider.tsx │ │ ├── descriptorHelper.ts │ │ ├── devtools │ │ │ ├── InspectorDialog.module.scss │ │ │ ├── InspectorDialog.tsx │ │ │ └── InspectorDialogVisibilityContext.tsx │ │ ├── EngineError.ts │ │ ├── event-handlers.ts │ │ ├── InspectorButton.module.scss │ │ ├── InspectorContext.tsx │ │ ├── interception │ │ │ ├── abstractions.ts │ │ │ ├── ApiInterceptor.ts │ │ │ ├── ApiInterceptorProvider.tsx │ │ │ ├── apiInterceptorWorker.ts │ │ │ ├── Backend.ts │ │ │ ├── Errors.ts │ │ │ ├── IndexedDb.ts │ │ │ ├── initMock.ts │ │ │ ├── InMemoryDb.ts │ │ │ ├── ReadonlyCollection.ts │ │ │ └── useApiInterceptorContext.tsx │ │ ├── loader │ │ │ ├── ApiLoader.tsx │ │ │ ├── DataLoader.tsx │ │ │ ├── ExternalDataLoader.tsx │ │ │ ├── Loader.tsx │ │ │ ├── MockLoaderRenderer.tsx │ │ │ └── PageableLoader.tsx │ │ ├── LoaderComponent.tsx │ │ ├── markup-check.ts │ │ ├── parts.ts │ │ ├── renderers.ts │ │ ├── rendering │ │ │ ├── AppContent.tsx │ │ │ ├── AppRoot.tsx │ │ │ ├── AppWrapper.tsx │ │ │ ├── buildProxy.ts │ │ │ ├── collectFnVarDeps.ts │ │ │ ├── ComponentAdapter.tsx │ │ │ ├── ComponentWrapper.tsx │ │ │ ├── Container.tsx │ │ │ ├── containers.ts │ │ │ ├── ContainerWrapper.tsx │ │ │ ├── ErrorBoundary.module.scss │ │ │ ├── ErrorBoundary.tsx │ │ │ ├── InvalidComponent.module.scss │ │ │ ├── InvalidComponent.tsx │ │ │ ├── nodeUtils.ts │ │ │ ├── reducer.ts │ │ │ ├── renderChild.tsx │ │ │ ├── StandaloneComponent.tsx │ │ │ ├── StateContainer.tsx │ │ │ ├── UnknownComponent.module.scss │ │ │ ├── UnknownComponent.tsx │ │ │ └── valueExtractor.ts │ │ ├── reportEngineError.ts │ │ ├── RestApiProxy.ts │ │ ├── script-runner │ │ │ ├── asyncProxy.ts │ │ │ ├── AttributeValueParser.ts │ │ │ ├── bannedFunctions.ts │ │ │ ├── BindingTreeEvaluationContext.ts │ │ │ ├── eval-tree-async.ts │ │ │ ├── eval-tree-common.ts │ │ │ ├── eval-tree-sync.ts │ │ │ ├── ParameterParser.ts │ │ │ ├── process-statement-async.ts │ │ │ ├── process-statement-common.ts │ │ │ ├── process-statement-sync.ts │ │ │ ├── ScriptingSourceTree.ts │ │ │ ├── simplify-expression.ts │ │ │ ├── statement-queue.ts │ │ │ └── visitors.ts │ │ ├── StandaloneApp.tsx │ │ ├── StandaloneExtensionManager.ts │ │ ├── TableOfContentsContext.tsx │ │ ├── theming │ │ │ ├── _themes.scss │ │ │ ├── component-layout-resolver.ts │ │ │ ├── extendThemeUtils.ts │ │ │ ├── hvar.ts │ │ │ ├── layout-resolver.ts │ │ │ ├── parse-layout-props.ts │ │ │ ├── StyleContext.tsx │ │ │ ├── StyleRegistry.ts │ │ │ ├── ThemeContext.tsx │ │ │ ├── ThemeProvider.tsx │ │ │ ├── themes │ │ │ │ ├── base-utils.ts │ │ │ │ ├── palette.ts │ │ │ │ ├── root.ts │ │ │ │ ├── solid.ts │ │ │ │ ├── theme-colors.ts │ │ │ │ └── xmlui.ts │ │ │ ├── themeVars.module.scss │ │ │ ├── themeVars.ts │ │ │ ├── transformThemeVars.ts │ │ │ └── utils.ts │ │ ├── utils │ │ │ ├── actionUtils.ts │ │ │ ├── audio-utils.ts │ │ │ ├── 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/conventions/component-qa-checklist.md: -------------------------------------------------------------------------------- ```markdown 1 | # XMLUI Component QA Checklist 2 | 3 | This document provides a comprehensive checklist for ensuring XMLUI components follow established conventions and best practices. Use this with GitHub Copilot to verify component compliance. 4 | 5 | ## 📋 Quick Reference 6 | 7 | **Component Location**: XMLUI components are located in the `xmlui/src/components` folder. 8 | 9 | **Component Status**: Use this checklist to verify component compliance before release. 10 | 11 | **Usage with Copilot**: Reference this document when asking Copilot to review or create XMLUI components. 12 | 13 | ## ⚠️ Component Review Scope 14 | 15 | **Skip HTML Tag Components**: HTML tag wrapper components in `HtmlTags.tsx` are marked as deprecated and scheduled for removal. **Skip these components during routine reviews** unless explicitly requested. Focus reviews on: 16 | - Core XMLUI components (Avatar, Button, Card, etc.) 17 | - Form components (TextBox, Checkbox, Select, etc.) 18 | - Layout components (Stack, App, Pages, etc.) 19 | - Advanced components (Charts, DatePicker, etc.) 20 | 21 | **Rationale**: HTML tag components are temporary solutions being phased out in favor of semantic HTML and dedicated XMLUI components. 22 | 23 | --- 24 | 25 | ## 🏗️ Component Structure 26 | 27 | ### ✅ File Organization Patterns 28 | 29 | #### Dual-File Pattern (Recommended) 30 | - [ ] **Native Component** (`ComponentNative.tsx`) 31 | - [ ] Uses `forwardRef` pattern 32 | - [ ] Contains pure React implementation 33 | - [ ] Defines `Props` type interface 34 | - [ ] Exports `defaultProps` object 35 | - [ ] Implements actual rendering logic 36 | - [ ] No XMLUI-specific dependencies 37 | 38 | - [ ] **Renderer Component** (`Component.tsx`) 39 | - [ ] Imports from `ComponentNative.tsx` 40 | - [ ] Creates metadata with `createMetadata` 41 | - [ ] Registers with `createComponentRenderer` 42 | - [ ] Defines theme variables 43 | - [ ] Maps XMLUI props to native props 44 | 45 | #### Single-File Pattern (Alternative) 46 | - [ ] **Combined Component** (`Component.tsx`) 47 | - [ ] Contains both React implementation and XMLUI renderer 48 | - [ ] Uses `createMetadata` for documentation 49 | - [ ] Registers with `createComponentRenderer` 50 | - [ ] Suitable for simple components or compositions 51 | 52 | ### ✅ File Naming Conventions 53 | - [ ] Component folder matches component name (PascalCase) 54 | - [ ] Native file: `ComponentName.tsx` or `ComponentNameNative.tsx` 55 | - [ ] Renderer file: `ComponentName.tsx` 56 | - [ ] Styles file: `ComponentName.module.scss` 57 | - [ ] Test file: `ComponentName.spec.ts` 58 | 59 | --- 60 | 61 | ## 🎯 Component Implementation 62 | 63 | ### ✅ Native Component Requirements 64 | 65 | #### forwardRef Pattern 66 | - [ ] Uses `forwardRef` with proper typing 67 | - [ ] Ref type is `Ref<any>` or specific HTML element type 68 | - [ ] Component function has descriptive name matching component 69 | 70 | ```typescript 71 | // ✅ Good 72 | export const ComponentName = forwardRef(function ComponentName( 73 | { prop1, prop2, ...rest }: Props, 74 | ref: Ref<HTMLDivElement> 75 | ) { 76 | // Implementation 77 | }); 78 | 79 | // ❌ Bad - anonymous function 80 | export const ComponentName = forwardRef((props, ref) => { 81 | // Implementation 82 | }); 83 | ``` 84 | 85 | #### Props and Default Values 86 | - [ ] `Props` type interface is defined 87 | - [ ] `defaultProps` object is exported 88 | - [ ] All props have proper TypeScript types 89 | - [ ] Optional props use `?` syntax 90 | - [ ] Default values are referenced in metadata 91 | 92 | ```typescript 93 | // ✅ Good 94 | type Props = { 95 | size?: string; 96 | name?: string; 97 | onClick?: (event: React.MouseEvent) => void; 98 | style?: CSSProperties; 99 | }; 100 | 101 | export const defaultProps: Pick<Props, 'size'> = { 102 | size: "sm", 103 | }; 104 | ``` 105 | 106 | #### Memoization 107 | - [ ] Component wrapped with `memo` for performance 108 | - [ ] Expensive calculations use `useMemo` 109 | - [ ] Event handlers use `useCallback` when appropriate 110 | 111 | ```typescript 112 | // ✅ Good 113 | export const ComponentName = memo(forwardRef(function ComponentName(props, ref) { 114 | const expensiveValue = useMemo(() => calculateValue(props.data), [props.data]); 115 | // Implementation 116 | })); 117 | ``` 118 | 119 | ### ✅ React Hooks Rules and Patterns 120 | 121 | #### Hook Usage Rules (CRITICAL) 122 | - [ ] **Only call hooks at the top level** - Never inside loops, conditions, or nested functions 123 | - [ ] **Only call hooks from React functions** - Components or custom hooks only 124 | - [ ] **Custom hooks start with "use"** - Follow naming convention for custom hooks 125 | - [ ] **Hook calls are consistent** - Same hooks called in same order on every render 126 | 127 | ```typescript 128 | // ✅ Good 129 | function ComponentName() { 130 | const [value, setValue] = useState(initialValue); 131 | const [loading, setLoading] = useState(false); 132 | 133 | useEffect(() => { 134 | // Effect logic 135 | }, []); 136 | 137 | // Component logic 138 | } 139 | 140 | // ❌ Bad - conditional hook call 141 | function ComponentName({ showFeature }) { 142 | if (showFeature) { 143 | const [value, setValue] = useState(initialValue); // ❌ Conditional hook 144 | } 145 | // Component logic 146 | } 147 | ``` 148 | 149 | #### useState Patterns 150 | - [ ] State initialized with proper default values 151 | - [ ] State updates use functional updates for dependent changes 152 | - [ ] Multiple related state values are grouped when appropriate 153 | - [ ] State is not mutated directly 154 | 155 | ```typescript 156 | // ✅ Good 157 | const [count, setCount] = useState(0); 158 | const [user, setUser] = useState({ name: '', email: '' }); 159 | 160 | // Functional update 161 | setCount(prevCount => prevCount + 1); 162 | 163 | // Object update (new object) 164 | setUser(prevUser => ({ ...prevUser, name: 'New Name' })); 165 | 166 | // ❌ Bad - mutating state directly 167 | user.name = 'New Name'; // ❌ Direct mutation 168 | setUser(user); // ❌ Same reference 169 | ``` 170 | 171 | #### useEffect Patterns 172 | - [ ] Effect cleanup functions provided when needed 173 | - [ ] Dependencies array is complete and accurate 174 | - [ ] Effects are split by concern (separate effects for different purposes) 175 | - [ ] Avoid infinite loops with proper dependency management 176 | 177 | ```typescript 178 | // ✅ Good 179 | useEffect(() => { 180 | const subscription = subscribe(something); 181 | return () => subscription.unsubscribe(); // Cleanup 182 | }, []); 183 | 184 | useEffect(() => { 185 | updateDocument(value); 186 | }, [value]); // Correct dependency 187 | 188 | // ❌ Bad - missing cleanup 189 | useEffect(() => { 190 | const interval = setInterval(() => { 191 | // Some logic 192 | }, 1000); 193 | // ❌ Missing cleanup 194 | }, []); 195 | 196 | // ❌ Bad - missing dependency 197 | useEffect(() => { 198 | updateDocument(value); 199 | }, []); // ❌ Missing 'value' dependency 200 | ``` 201 | 202 | #### useCallback and useMemo Patterns 203 | - [ ] `useCallback` used for event handlers passed to child components 204 | - [ ] `useMemo` used for expensive computations 205 | - [ ] Dependencies are properly specified 206 | - [ ] Not overused (only when needed for performance) 207 | 208 | ```typescript 209 | // ✅ Good 210 | const handleClick = useCallback((event) => { 211 | onClick(event, value); 212 | }, [onClick, value]); 213 | 214 | const expensiveValue = useMemo(() => { 215 | return heavyComputation(data); 216 | }, [data]); 217 | 218 | // ❌ Bad - unnecessary useCallback 219 | const handleClick = useCallback(() => { 220 | console.log('clicked'); 221 | }, []); // ❌ No dependencies needed for static function 222 | ``` 223 | 224 | #### Custom Hook Patterns 225 | - [ ] Custom hooks start with "use" prefix 226 | - [ ] Custom hooks encapsulate related logic 227 | - [ ] Custom hooks return consistent interface 228 | - [ ] Custom hooks follow same rules as built-in hooks 229 | 230 | ```typescript 231 | // ✅ Good 232 | function useCounter(initialValue = 0) { 233 | const [count, setCount] = useState(initialValue); 234 | 235 | const increment = useCallback(() => { 236 | setCount(prev => prev + 1); 237 | }, []); 238 | 239 | const decrement = useCallback(() => { 240 | setCount(prev => prev - 1); 241 | }, []); 242 | 243 | return { count, increment, decrement }; 244 | } 245 | 246 | // ❌ Bad - doesn't start with "use" 247 | function counter(initialValue = 0) { // ❌ Wrong naming 248 | // Hook logic 249 | } 250 | ``` 251 | 252 | #### Context and useContext Patterns 253 | - [ ] Context providers are placed at appropriate levels 254 | - [ ] Context values are memoized to prevent unnecessary re-renders 255 | - [ ] Custom hooks provide context access 256 | - [ ] Context is split by concern when appropriate 257 | 258 | ```typescript 259 | // ✅ Good 260 | const ThemeContext = createContext(); 261 | 262 | const ThemeProvider = ({ children }) => { 263 | const [theme, setTheme] = useState('light'); 264 | 265 | const value = useMemo(() => ({ 266 | theme, 267 | setTheme 268 | }), [theme]); 269 | 270 | return ( 271 | <ThemeContext.Provider value={value}> 272 | {children} 273 | </ThemeContext.Provider> 274 | ); 275 | }; 276 | 277 | // Custom hook for context access 278 | function useTheme() { 279 | const context = useContext(ThemeContext); 280 | if (!context) { 281 | throw new Error('useTheme must be used within ThemeProvider'); 282 | } 283 | return context; 284 | } 285 | ``` 286 | 287 | ### ✅ Accessibility Requirements 288 | 289 | #### ARIA Attributes 290 | - [ ] Proper `aria-label` for screen readers 291 | - [ ] `role` attribute when semantic HTML isn't sufficient 292 | - [ ] `aria-disabled` for disabled states 293 | - [ ] `aria-expanded` for expandable components 294 | 295 | #### Keyboard Navigation 296 | - [ ] Interactive elements are focusable (`tabIndex={0}`) 297 | - [ ] Non-interactive elements are not focusable 298 | - [ ] Enter and Space keys trigger actions 299 | - [ ] Escape key closes modals/dropdowns 300 | - [ ] Arrow keys for navigation where appropriate 301 | 302 | ```typescript 303 | // ✅ Good 304 | const handleKeyDown = (event: React.KeyboardEvent) => { 305 | if (onClick && (event.key === 'Enter' || event.key === ' ')) { 306 | event.preventDefault(); 307 | onClick(event as any); 308 | } 309 | }; 310 | 311 | return ( 312 | <div 313 | role="button" 314 | aria-label={altText} 315 | tabIndex={onClick ? 0 : undefined} 316 | onKeyDown={handleKeyDown} 317 | > 318 | {content} 319 | </div> 320 | ); 321 | ``` 322 | 323 | #### Focus Management 324 | - [ ] Focus states are visible 325 | - [ ] Focus is properly managed in modal dialogs 326 | - [ ] Focus returns to trigger element when closing 327 | - [ ] Skip links provided for complex navigation 328 | 329 | ### ✅ Event Handling 330 | 331 | #### Event Declaration 332 | - [ ] Events declared in component metadata 333 | - [ ] Event handlers properly typed 334 | - [ ] Event names follow convention (camelCase) 335 | 336 | #### Event Implementation 337 | - [ ] Events connected via `lookupEventHandler` 338 | - [ ] Event handlers are optional 339 | - [ ] Proper event object passed to handlers 340 | 341 | ```typescript 342 | // ✅ Good - In renderer 343 | onClick={lookupEventHandler("click")} 344 | 345 | // ✅ Good - In native component 346 | onClick?: (event: React.MouseEvent) => void; 347 | ``` 348 | 349 | #### Renderer Function Patterns 350 | - [ ] **No React hooks in renderer functions** (CRITICAL) 351 | - [ ] Renderer functions only contain JSX mapping logic 352 | - [ ] React hooks are used only in React components 353 | - [ ] Complex logic wrapped in React components when hooks needed 354 | 355 | ```typescript 356 | // ✅ Good - No hooks in renderer 357 | export const componentRenderer = createComponentRenderer( 358 | COMP, 359 | ComponentMd, 360 | ({ node, extractValue, lookupEventHandler, layoutCss }) => { 361 | return ( 362 | <ComponentNative 363 | prop1={extractValue(node.props.prop1)} 364 | prop2={extractValue(node.props.prop2)} 365 | onClick={lookupEventHandler("click")} 366 | style={layoutCss} 367 | /> 368 | ); 369 | }, 370 | ); 371 | 372 | // ✅ Good - Use React component wrapper when hooks needed 373 | const ComponentWithState = ({ initialValue, ...props }) => { 374 | const [state, setState] = useState(initialValue); 375 | 376 | useEffect(() => { 377 | // Effect logic here 378 | }, []); 379 | 380 | return <ComponentNative {...props} state={state} />; 381 | }; 382 | 383 | export const componentRenderer = createComponentRenderer( 384 | COMP, 385 | ComponentMd, 386 | ({ node, extractValue, lookupEventHandler, layoutCss }) => { 387 | return ( 388 | <ComponentWithState 389 | initialValue={extractValue(node.props.initialValue)} 390 | onClick={lookupEventHandler("click")} 391 | style={layoutCss} 392 | /> 393 | ); 394 | }, 395 | ); 396 | 397 | // ❌ Bad - Hooks directly in renderer function 398 | export const componentRenderer = createComponentRenderer( 399 | COMP, 400 | ComponentMd, 401 | ({ node, extractValue, lookupEventHandler, layoutCss }) => { 402 | const [state, setState] = useState(); // ❌ Hook in renderer function 403 | 404 | useEffect(() => { // ❌ Hook in renderer function 405 | // Effect logic 406 | }, []); 407 | 408 | return ( 409 | <ComponentNative 410 | prop1={extractValue(node.props.prop1)} 411 | style={layoutCss} 412 | /> 413 | ); 414 | }, 415 | ); 416 | ``` 417 | 418 | ### ✅ Common Events 419 | - [ ] `click` for clickable elements 420 | - [ ] `didChange` for value changes 421 | - [ ] `gotFocus` / `lostFocus` for focus events 422 | - [ ] Component-specific events documented 423 | 424 | --- 425 | 426 | ## 🎨 Styling and Theming 427 | 428 | ### ✅ SCSS Module Pattern 429 | - [ ] Component has dedicated SCSS module 430 | - [ ] Styles imported as `styles` object 431 | - [ ] Classes applied using `classnames` library 432 | - [ ] CSS variables follow naming convention 433 | 434 | ### ✅ Theme Variables 435 | - [ ] Theme variables defined in metadata 436 | - [ ] Variables follow `propertyName-ComponentName` pattern 437 | - [ ] Default theme variables provided 438 | - [ ] Variables parsed with `parseScssVar` 439 | 440 | ```typescript 441 | // ✅ Good 442 | defaultThemeVars: { 443 | [`borderRadius-${COMP}`]: "4px", 444 | [`boxShadow-${COMP}`]: "inset 0 0 0 1px rgba(4,32,69,0.1)", 445 | [`textColor-${COMP}`]: "$textColor-secondary", 446 | [`backgroundColor-${COMP}`]: "$color-surface-100", 447 | } 448 | ``` 449 | 450 | ### ✅ Responsive Design 451 | - [ ] Components adapt to different screen sizes 452 | - [ ] Breakpoints use consistent values 453 | - [ ] Text remains readable at all sizes 454 | - [ ] Touch targets meet minimum size requirements 455 | 456 | --- 457 | 458 | ## 📚 Metadata and Documentation 459 | 460 | ### ✅ Component Metadata 461 | - [ ] `createMetadata` used for documentation 462 | - [ ] Component description is comprehensive 463 | - [ ] All props documented with descriptions 464 | - [ ] Available values listed for constrained props 465 | - [ ] Default values specified 466 | - [ ] Component status indicated (`stable`, `experimental`, etc.) 467 | 468 | ### ✅ Prop Documentation 469 | - [ ] Each prop has clear description 470 | - [ ] Type information provided 471 | - [ ] Required props marked as `isRequired: true` 472 | - [ ] Available values listed for enums 473 | - [ ] Default values referenced from `defaultProps` 474 | 475 | ```typescript 476 | // ✅ Good 477 | export const ComponentMd = createMetadata({ 478 | status: "stable", 479 | description: "Clear description of component purpose and usage", 480 | props: { 481 | size: { 482 | description: "Controls the display size of the component", 483 | type: "string", 484 | availableValues: ["xs", "sm", "md", "lg"], 485 | defaultValue: defaultProps.size, 486 | }, 487 | }, 488 | }); 489 | ``` 490 | 491 | ### ✅ Event Documentation 492 | - [ ] All events documented in metadata 493 | - [ ] Event descriptions explain when triggered 494 | - [ ] Event parameter types specified 495 | 496 | --- 497 | 498 | ## 🔄 State Management 499 | 500 | ### ✅ Internal State 501 | - [ ] Component state is properly managed 502 | - [ ] State updates are batched when possible 503 | - [ ] State is synchronized with XMLUI when needed 504 | 505 | ### ✅ API Registration (Only for Interactive Components) 506 | 507 | **Note**: Most UI components correctly have no APIs. Only apply this section to components that need programmatic control (form inputs, data tables, etc.). 508 | 509 | - [ ] Component API methods registered with `registerComponentApi` (if component needs APIs) 510 | - [ ] Common methods implemented where applicable: `setValue`, `focus`, `reset` 511 | - [ ] API methods are properly typed 512 | - [ ] API documentation explains when/why to use each method 513 | 514 | ```typescript 515 | // ✅ Good - Only for components that need programmatic control 516 | useEffect(() => { 517 | registerComponentApi?.({ 518 | setValue: (value: string) => { 519 | setInternalValue(value); 520 | updateState?.({ value }); 521 | }, 522 | focus: () => inputRef.current?.focus(), 523 | }); 524 | }, [registerComponentApi, updateState]); 525 | ``` 526 | 527 | ### ✅ State Synchronization 528 | - [ ] Internal state synchronized with XMLUI 529 | - [ ] `updateState` called when values change 530 | - [ ] State updates are properly debounced if needed 531 | 532 | --- 533 | 534 | ## 🧪 Testing 535 | 536 | ### ✅ Component Driver 537 | - [ ] Component has dedicated driver class 538 | - [ ] Driver extends `ComponentDriver` 539 | - [ ] Driver provides component-specific methods 540 | - [ ] Driver used in all component tests 541 | 542 | ### ✅ Test Coverage 543 | - [ ] Basic functionality tests 544 | - [ ] Accessibility tests (REQUIRED) 545 | - [ ] Visual state tests 546 | - [ ] Edge case tests (null, undefined, special characters) 547 | - [ ] Performance tests (memoization, rapid changes) 548 | - [ ] Integration tests (layout contexts) 549 | 550 | ### ✅ Test Organization 551 | - [ ] Tests grouped by category with section headers 552 | - [ ] Descriptive test names 553 | - [ ] Tests are independent and isolated 554 | - [ ] Tests use proper assertions 555 | 556 | --- 557 | 558 | ## 📦 Component Registration 559 | 560 | ### ✅ Registration Pattern 561 | - [ ] Component registered in `ComponentRegistry` 562 | - [ ] Conditional registration based on environment 563 | - [ ] Component name matches metadata 564 | 565 | ```typescript 566 | // ✅ Good 567 | if (process.env.VITE_USED_COMPONENTS_ComponentName !== "false") { 568 | this.registerCoreComponent(componentNameComponentRenderer); 569 | } 570 | ``` 571 | 572 | --- 573 | 574 | ## 🚀 Performance 575 | 576 | ### ✅ Optimization Patterns 577 | - [ ] Component uses `memo` for re-render prevention 578 | - [ ] Expensive calculations are memoized 579 | - [ ] Event handlers are stable references 580 | - [ ] Avoid unnecessary re-renders 581 | - [ ] Hook dependencies are optimized 582 | - [ ] Context values are memoized 583 | 584 | ### ✅ Hook Performance Best Practices 585 | - [ ] `useCallback` dependencies are minimal and stable 586 | - [ ] `useMemo` is used for expensive computations only 587 | - [ ] Context providers memoize their values 588 | - [ ] Effects are split to minimize re-runs 589 | - [ ] State updates are batched when possible 590 | 591 | ```typescript 592 | // ✅ Good Hook Performance 593 | const ComponentName = memo(({ items, onItemClick }) => { 594 | // Memoize expensive computation 595 | const processedItems = useMemo(() => { 596 | return items.map(item => expensiveProcessing(item)); 597 | }, [items]); 598 | 599 | // Stable event handler 600 | const handleItemClick = useCallback((item) => { 601 | onItemClick(item); 602 | }, [onItemClick]); 603 | 604 | // Split effects by concern 605 | useEffect(() => { 606 | trackPageView(); 607 | }, []); // Only on mount 608 | 609 | useEffect(() => { 610 | updateItems(processedItems); 611 | }, [processedItems]); // Only when items change 612 | 613 | return ( 614 | <div> 615 | {processedItems.map(item => ( 616 | <Item key={item.id} data={item} onClick={handleItemClick} /> 617 | ))} 618 | </div> 619 | ); 620 | }); 621 | ``` 622 | 623 | ### ✅ Memory Management 624 | - [ ] Event listeners properly cleaned up 625 | - [ ] Subscriptions disposed in cleanup 626 | - [ ] No memory leaks in component lifecycle 627 | - [ ] Effect cleanup functions implemented 628 | - [ ] Timers and intervals cleared 629 | - [ ] AbortController used for cancellable requests 630 | 631 | ```typescript 632 | // ✅ Good Memory Management 633 | function ComponentName() { 634 | useEffect(() => { 635 | // Event listener cleanup 636 | const handleResize = () => updateLayout(); 637 | window.addEventListener('resize', handleResize); 638 | return () => window.removeEventListener('resize', handleResize); 639 | }, []); 640 | 641 | useEffect(() => { 642 | // Timer cleanup 643 | const timer = setTimeout(() => performAction(), 1000); 644 | return () => clearTimeout(timer); 645 | }, []); 646 | 647 | useEffect(() => { 648 | // Subscription cleanup 649 | const subscription = dataService.subscribe(handleData); 650 | return () => subscription.unsubscribe(); 651 | }, []); 652 | 653 | useEffect(() => { 654 | // Request cancellation 655 | const controller = new AbortController(); 656 | 657 | fetchData({ signal: controller.signal }) 658 | .then(handleData) 659 | .catch(error => { 660 | if (error.name !== 'AbortError') { 661 | handleError(error); 662 | } 663 | }); 664 | 665 | return () => controller.abort(); 666 | }, []); 667 | } 668 | ``` 669 | 670 | --- 671 | 672 | ## 📊 Component Status and Lifecycle 673 | 674 | ### ✅ Status Declaration 675 | - [ ] Component has explicit status in metadata (`status: "experimental" | "stable" | "deprecated"`) 676 | - [ ] Status is appropriate for component maturity level 677 | - [ ] Breaking changes are documented in status transitions 678 | - [ ] Experimental components have clear graduation criteria 679 | - [ ] Deprecated components include migration guidance 680 | 681 | ### ✅ Documentation Requirements by Status 682 | - [ ] **Experimental**: Clear warnings about API instability 683 | - [ ] **Stable**: Comprehensive documentation and examples 684 | - [ ] **Deprecated**: Deprecation timeline and alternatives 685 | 686 | ### ⚠️ Special Handling for Deprecated Components 687 | - [ ] **HTML Tag Components**: All components in `HtmlTags.tsx` are deprecated 688 | - [ ] Skip during routine component reviews 689 | - [ ] Do not enhance or extend these components 690 | - [ ] Direct new development to semantic HTML or dedicated XMLUI components 691 | - [ ] Include migration guidance in documentation 692 | 693 | **Example:** 694 | ```typescript 695 | export const ComponentMd = createMetadata({ 696 | status: "stable", // or "experimental" | "deprecated" 697 | description: "Component description with status context...", 698 | // ... rest of metadata 699 | }); 700 | ``` 701 | 702 | --- 703 | 704 | ## 🎨 Template and Customization Standards 705 | 706 | ### ✅ Template Property Patterns 707 | - [ ] Template properties follow `*Template` suffix naming convention 708 | - [ ] **Template properties use `dComponent()` metadata helper** (CRITICAL) 709 | - [ ] Template documentation includes expected data structure 710 | - [ ] Context variables are documented (e.g., `$item`, `$index`, `$itemContext`) 711 | - [ ] Template examples are provided in component documentation 712 | - [ ] Template fallbacks are properly handled in implementation 713 | 714 | ### ✅ Template Import Requirements 715 | - [ ] `dComponent` imported from `../metadata-helpers` when templates are used 716 | - [ ] Import statement includes `dComponent` alongside other helpers 717 | 718 | ```typescript 719 | // ✅ Good - Import dComponent when using templates 720 | import { 721 | createMetadata, 722 | d, 723 | dComponent, // Required for template properties 724 | dEnabled, 725 | dLabel 726 | } from "../metadata-helpers"; 727 | ``` 728 | 729 | ### ✅ Template Property Documentation Standards 730 | - [ ] **All template properties use `dComponent()` consistently** (avoid `d()` for templates) 731 | - [ ] Properties requiring additional flags use spread operator pattern 732 | - [ ] Context variables are explicitly documented in descriptions 733 | - [ ] Internal templates are marked appropriately 734 | 735 | **✅ Correct Template Property Patterns:** 736 | ```typescript 737 | props: { 738 | // Standard template property 739 | optionTemplate: dComponent( 740 | `Template for rendering dropdown options. Context: $item (option data).` 741 | ), 742 | 743 | // Empty state template 744 | emptyListTemplate: dComponent( 745 | `Template shown when no options are available. No additional context.` 746 | ), 747 | 748 | // Template with additional properties 749 | tabTemplate: { 750 | ...dComponent( 751 | `Template for clickable tab area. Context: $tab (tab data).` 752 | ), 753 | isInternal: true, 754 | }, 755 | 756 | // Template with complex context 757 | valueTemplate: dComponent( 758 | `Template for selected values in multi-select. Context: $item (selected item), $itemContext ({ removeItem }).` 759 | ), 760 | } 761 | ``` 762 | 763 | **❌ Incorrect Template Property Patterns:** 764 | ```typescript 765 | props: { 766 | // ❌ Wrong - using d() instead of dComponent() 767 | emptyListTemplate: d( 768 | "Template for empty state." 769 | ), 770 | 771 | // ❌ Wrong - plain object instead of dComponent() 772 | tabTemplate: { 773 | description: "Template for tabs.", 774 | valueType: "ComponentDef", 775 | isInternal: true, 776 | }, 777 | 778 | // ❌ Wrong - missing context variable documentation 779 | optionTemplate: dComponent( 780 | `Template for options.` // Missing $item context info 781 | ), 782 | } 783 | ``` 784 | 785 | ### ✅ Template Implementation 786 | - [ ] Templates receive appropriate context data 787 | - [ ] Template rendering uses `MemoizedItem` for performance 788 | - [ ] Template components handle missing data gracefully 789 | - [ ] Custom templates maintain accessibility standards 790 | - [ ] Context variables are properly passed to templates 791 | 792 | **Template Usage Pattern:** 793 | ```typescript 794 | // ✅ Good - Template rendering with context 795 | valueRenderer={ 796 | node.props.valueTemplate 797 | ? (item, removeItem) => { 798 | return ( 799 | <MemoizedItem 800 | contextVars={{ 801 | $item: item, 802 | $itemContext: { removeItem }, 803 | }} 804 | node={node.props.valueTemplate} 805 | item={item} 806 | renderChild={renderChild} 807 | /> 808 | ); 809 | } 810 | : undefined 811 | } 812 | ``` 813 | 814 | ### ✅ Template Property Compliance Checklist 815 | - [ ] **No template properties use `d()` helper** (should be `dComponent()`) 816 | - [ ] **No template properties use plain objects** (should use `dComponent()`) 817 | - [ ] **All template imports include `dComponent`** from metadata-helpers 818 | - [ ] **Context variables documented** in all template descriptions 819 | - [ ] **Spread operator used** for templates with additional properties 820 | - [ ] **Template names end with `Template`** suffix consistently 821 | 822 | --- 823 | 824 | ## 🔧 Wrapper Component Guidelines 825 | 826 | ### ✅ Wrapper Component Standards 827 | - [ ] Simple re-exports are justified (avoid unnecessary wrappers) 828 | - [ ] Wrapper components maintain proper TypeScript exports 829 | - [ ] Wrapper components preserve original component metadata 830 | - [ ] Complex wrappers use proper `createComponentRenderer` pattern 831 | - [ ] Wrapper purpose is clearly documented 832 | 833 | ### ✅ When to Use Wrappers 834 | - [ ] **Valid**: Legacy compatibility layers 835 | - [ ] **Valid**: Component composition with added functionality 836 | - [ ] **Invalid**: Simple re-exports without added value 837 | - [ ] **Invalid**: Breaking TypeScript type chains 838 | 839 | --- 840 | 841 | ## 📊 Data Component Standards 842 | 843 | ### ✅ Data Fetching Components 844 | - [ ] Loading states are implemented with proper UI feedback 845 | - [ ] Error handling provides meaningful user feedback 846 | - [ ] Data components support polling/refresh patterns when appropriate 847 | - [ ] Result selectors are documented with usage examples 848 | - [ ] Caching strategies are clearly defined and implemented 849 | - [ ] Data transformation patterns are consistent across components 850 | 851 | ### ✅ Data Flow Patterns 852 | - [ ] Components follow unidirectional data flow 853 | - [ ] Data mutations are handled through proper callbacks 854 | - [ ] Component state is minimized in favor of props 855 | - [ ] Data validation occurs at appropriate boundaries 856 | 857 | **Example:** 858 | ```typescript 859 | props: { 860 | resultSelector: d( 861 | "Extract subset of response data. Example: 'data.items' for nested arrays." 862 | ), 863 | pollIntervalInSeconds: d( 864 | "Polling interval for real-time updates. Set to 0 to disable polling." 865 | ), 866 | } 867 | ``` 868 | 869 | --- 870 | 871 | ## 🌐 API and Data Manipulation Standards 872 | 873 | ### ✅ API Component Requirements 874 | - [ ] **HTTP methods handled comprehensively** (GET, POST, PUT, DELETE, PATCH) 875 | - [ ] **Request/response lifecycle documented** with clear event patterns 876 | - [ ] **Confirmation dialogs supported** for destructive operations 877 | - [ ] **Optimistic updates implemented** where appropriate 878 | - [ ] **Error handling comprehensive** with user-friendly messages 879 | - [ ] **Loading states managed** with proper UI feedback 880 | 881 | ### ✅ API Component Event Lifecycle 882 | - [ ] **beforeRequest**: Validation and preparation phase 883 | - [ ] **progress**: Loading state and intermediate updates 884 | - [ ] **success**: Successful completion handling 885 | - [ ] **error**: Error handling and user notification 886 | 887 | **Example:** 888 | ```typescript 889 | export const APICallMd = createMetadata({ 890 | description: "API component for data manipulation operations", 891 | events: { 892 | beforeRequest: d("Fired before request execution for validation"), 893 | progress: d("Fired during request processing for progress indication"), 894 | success: d("Fired on successful completion with response data"), 895 | error: d("Fired on error with error details for user feedback"), 896 | }, 897 | // ... rest of metadata 898 | }); 899 | ``` 900 | 901 | ### ✅ Data Fetching and Caching 902 | - [ ] **Polling patterns supported** with configurable intervals 903 | - [ ] **Cache invalidation strategies** clearly defined 904 | - [ ] **Result selectors documented** with usage examples 905 | - [ ] **Query parameter handling** comprehensive 906 | - [ ] **Data transformation patterns** consistent 907 | 908 | --- 909 | 910 | ## 📊 Chart and Visualization Standards 911 | 912 | ### ✅ Chart Component Requirements 913 | - [ ] **Data visualization accessibility** for screen readers 914 | - [ ] **Responsive chart layouts** for different screen sizes 915 | - [ ] **Consistent data key patterns** across chart types 916 | - [ ] **Comprehensive theming support** following XMLUI standards 917 | - [ ] **Performance optimized** for large datasets 918 | - [ ] **Interactive elements** properly accessible 919 | 920 | ### ✅ Chart Data Patterns 921 | - [ ] **Data structure documented** with clear examples 922 | - [ ] **Data validation implemented** with proper error handling 923 | - [ ] **Data transformation utilities** available and documented 924 | - [ ] **Color schemes accessible** with proper contrast ratios 925 | - [ ] **Animation performance** optimized for smooth interactions 926 | 927 | **Example:** 928 | ```typescript 929 | export const BarChartMd = createMetadata({ 930 | status: "experimental", 931 | description: "Bar chart component for data visualization", 932 | props: { 933 | data: d("Array of objects with consistent data structure"), 934 | dataKeys: d("Keys to extract values - e.g., 'value', 'category'"), 935 | accessibility: d("Screen reader descriptions for chart elements"), 936 | }, 937 | // ... chart-specific metadata 938 | }); 939 | ``` 940 | 941 | --- 942 | 943 | ## 📝 Rich Content Component Standards 944 | 945 | ### ✅ Content Processing Requirements 946 | - [ ] **Content sanitization implemented** to prevent XSS attacks 947 | - [ ] **Keyboard navigation support** for rich content areas 948 | - [ ] **Undo/redo functionality** for editor components 949 | - [ ] **Large document performance** optimized with virtualization 950 | - [ ] **Content accessibility standards** followed (WCAG compliance) 951 | - [ ] **Export/import capabilities** documented and tested 952 | 953 | ### ✅ Editor Component Patterns 954 | - [ ] **Editor state management** follows controlled component patterns 955 | - [ ] **Plugin architecture supported** for extensibility 956 | - [ ] **Content validation** implemented with user feedback 957 | - [ ] **Auto-save functionality** with conflict resolution 958 | - [ ] **Collaborative editing** considerations documented 959 | 960 | **Example:** 961 | ```typescript 962 | export const MarkdownMd = createMetadata({ 963 | description: "Rich markdown content processor with syntax highlighting", 964 | props: { 965 | content: d("Markdown content with security sanitization"), 966 | removeIndents: d("Auto-format content for display consistency"), 967 | showHeadingAnchors: d("Navigation aids for long documents"), 968 | }, 969 | // ... content-specific metadata 970 | }); 971 | ``` 972 | 973 | --- 974 | 975 | ## 🔌 External Library Integration Standards 976 | 977 | ### ✅ Integration Requirements 978 | - [ ] **External dependencies documented** with version compatibility 979 | - [ ] **Library integrations follow XMLUI patterns** consistently 980 | - [ ] **Theme consistency maintained** across external components 981 | - [ ] **Library updates versioned and tested** systematically 982 | - [ ] **Integration error handling** graceful with fallbacks 983 | - [ ] **Bundle size impact** documented and minimized 984 | 985 | ### ✅ Third-Party Component Wrapper Patterns 986 | - [ ] **Wrapper components use createComponentRenderer** when possible 987 | - [ ] **External component props mapped** to XMLUI conventions 988 | - [ ] **Error boundaries implemented** for external component failures 989 | - [ ] **Performance considerations** documented for heavy libraries 990 | - [ ] **Alternative implementations** considered for critical features 991 | 992 | **Example:** 993 | ```typescript 994 | // TipTap editor integration 995 | export const TableEditorMd = createMetadata({ 996 | description: "Rich table editor using TipTap library", 997 | externalDependencies: ["@tiptap/react", "@tiptap/starter-kit"], 998 | props: { 999 | // Map external props to XMLUI patterns 1000 | }, 1001 | // ... integration metadata 1002 | }); 1003 | ``` 1004 | 1005 | --- 1006 | 1007 | ## ⚛️ React-Only Component Guidelines 1008 | 1009 | ### ✅ Non-XMLUI Component Standards 1010 | - [ ] **Pure React components justified** with clear reasoning 1011 | - [ ] **Theme integration maintained** despite bypassing XMLUI 1012 | - [ ] **Documentation explains** why XMLUI patterns are not used 1013 | - [ ] **React best practices followed** consistently 1014 | - [ ] **Components marked clearly** as non-XMLUI in documentation 1015 | 1016 | ### ✅ When React-Only is Acceptable 1017 | - [ ] **Complex state management** not suited to XMLUI patterns 1018 | - [ ] **Heavy external library integration** requiring direct React usage 1019 | - [ ] **Performance-critical components** needing direct React optimizations 1020 | - [ ] **Legacy integration** requiring specific React patterns 1021 | 1022 | **Example:** 1023 | ```typescript 1024 | // ProfileMenu - React-only component 1025 | export const ProfileMenu = ({ loggedInUser }: Props) => { 1026 | // Direct React implementation for complex dropdown logic 1027 | // Note: This bypasses XMLUI createComponentRenderer pattern 1028 | // Justification: Complex user state management and theme switching 1029 | return ( 1030 | <DropdownMenu triggerTemplate={<Avatar {...props} />}> 1031 | {/* Complex menu logic */} 1032 | </DropdownMenu> 1033 | ); 1034 | }; 1035 | ``` 1036 | 1037 | ### ❌ React-Only Antipatterns 1038 | - [ ] **Avoid**: Creating React-only components when XMLUI patterns would work 1039 | - [ ] **Avoid**: Bypassing theme system without justification 1040 | - [ ] **Avoid**: Missing documentation for architecture decisions 1041 | - [ ] **Avoid**: Inconsistent patterns within the same feature area 1042 | 1043 | --- 1044 | 1045 | ## 📏 Component File Size and Modularization 1046 | 1047 | ### ✅ File Size Management 1048 | - [ ] **Individual component files under 500 lines** (excluding generated content) 1049 | - [ ] **Large component collections modularized** by logical groups 1050 | - [ ] **Utility files separated** from component definitions 1051 | - [ ] **Generated content uses build-time generation** when possible 1052 | - [ ] **Monolithic files identified** and refactoring planned 1053 | 1054 | ### ✅ Modularization Strategies 1055 | - [ ] **Related components grouped** in logical folders 1056 | - [ ] **Shared utilities extracted** to common modules 1057 | - [ ] **Registration files modular** to avoid massive imports 1058 | - [ ] **Component dependencies minimized** through proper separation 1059 | 1060 | **Example - HtmlTags Modularization:** 1061 | ```typescript 1062 | // Instead of 2,500-line HtmlTags.tsx, create: 1063 | // components/HtmlTags/TextTags.tsx 1064 | // components/HtmlTags/MediaTags.tsx 1065 | // components/HtmlTags/FormTags.tsx 1066 | // components/HtmlTags/index.ts (barrel export) 1067 | 1068 | export const textTagRenderers = { 1069 | htmlATagRenderer, 1070 | htmlPTagRenderer, 1071 | htmlSpanTagRenderer, 1072 | // ... text-related tags 1073 | }; 1074 | 1075 | export const mediaTagRenderers = { 1076 | htmlImgTagRenderer, 1077 | htmlVideoTagRenderer, 1078 | htmlAudioTagRenderer, 1079 | // ... media-related tags 1080 | }; 1081 | ``` 1082 | 1083 | ### ❌ File Size Antipatterns 1084 | - [ ] **Avoid**: Single files exceeding 500 lines without justification 1085 | - [ ] **Avoid**: Mixing unrelated functionality in single files 1086 | - [ ] **Avoid**: Generated content committed without build process 1087 | - [ ] **Avoid**: Monolithic registration files 1088 | 1089 | --- 1090 | 1091 | ## 🧩 Child Component and Sub-Component Patterns 1092 | 1093 | ### ✅ Child Component Standards 1094 | - [ ] **Child components use dedicated files** when exceeding 50 lines 1095 | - [ ] **Consistent naming pattern** (ParentChild format) 1096 | - [ ] **Parent relationship documented** in component metadata 1097 | - [ ] **Independent metadata maintained** for each child component 1098 | - [ ] **Parent-child context passing documented** with examples 1099 | 1100 | ### ✅ Sub-Component Architecture 1101 | - [ ] **Child component registration** handled properly with parent 1102 | - [ ] **Shared styling and theming** consistent between parent and children 1103 | - [ ] **Child component lifecycle** managed by parent appropriately 1104 | - [ ] **Context boundaries clear** between parent and child responsibilities 1105 | 1106 | **Example:** 1107 | ```typescript 1108 | // TabItem - Child component for Tabs 1109 | export const TabItemMd = createMetadata({ 1110 | description: "Individual tab within Tabs component", 1111 | docFolder: "Tabs", // Groups with parent documentation 1112 | parentComponent: "Tabs", 1113 | props: { 1114 | label: dLabel("Tab header text displayed in tab bar"), 1115 | }, 1116 | }); 1117 | ``` 1118 | 1119 | ### ❌ Child Component Antipatterns 1120 | - [ ] **Avoid**: Inconsistent approaches to parent-child relationships 1121 | - [ ] **Avoid**: Large child components inlined in parent files 1122 | - [ ] **Avoid**: Missing documentation of parent-child contracts 1123 | - [ ] **Avoid**: Tight coupling preventing child component reuse 1124 | 1125 | --- 1126 | 1127 | ## 📖 Context Variable Documentation 1128 | 1129 | ### ✅ Context Variable Standards 1130 | - [ ] **All template components document** available context variables 1131 | - [ ] **Context variables use `$variable` naming** convention consistently 1132 | - [ ] **Variable types and descriptions provided** for all context data 1133 | - [ ] **Usage examples included** in component documentation 1134 | - [ ] **Context variable scope clearly defined** for nested components 1135 | 1136 | ### ✅ Template Context Patterns 1137 | - [ ] **Standard context variables** used consistently (`$item`, `$index`, `$value`) 1138 | - [ ] **Component-specific context** documented with clear examples 1139 | - [ ] **Context data transformation** explained when applicable 1140 | - [ ] **Context performance implications** considered for large datasets 1141 | 1142 | **Example:** 1143 | ```typescript 1144 | export const ColumnMd = createMetadata({ 1145 | description: "Table column with rich context support", 1146 | contextVars: { 1147 | $item: { 1148 | description: "The complete data row object being rendered", 1149 | type: "object", 1150 | example: "{ id: 1, name: 'John', email: '[email protected]' }", 1151 | }, 1152 | $cell: { 1153 | description: "The specific cell value for this column", 1154 | type: "any", 1155 | example: "'John' (when bindTo='name')", 1156 | }, 1157 | $itemIndex: { 1158 | description: "Zero-based row index", 1159 | type: "number", 1160 | example: "0, 1, 2, ...", 1161 | }, 1162 | $colIndex: { 1163 | description: "Zero-based column index", 1164 | type: "number", 1165 | example: "0, 1, 2, ...", 1166 | }, 1167 | }, 1168 | // ... rest of metadata 1169 | }); 1170 | ``` 1171 | 1172 | ### ❌ Context Variable Antipatterns 1173 | - [ ] **Avoid**: Missing context variable documentation for template components 1174 | - [ ] **Avoid**: Inconsistent context variable naming across components 1175 | - [ ] **Avoid**: Undocumented context data transformation 1176 | - [ ] **Avoid**: Context variables without type information 1177 | 1178 | --- 1179 | ``` -------------------------------------------------------------------------------- /xmlui/src/components/TextBox/TextBox.spec.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { getBounds, SKIP_REASON } from "../../testing/component-test-helpers"; 2 | import { test, expect } from "../../testing/fixtures"; 3 | 4 | // ============================================================================= 5 | // BASIC FUNCTIONALITY TESTS 6 | // ============================================================================= 7 | 8 | test.describe("Basic Functionality", () => { 9 | test("component renders", async ({ initTestBed, createTextBoxDriver }) => { 10 | await initTestBed(`<TextBox testId="test" />`); 11 | const driver = await createTextBoxDriver("test"); 12 | await expect(driver.component).toBeVisible(); 13 | }); 14 | 15 | test("component renders with label", async ({ initTestBed, createTextBoxDriver }) => { 16 | await initTestBed(`<TextBox testId="test" label="Username" />`); 17 | const driver = await createTextBoxDriver("test"); 18 | await expect(driver.component).toBeVisible(); 19 | await expect(driver.label).toBeVisible(); 20 | }); 21 | 22 | test("initialValue sets field value", async ({ initTestBed, createTextBoxDriver }) => { 23 | await initTestBed(`<TextBox testId="test" initialValue="test value" />`); 24 | const driver = await createTextBoxDriver("test"); 25 | await expect(driver.input).toHaveValue("test value"); 26 | }); 27 | 28 | test("initialValue accepts empty as empty string", async ({ initTestBed, createTextBoxDriver }) => { 29 | await initTestBed(`<TextBox testId="test" initialValue="" />`); 30 | const driver = await createTextBoxDriver("test"); 31 | await expect(driver.input).toHaveValue(""); 32 | }); 33 | 34 | test("initialValue accepts different data types", async ({ initTestBed, createTextBoxDriver }) => { 35 | // Test string 36 | await initTestBed(`<TextBox testId="test" initialValue="hello" />`); 37 | const driver1 = await createTextBoxDriver("test"); 38 | await expect(driver1.input).toHaveValue("hello"); 39 | 40 | // Test number 41 | await initTestBed(`<TextBox testId="test" initialValue="{123}" />`); 42 | const driver2 = await createTextBoxDriver("test"); 43 | await expect(driver2.input).toHaveValue("123"); 44 | 45 | // Test boolean 46 | await initTestBed(`<TextBox testId="test" initialValue="{true}" />`); 47 | const driver3 = await createTextBoxDriver("test"); 48 | await expect(driver3.input).toHaveValue("true"); 49 | }); 50 | 51 | test("initialValue handles null", async ({ initTestBed, createTextBoxDriver }) => { 52 | await initTestBed(`<TextBox testId="test" initialValue="{null}" />`); 53 | const driver = await createTextBoxDriver("test"); 54 | await expect(driver.input).toHaveValue(""); 55 | }); 56 | 57 | test("initialValue handles undefined", async ({ initTestBed, createTextBoxDriver }) => { 58 | await initTestBed(`<TextBox testId="test" initialValue="{undefined}" />`); 59 | const driver = await createTextBoxDriver("test"); 60 | await expect(driver.input).toHaveValue(""); 61 | }); 62 | 63 | test("component accepts user input", async ({ initTestBed, createTextBoxDriver }) => { 64 | await initTestBed(`<TextBox testId="test" />`); 65 | const driver = await createTextBoxDriver("test"); 66 | await driver.input.fill("user input"); 67 | await expect(driver.input).toHaveValue("user input"); 68 | }); 69 | 70 | test("component clears input correctly", async ({ initTestBed, createTextBoxDriver }) => { 71 | await initTestBed(`<TextBox testId="test" initialValue="initial text" />`); 72 | const driver = await createTextBoxDriver("test"); 73 | await expect(driver.input).toHaveValue("initial text"); 74 | await driver.input.clear(); 75 | await expect(driver.input).toHaveValue(""); 76 | }); 77 | 78 | test("component required prop adds required attribute", async ({ initTestBed, createTextBoxDriver }) => { 79 | await initTestBed(`<TextBox testId="test" required="true" />`); 80 | const driver = await createTextBoxDriver("test"); 81 | await expect(driver.input).toHaveAttribute("required"); 82 | }); 83 | 84 | test("enabled=false disables control", async ({ initTestBed, createTextBoxDriver }) => { 85 | await initTestBed(`<TextBox testId="test" enabled="false" />`); 86 | const driver = await createTextBoxDriver("test"); 87 | await expect(driver.input).toBeDisabled(); 88 | }); 89 | 90 | test("enabled=false prevents user input", async ({ initTestBed, createTextBoxDriver }) => { 91 | await initTestBed(`<TextBox testId="test" enabled="false" />`); 92 | const driver = await createTextBoxDriver("test"); 93 | await expect(driver.input).not.toBeEditable(); 94 | }); 95 | 96 | test("readOnly prevents editing but allows focus", async ({ initTestBed, createTextBoxDriver }) => { 97 | await initTestBed(`<TextBox testId="test" readOnly="true" initialValue="read only text" />`); 98 | const driver = await createTextBoxDriver("test"); 99 | await expect(driver.input).toHaveAttribute("readonly"); 100 | await expect(driver.input).toHaveValue("read only text"); 101 | await expect(driver.input).not.toBeEditable(); 102 | 103 | await driver.input.focus(); 104 | await expect(driver.input).toBeFocused(); 105 | }); 106 | 107 | test("autoFocus focuses input on mount", async ({ initTestBed, createTextBoxDriver }) => { 108 | await initTestBed(`<TextBox testId="test" autoFocus="true" />`); 109 | const driver = await createTextBoxDriver("test"); 110 | await expect(driver.input).toBeFocused(); 111 | }); 112 | 113 | test("autoFocus focuses input on mount with label", async ({ initTestBed, createTextBoxDriver }) => { 114 | await initTestBed(`<TextBox testId="test" autoFocus="true" label="Auto-focused input" />`); 115 | const driver = await createTextBoxDriver("test"); 116 | await expect(driver.input).toBeFocused(); 117 | }); 118 | 119 | test("placeholder shows when input is empty", async ({ initTestBed, createTextBoxDriver }) => { 120 | await initTestBed(`<TextBox testId="test" placeholder="Enter text here..." />`); 121 | const driver = await createTextBoxDriver("test"); 122 | await expect(driver.input).toHaveAttribute("placeholder", "Enter text here..."); 123 | }); 124 | 125 | test("maxLength limits input length", async ({ initTestBed, createTextBoxDriver }) => { 126 | await initTestBed(`<TextBox testId="test" maxLength="5" />`); 127 | const driver = await createTextBoxDriver("test"); 128 | await driver.input.fill("12345678"); 129 | await expect(driver.input).toHaveValue("12345"); 130 | }); 131 | 132 | test("can render startIcon", async ({ initTestBed, createTextBoxDriver }) => { 133 | await initTestBed(`<TextBox testId="test" startIcon="search" />`); 134 | const driver = await createTextBoxDriver("test"); 135 | await expect(driver.startAdornment).toBeVisible(); 136 | }); 137 | 138 | test("can render endIcon", async ({ initTestBed, createTextBoxDriver }) => { 139 | await initTestBed(`<TextBox testId="test" endIcon="search" />`); 140 | const driver = await createTextBoxDriver("test"); 141 | await expect(driver.endAdornment).toBeVisible(); 142 | }); 143 | }); 144 | 145 | // ============================================================================= 146 | // ACCESSIBILITY TESTS 147 | // ============================================================================= 148 | 149 | test.describe("Accessibility", () => { 150 | test("label is properly associated with input", async ({ initTestBed, createTextBoxDriver }) => { 151 | await initTestBed(`<TextBox testId="test" label="Username" />`); 152 | const driver = await createTextBoxDriver("test"); 153 | await expect(driver.label).toBeVisible(); 154 | }); 155 | 156 | test("component supports keyboard navigation", async ({ initTestBed, createTextBoxDriver }) => { 157 | await initTestBed(`<TextBox testId="test" label="Input" />`); 158 | const driver = await createTextBoxDriver("test"); 159 | await driver.input.focus(); 160 | await expect(driver.input).toBeFocused(); 161 | }); 162 | 163 | test("component supports keyboard navigation from other elements", async ({ 164 | initTestBed, 165 | page, 166 | createTextBoxDriver, 167 | }) => { 168 | await initTestBed(` 169 | <Fragment> 170 | <TextBox testId="first-input" label="First input" /> 171 | <TextBox testId="second-input" label="Second input" /> 172 | </Fragment> 173 | `); 174 | const firstInput = await createTextBoxDriver("first-input"); 175 | const secondInput = await createTextBoxDriver("second-input"); 176 | 177 | await firstInput.input.focus(); 178 | await expect(firstInput.input).toBeFocused(); 179 | 180 | await page.keyboard.press("Tab", { delay: 100 }); 181 | await expect(secondInput.input).toBeFocused(); 182 | }); 183 | 184 | test("required has proper ARIA attributes", async ({ initTestBed, createTextBoxDriver }) => { 185 | await initTestBed(`<TextBox testId="test" required="true" label="Required input" />`); 186 | const driver = await createTextBoxDriver("test"); 187 | await expect(driver.input).toHaveAttribute("required"); 188 | await expect(driver.label).toContainText("*"); 189 | }); 190 | 191 | test("disabled component has proper attribute", async ({ initTestBed, createTextBoxDriver }) => { 192 | await initTestBed(`<TextBox testId="test" enabled="false" />`); 193 | const driver = await createTextBoxDriver("test"); 194 | await expect(driver.input).toHaveAttribute("disabled"); 195 | }); 196 | 197 | test("readOnly has proper ARIA attributes", async ({ initTestBed, createTextBoxDriver }) => { 198 | await initTestBed(`<TextBox testId="test" readOnly="true" label="Read-only input" />`); 199 | const driver = await createTextBoxDriver("test"); 200 | await expect(driver.input).toHaveAttribute("readonly"); 201 | await expect(driver.label).toContainText("Read-only input"); 202 | await expect(driver.input).not.toBeEditable(); 203 | }); 204 | }); 205 | 206 | // ============================================================================= 207 | // LABEL POSITIONING TESTS 208 | // ============================================================================= 209 | 210 | test.describe("Label", () => { 211 | test("labelPosition=start positions label before input", async ({ initTestBed, createTextBoxDriver }) => { 212 | await initTestBed(`<TextBox testId="test" direction="ltr" label="test" labelPosition="start" />`); 213 | 214 | const driver = await createTextBoxDriver("test"); 215 | 216 | const { left: textboxLeft } = await getBounds(driver.input); 217 | const { right: labelRight } = await getBounds(driver.label); 218 | 219 | expect(labelRight).toBeLessThan(textboxLeft); 220 | }); 221 | 222 | test("labelPosition=end positions label after input", async ({ initTestBed, createTextBoxDriver }) => { 223 | await initTestBed(`<TextBox testId="test" direction="ltr" label="test" labelPosition="end" />`); 224 | 225 | const driver = await createTextBoxDriver("test"); 226 | 227 | const { right: textboxRight } = await getBounds(driver.input); 228 | const { left: labelLeft } = await getBounds(driver.label); 229 | 230 | expect(labelLeft).toBeGreaterThan(textboxRight); 231 | }); 232 | 233 | test("labelPosition=top positions label above input", async ({ initTestBed, createTextBoxDriver }) => { 234 | await initTestBed(`<TextBox testId="test" label="test" labelPosition="top" />`); 235 | 236 | const driver = await createTextBoxDriver("test"); 237 | 238 | const { top: textboxTop } = await getBounds(driver.input); 239 | const { bottom: labelBottom } = await getBounds(driver.label); 240 | 241 | expect(labelBottom).toBeLessThan(textboxTop); 242 | }); 243 | 244 | test("labelPosition=bottom positions label below input", async ({ initTestBed, createTextBoxDriver }) => { 245 | await initTestBed(`<TextBox testId="test" label="test" labelPosition="bottom" />`); 246 | 247 | const driver = await createTextBoxDriver("test"); 248 | 249 | const { bottom: textboxBottom } = await getBounds(driver.input); 250 | const { top: labelTop } = await getBounds(driver.label); 251 | 252 | expect(labelTop).toBeGreaterThan(textboxBottom); 253 | }); 254 | 255 | test("labelBreak enables label line breaks", async ({ initTestBed, createTextBoxDriver }) => { 256 | const labelText = "Very long label text that should break"; 257 | const commonProps = `label="${labelText}" labelWidth="100px"`; 258 | await initTestBed( 259 | `<Fragment> 260 | <TextBox ${commonProps} testId="break" labelBreak="{true}" /> 261 | <TextBox ${commonProps} testId="oneLine" labelBreak="{false}" /> 262 | </Fragment>`, 263 | ); 264 | const driverBreak = await createTextBoxDriver("break"); 265 | const driverOneLine = await createTextBoxDriver("oneLine"); 266 | const { height: heightBreak } = await getBounds(driverBreak.label); 267 | const { height: heightOneLine } = await getBounds(driverOneLine.label); 268 | 269 | expect(heightBreak).toBeGreaterThan(heightOneLine); 270 | }); 271 | 272 | test("component handles invalid labelPosition gracefully", async ({ initTestBed, createTextBoxDriver }) => { 273 | await initTestBed(`<TextBox testId="test" labelPosition="invalid" label="test" />`); 274 | const driver = await createTextBoxDriver("test"); 275 | await expect(driver.label).toBeVisible(); 276 | await expect(driver.input).toBeVisible(); 277 | }); 278 | }); 279 | 280 | // ============================================================================= 281 | // EVENT HANDLING TESTS 282 | // ============================================================================= 283 | 284 | test.describe("Event Handling", () => { 285 | test("didChange event fires on input change", async ({ initTestBed, createTextBoxDriver }) => { 286 | const { testStateDriver } = await initTestBed(` 287 | <TextBox testId="test" onDidChange="testState = 'changed'" /> 288 | `); 289 | const driver = await createTextBoxDriver("test"); 290 | await driver.input.fill("test"); 291 | await expect.poll(testStateDriver.testState).toEqual("changed"); 292 | }); 293 | 294 | test("didChange event passes new value", async ({ initTestBed, createTextBoxDriver }) => { 295 | const { testStateDriver } = await initTestBed(` 296 | <TextBox testId="test" onDidChange="arg => testState = arg" /> 297 | `); 298 | const driver = await createTextBoxDriver("test"); 299 | await driver.input.fill("test value"); 300 | await expect.poll(testStateDriver.testState).toEqual("test value"); 301 | }); 302 | 303 | test("gotFocus event fires on focus", async ({ initTestBed, createTextBoxDriver }) => { 304 | const { testStateDriver } = await initTestBed(` 305 | <TextBox testId="test" onGotFocus="testState = 'focused'" /> 306 | `); 307 | const driver = await createTextBoxDriver("test"); 308 | await driver.input.focus(); 309 | await expect.poll(testStateDriver.testState).toEqual("focused"); 310 | }); 311 | 312 | test("gotFocus event fires on label click", async ({ initTestBed, page }) => { 313 | const { testStateDriver } = await initTestBed(` 314 | <TextBox label="Username" onGotFocus="testState = 'focused'" /> 315 | `); 316 | await page.getByText("Username").click(); 317 | await expect.poll(testStateDriver.testState).toEqual("focused"); 318 | }); 319 | 320 | test("component lostFocus event fires on blur", async ({ initTestBed, createTextBoxDriver }) => { 321 | const { testStateDriver } = await initTestBed(` 322 | <TextBox testId="test" onLostFocus="testState = 'blurred'" /> 323 | `); 324 | const driver = await createTextBoxDriver("test"); 325 | await driver.input.focus(); 326 | await driver.input.blur(); 327 | await expect.poll(testStateDriver.testState).toEqual("blurred"); 328 | }); 329 | 330 | test("events do not fire when component is disabled", async ({ initTestBed, createTextBoxDriver }) => { 331 | const { testStateDriver } = await initTestBed(` 332 | <TextBox testId="test" enabled="false" didChange="testState = 'changed'" gotFocus="testState = 'focused'" /> 333 | `); 334 | const driver = await createTextBoxDriver("test"); 335 | await driver.input.focus(); 336 | await driver.input.fill("test", { force: true }); 337 | await expect.poll(testStateDriver.testState).toEqual(null); 338 | }); 339 | }); 340 | 341 | // ============================================================================= 342 | // API TESTS 343 | // ============================================================================= 344 | 345 | test.describe("Api", () => { 346 | test("component value API returns current state", async ({ initTestBed, page }) => { 347 | await initTestBed(` 348 | <Fragment> 349 | <TextBox id="myTextBox" initialValue="initial" /> 350 | <Text testId="value">{myTextBox.value}</Text> 351 | </Fragment> 352 | `); 353 | await expect(page.getByTestId("value")).toHaveText("initial"); 354 | }); 355 | 356 | test("component value API returns state after change", async ({ initTestBed, page, createTextBoxDriver }) => { 357 | await initTestBed(` 358 | <Fragment> 359 | <TextBox id="myTextBox" testId="myTextBox" /> 360 | <Text testId="value">{myTextBox.value}</Text> 361 | </Fragment> 362 | `); 363 | const driver = await createTextBoxDriver("myTextBox"); 364 | await expect(page.getByTestId("value")).toHaveText(""); 365 | await driver.input.fill("new value"); 366 | await expect(page.getByTestId("value")).toHaveText("new value"); 367 | }); 368 | 369 | test("component setValue API updates state", async ({ initTestBed, page, createTextBoxDriver }) => { 370 | await initTestBed(` 371 | <Fragment> 372 | <TextBox id="myTextBox" testId="myTextBox" /> 373 | <Button testId="setBtn" onClick="myTextBox.setValue('api value')" /> 374 | </Fragment> 375 | `); 376 | const driver = await createTextBoxDriver("myTextBox"); 377 | await page.getByTestId("setBtn").click(); 378 | await expect(driver.input).toHaveValue("api value"); 379 | }); 380 | 381 | test("component setValue API triggers events", async ({ initTestBed, page }) => { 382 | const { testStateDriver } = await initTestBed(` 383 | <Fragment> 384 | <TextBox id="myTextBox" onDidChange="testState = 'api-changed'" /> 385 | <Button testId="setBtn" onClick="myTextBox.setValue('test')">Set Value</Button> 386 | </Fragment> 387 | `); 388 | await page.getByTestId("setBtn").click(); 389 | await expect.poll(testStateDriver.testState).toEqual("api-changed"); 390 | }); 391 | 392 | test("focus API focuses the input", async ({ initTestBed, page, createTextBoxDriver }) => { 393 | await initTestBed(` 394 | <Fragment> 395 | <TextBox id="myTextBox" /> 396 | <Button testId="focusBtn" onClick="myTextBox.focus()">Focus</Button> 397 | </Fragment> 398 | `); 399 | const driver = await createTextBoxDriver("myTextBox"); 400 | await expect(driver.input).not.toBeFocused(); 401 | 402 | await page.getByTestId("focusBtn").click(); 403 | await expect(driver.input).toBeFocused(); 404 | }); 405 | 406 | test("focus API does nothing when component is disabled", async ({ initTestBed, page, createTextBoxDriver }) => { 407 | await initTestBed(` 408 | <Fragment> 409 | <TextBox id="myTextBox" enabled="false" /> 410 | <Button testId="focusBtn" onClick="myTextBox.focus()">Focus</Button> 411 | </Fragment> 412 | `); 413 | const driver = await createTextBoxDriver("myTextBox"); 414 | await page.getByTestId("focusBtn").click(); 415 | await expect(driver.input).not.toBeFocused(); 416 | }); 417 | }); 418 | 419 | // ============================================================================= 420 | // INPUT ADORNMENTS TESTS 421 | // ============================================================================= 422 | 423 | test.describe("Input Adornments", () => { 424 | test("startText displays at beginning of input", async ({ initTestBed, createTextBoxDriver }) => { 425 | await initTestBed(`<TextBox testId="input" startText="$" />`); 426 | const driver = await createTextBoxDriver("input"); 427 | 428 | const { left: compLeft, right: compRight } = await getBounds(driver.input); 429 | const { left: textLeft, right: textRight } = await getBounds(driver.startAdornment); 430 | 431 | await expect(driver.startAdornment).toContainText("$"); 432 | expect(textRight - compLeft).toBeLessThanOrEqual(compRight - textLeft); 433 | }); 434 | 435 | test("endText displays at end of input", async ({ initTestBed, createTextBoxDriver }) => { 436 | await initTestBed(`<TextBox testId="input" endText="USD" />`); 437 | const driver = await createTextBoxDriver("input"); 438 | 439 | const { left: compLeft, right: compRight } = await getBounds(driver.input); 440 | const { left: textLeft, right: textRight } = await getBounds(driver.endAdornment); 441 | 442 | await expect(driver.endAdornment).toContainText("USD"); 443 | expect(textRight - compLeft).toBeGreaterThanOrEqual(compRight - textLeft); 444 | }); 445 | 446 | test("startIcon displays at beginning of input", async ({ initTestBed, createTextBoxDriver }) => { 447 | await initTestBed(`<TextBox testId="input" startIcon="search" />`); 448 | const driver = await createTextBoxDriver("input"); 449 | 450 | const { left: compLeft, right: compRight } = await getBounds(driver.input); 451 | const { left: iconLeft, right: iconRight } = await getBounds(driver.startAdornment); 452 | 453 | expect(iconRight - compLeft).toBeLessThanOrEqual(compRight - iconLeft); 454 | }); 455 | 456 | test("endIcon displays at end of input", async ({ initTestBed, createTextBoxDriver }) => { 457 | await initTestBed(`<TextBox testId="input" endIcon="search" />`); 458 | 459 | const driver = await createTextBoxDriver("input"); 460 | const { left: compLeft, right: compRight } = await getBounds(driver.input); 461 | const { left: iconLeft, right: iconRight } = await getBounds(driver.endAdornment); 462 | 463 | expect(iconRight - compLeft).toBeGreaterThanOrEqual(compRight - iconLeft); 464 | }); 465 | 466 | test("multiple adornments can be combined", async ({ initTestBed, createTextBoxDriver }) => { 467 | await initTestBed(` 468 | <TextBox testId="input" startText="$" endText="USD" startIcon="search" endIcon="search" />`); 469 | const driver = await createTextBoxDriver("input"); 470 | await expect(driver.startAdornment).toContainText("$"); 471 | await expect(driver.endAdornment).toContainText("USD"); 472 | await expect(driver.startAdornment).toBeVisible(); 473 | await expect(driver.endAdornment).toBeVisible(); 474 | }); 475 | 476 | test("all adornments appear in the right place", async ({ initTestBed, createTextBoxDriver }) => { 477 | await initTestBed(` 478 | <TextBox testId="input" startText="$" endText="USD" startIcon="search" endIcon="search" direction="ltr" /> 479 | `); 480 | const driver = await createTextBoxDriver("input"); 481 | const { left: compLeft, right: compRight } = await getBounds(driver.input); 482 | const { left: startTextLeft, right: startTextRight } = await getBounds(driver.startAdornment); 483 | const { left: endTextLeft, right: endTextRight } = await getBounds(driver.endAdornment); 484 | const { left: startIconLeft, right: startIconRight } = await getBounds( 485 | driver.startAdornment, 486 | ); 487 | const { left: endIconLeft, right: endIconRight } = await getBounds( 488 | driver.endAdornment, 489 | ); 490 | 491 | // Check order of adornments 492 | expect(startTextRight - compLeft).toBeLessThanOrEqual(compRight - startTextLeft); 493 | expect(startIconRight - compLeft).toBeLessThanOrEqual(compRight - startIconLeft); 494 | expect(endTextRight - compLeft).toBeGreaterThanOrEqual(compRight - endTextLeft); 495 | expect(endIconRight - compLeft).toBeGreaterThanOrEqual(compRight - endIconLeft); 496 | }); 497 | }); 498 | 499 | // ============================================================================= 500 | // PASSWORD INPUT TESTS 501 | // ============================================================================= 502 | 503 | test.describe("Password Input", () => { 504 | test("component renders", async ({ initTestBed, createTextBoxDriver }) => { 505 | await initTestBed(`<PasswordInput testId="input" />`); 506 | const driver = await createTextBoxDriver("input"); 507 | await expect(driver.component).toBeVisible(); 508 | }); 509 | 510 | test("component has password type", async ({ initTestBed, createTextBoxDriver }) => { 511 | await initTestBed(`<PasswordInput testId="input" />`); 512 | const driver = await createTextBoxDriver("input"); 513 | await expect(driver.input).toHaveAttribute("type", "password"); 514 | }); 515 | 516 | test("component has initial value", async ({ initTestBed, createTextBoxDriver }) => { 517 | await initTestBed(`<PasswordInput testId="input" initialValue="secret" />`); 518 | const driver = await createTextBoxDriver("input"); 519 | await expect(driver.input).toHaveValue("secret"); 520 | }); 521 | 522 | test("showPasswordToggle displays visibility toggle", async ({ initTestBed, createTextBoxDriver }) => { 523 | await initTestBed(`<PasswordInput testId="input" showPasswordToggle="true" />`); 524 | const driver = await createTextBoxDriver("input"); 525 | await expect(driver.input).toBeVisible(); 526 | }); 527 | 528 | test("password toggle switches between visible and hidden", async ({ initTestBed, createTextBoxDriver }) => { 529 | await initTestBed(`<PasswordInput testId="input" showPasswordToggle="true" />`); 530 | const driver = await createTextBoxDriver("input"); 531 | await expect(driver.input).toHaveAttribute("type", "password"); 532 | await driver.button.click(); 533 | await expect(driver.input).toHaveAttribute("type", "text"); 534 | }); 535 | 536 | test("custom password icons work correctly", async ({ initTestBed, createTextBoxDriver }) => { 537 | await initTestBed( 538 | ` 539 | <PasswordInput 540 | testId="input" 541 | showPasswordToggle="true" 542 | passwordVisibleIcon="test" 543 | passwordHiddenIcon="test" 544 | />`, 545 | { 546 | resources: { 547 | "icon.test": "resources/bell.svg", 548 | }, 549 | }, 550 | ); 551 | const driver = await createTextBoxDriver("input"); 552 | const icon = driver.button; 553 | await expect(icon).toBeVisible(); 554 | await icon.click(); 555 | await expect(icon).toBeVisible(); 556 | }); 557 | }); 558 | 559 | // ============================================================================= 560 | // VISUAL STATE TESTS 561 | // ============================================================================= 562 | 563 | test.describe("Theme Vars", () => { 564 | test("backgroundColor applies correctly", async ({ initTestBed, createTextBoxDriver }) => { 565 | await initTestBed(`<TextBox testId="input" />`, { 566 | testThemeVars: { 567 | "backgroundColor-TextBox": "rgb(255, 240, 240)", 568 | }, 569 | }); 570 | const driver = await createTextBoxDriver("input"); 571 | await expect(driver.component).toHaveCSS("background-color", "rgb(255, 240, 240)"); 572 | }); 573 | 574 | test("borderColor applies correctly", async ({ initTestBed, createTextBoxDriver }) => { 575 | await initTestBed(`<TextBox testId="input" />`, { 576 | testThemeVars: { 577 | "borderColor-TextBox": "rgb(255, 0, 0)", 578 | }, 579 | }); 580 | const driver = await createTextBoxDriver("input"); 581 | await expect(driver.component).toHaveCSS("border-color", "rgb(255, 0, 0)"); 582 | }); 583 | 584 | test("textColor applies correctly", async ({ initTestBed, createTextBoxDriver }) => { 585 | await initTestBed(`<TextBox testId="input" />`, { 586 | testThemeVars: { 587 | "textColor-TextBox": "rgb(0, 0, 255)", 588 | }, 589 | }); 590 | const driver = await createTextBoxDriver("input"); 591 | await expect(driver.component).toHaveCSS("color", "rgb(0, 0, 255)"); 592 | }); 593 | 594 | test("focus borderColor applies on focus", async ({ initTestBed, createTextBoxDriver }) => { 595 | await initTestBed(`<TextBox testId="input" />`, { 596 | testThemeVars: { 597 | "borderColor-TextBox--focus": "rgb(0, 255, 0)", 598 | }, 599 | }); 600 | const driver = await createTextBoxDriver("input"); 601 | await driver.input.focus(); 602 | await expect(driver.component).toHaveCSS("border-color", "rgb(0, 255, 0)"); 603 | }); 604 | 605 | test("disabled backgroundColor applies when disabled", async ({ initTestBed, createTextBoxDriver }) => { 606 | await initTestBed(`<TextBox testId="input" enabled="false" />`, { 607 | testThemeVars: { 608 | "backgroundColor-TextBox--disabled": "rgb(240, 240, 240)", 609 | }, 610 | }); 611 | const driver = await createTextBoxDriver("input"); 612 | await expect(driver.component).toHaveCSS("background-color", "rgb(240, 240, 240)"); 613 | }); 614 | 615 | test("error borderColor applies with error validation", async ({ initTestBed, createTextBoxDriver }) => { 616 | await initTestBed(`<TextBox testId="input" validationStatus="error" />`, { 617 | testThemeVars: { 618 | "borderColor-TextBox-error": "rgb(255, 0, 0)", 619 | }, 620 | }); 621 | const driver = await createTextBoxDriver("input"); 622 | await expect(driver.component).toHaveCSS("border-color", "rgb(255, 0, 0)"); 623 | }); 624 | 625 | test("warning borderColor applies with warning validation", async ({ initTestBed, createTextBoxDriver }) => { 626 | await initTestBed(`<TextBox testId="input" validationStatus="warning" />`, { 627 | testThemeVars: { 628 | "borderColor-TextBox-warning": "rgb(255, 165, 0)", 629 | }, 630 | }); 631 | const driver = await createTextBoxDriver("input"); 632 | await expect(driver.component).toHaveCSS("border-color", "rgb(255, 165, 0)"); 633 | }); 634 | 635 | test("success borderColor applies with valid validation", async ({ initTestBed, createTextBoxDriver }) => { 636 | await initTestBed(`<TextBox testId="input" validationStatus="valid" />`, { 637 | testThemeVars: { 638 | "borderColor-TextBox-success": "rgb(0, 255, 0)", 639 | }, 640 | }); 641 | const driver = await createTextBoxDriver("input"); 642 | await expect(driver.component).toHaveCSS("border-color", "rgb(0, 255, 0)"); 643 | }); 644 | 645 | test("borderRadius applies correctly", async ({ initTestBed, createTextBoxDriver }) => { 646 | await initTestBed(`<TextBox testId="input" />`, { 647 | testThemeVars: { 648 | "borderRadius-TextBox": "8px", 649 | }, 650 | }); 651 | const driver = await createTextBoxDriver("input"); 652 | await expect(driver.component).toHaveCSS("border-radius", "8px"); 653 | }); 654 | 655 | test("padding applies correctly", async ({ initTestBed, createTextBoxDriver }) => { 656 | await initTestBed(`<TextBox testId="input" />`, { 657 | testThemeVars: { 658 | "padding-TextBox": "12px", 659 | }, 660 | }); 661 | const driver = await createTextBoxDriver("input"); 662 | await expect(driver.component).toHaveCSS("padding", "12px"); 663 | }); 664 | }); 665 | 666 | // ============================================================================= 667 | // VALIDATION STATUS TESTS 668 | // ============================================================================= 669 | 670 | test.describe("Validation", () => { 671 | test("validationStatus=error correctly displayed", async ({ initTestBed, createTextBoxDriver }) => { 672 | await initTestBed(`<TextBox testId="input" validationStatus="error" />`, { 673 | testThemeVars: { 674 | "borderColor-TextBox-error": "rgb(255, 0, 0)", 675 | }, 676 | }); 677 | const driver = await createTextBoxDriver("input"); 678 | await expect(driver.component).toHaveCSS("border-color", "rgb(255, 0, 0)"); 679 | }); 680 | 681 | test("validationStatus=warning correctly displayed", async ({ initTestBed, createTextBoxDriver }) => { 682 | await initTestBed(`<TextBox testId="input" validationStatus="warning" />`, { 683 | testThemeVars: { 684 | "borderColor-TextBox-warning": "rgb(255, 165, 0)", 685 | }, 686 | }); 687 | const driver = await createTextBoxDriver("input"); 688 | await expect(driver.component).toHaveCSS("border-color", "rgb(255, 165, 0)"); 689 | }); 690 | 691 | test("validationStatus=valid correctly displayed", async ({ initTestBed, createTextBoxDriver }) => { 692 | await initTestBed(`<TextBox testId="input" validationStatus="valid" />`, { 693 | testThemeVars: { 694 | "borderColor-TextBox-success": "rgb(0, 255, 0)", 695 | }, 696 | }); 697 | const driver = await createTextBoxDriver("input"); 698 | await expect(driver.component).toHaveCSS("border-color", "rgb(0, 255, 0)"); 699 | }); 700 | 701 | test("handles invalid validationStatus gracefully", async ({ initTestBed, createTextBoxDriver }) => { 702 | await initTestBed(`<TextBox testId="input" validationStatus="invalid-status" />`, { 703 | testThemeVars: { 704 | "borderColor-TextBox": "rgb(0, 0, 0)", 705 | "borderColor-TextBox-error": "rgb(255, 0, 0)", 706 | "borderColor-TextBox-warning": "rgb(255, 165, 0)", 707 | "borderColor-TextBox-success": "rgb(0, 255, 0)", 708 | }, 709 | }); 710 | const driver = await createTextBoxDriver("input"); 711 | await expect(driver.component).not.toHaveCSS("border-color", "rgb(255, 0, 0)"); 712 | await expect(driver.component).not.toHaveCSS("border-color", "rgb(255, 165, 0)"); 713 | await expect(driver.component).not.toHaveCSS("border-color", "rgb(0, 255, 0)"); 714 | await expect(driver.component).toHaveCSS("border-color", "rgb(0, 0, 0)"); 715 | }); 716 | }); 717 | 718 | // ============================================================================= 719 | // EDGE CASE TESTS 720 | // ============================================================================= 721 | 722 | test.describe("Edge Cases", () => { 723 | test("handle special characters in input", async ({ initTestBed, createTextBoxDriver }) => { 724 | await initTestBed(`<TextBox testId="input"/>`); 725 | const driver = await createTextBoxDriver("input"); 726 | await driver.input.fill("Hello 日本語 @#$%!"); 727 | await expect(driver.input).toHaveValue("Hello 日本語 @#$%!"); 728 | }); 729 | 730 | test("handle Unicode characters in input", async ({ initTestBed, createTextBoxDriver }) => { 731 | await initTestBed(`<TextBox testId="input"/>`); 732 | const driver = await createTextBoxDriver("input"); 733 | await driver.input.fill("🚀 Unicode test 🎉"); 734 | await expect(driver.input).toHaveValue("🚀 Unicode test 🎉"); 735 | }); 736 | 737 | test("component handles very long input text", async ({ initTestBed, createTextBoxDriver }) => { 738 | const longText = 739 | "This is a very long text that might cause layout or performance issues in the component".repeat( 740 | 10, 741 | ); 742 | await initTestBed(`<TextBox testId="input"/>`); 743 | const driver = await createTextBoxDriver("input"); 744 | await driver.input.fill(longText); 745 | await expect(driver.input).toHaveValue(longText); 746 | }); 747 | 748 | test("component handles special characters correctly", async ({ initTestBed, createTextBoxDriver }) => { 749 | await initTestBed(`<TextBox testId="input" label="Input with !@#$%^&*()"/>`, {}); 750 | const driver = await createTextBoxDriver("input"); 751 | await expect(driver.label).toBeVisible(); 752 | }); 753 | 754 | test("component handles extremely long input values", async ({ initTestBed, createTextBoxDriver }) => { 755 | await initTestBed(`<TextBox testId="input"/>`); 756 | const driver = await createTextBoxDriver("input"); 757 | const veryLongText = "A".repeat(10000); 758 | await driver.input.fill(veryLongText); 759 | await expect(driver.input).toHaveValue(veryLongText); 760 | }); 761 | }); 762 | 763 | // ============================================================================= 764 | // INTEGRATION TESTS 765 | // ============================================================================= 766 | 767 | test.describe("Integration", () => { 768 | test("component works correctly in Stack layout contexts", async ({ initTestBed, createTextBoxDriver }) => { 769 | await initTestBed(`<Stack><TextBox testId="input" /></Stack>`); 770 | const driver = await createTextBoxDriver("input"); 771 | const { width, height } = await getBounds(driver.input); 772 | 773 | await expect(driver.input).toBeVisible(); 774 | expect(width).toBeGreaterThan(0); 775 | expect(height).toBeGreaterThan(0); 776 | }); 777 | 778 | test("component works correctly in FlowLayout layout contexts", async ({ initTestBed, createTextBoxDriver }) => { 779 | await initTestBed(`<FlowLayout><TextBox testId="input" /></FlowLayout>`); 780 | const driver = await createTextBoxDriver("input"); 781 | const { width, height } = await getBounds(driver.input); 782 | 783 | await expect(driver.input).toBeVisible(); 784 | expect(width).toBeGreaterThan(0); 785 | expect(height).toBeGreaterThan(0); 786 | }); 787 | 788 | test("component integrates with forms correctly", async ({ initTestBed, createTextBoxDriver }) => { 789 | await initTestBed(`<Form><TextBox testId="input" label="Username" /></Form>`); 790 | const driver = await createTextBoxDriver("input"); 791 | const { width, height } = await getBounds(driver.input); 792 | 793 | await expect(driver.input).toBeVisible(); 794 | expect(width).toBeGreaterThan(0); 795 | expect(height).toBeGreaterThan(0); 796 | }); 797 | 798 | test("component works with conditional rendering", async ({ initTestBed, page, createTextBoxDriver }) => { 799 | await initTestBed(` 800 | <Fragment var.showInput="{true}"> 801 | <Fragment when="{showInput}"> 802 | <TextBox testId="input" label="Conditional input" /> 803 | </Fragment> 804 | <Button testId="toggleBtn" onClick="showInput = !showInput">Toggle</Button> 805 | </Fragment> 806 | `); 807 | 808 | const driver = await createTextBoxDriver("input"); 809 | await expect(driver.label).toBeVisible(); 810 | 811 | await page.getByTestId("toggleBtn").click(); 812 | await expect(driver.label).not.toBeVisible(); 813 | }); 814 | }); 815 | 816 | 817 | test("labelWidth applies custom label width", async ({ initTestBed, createTextBoxDriver }) => { 818 | const expected = 200; 819 | await initTestBed(`<TextBox testId="test" label="test test" labelWidth="${expected}px" />`); 820 | const driver = await createTextBoxDriver("test"); 821 | const { width } = await getBounds(driver.label); 822 | expect(width).toEqual(expected); 823 | }); 824 | 825 | // ============================================================================= 826 | // VISUAL STATE TESTS 827 | // ============================================================================= 828 | 829 | 830 | test("input has correct width", async ({ initTestBed, page }) => { 831 | await initTestBed(` 832 | <TextBox width="200px" testId="test"/> 833 | `); 834 | const { width } = await page.getByTestId("test").boundingBox(); 835 | expect(width).toBe(200); 836 | }); 837 | 838 | test("input with label has correct width", async ({ initTestBed, page }) => { 839 | await initTestBed(` 840 | <TextBox width="200px" label="test" testId="test"/> 841 | `); 842 | const { width } = await page.getByTestId("test").boundingBox(); 843 | expect(width).toBe(200); 844 | }); 845 | 846 | test("input has correct width in %", async ({ page, initTestBed }) => { 847 | await page.setViewportSize({ width: 400, height: 300}); 848 | await initTestBed(`<TextBox width="50%" testId="test"/>`, {}); 849 | 850 | const input = page.getByTestId("test"); 851 | const { width } = await input.boundingBox(); 852 | expect(width).toBe(200); 853 | }); 854 | 855 | test("input with label has correct width in %", async ({ page, initTestBed }) => { 856 | await page.setViewportSize({ width: 400, height: 300}); 857 | await initTestBed(`<TextBox width="50%" label="test" testId="test"/>`, {}); 858 | 859 | const input = page.getByTestId("test"); 860 | const { width } = await input.boundingBox(); 861 | expect(width).toBe(200); 862 | }); 863 | ```