This is page 130 of 138. Use http://codebase.md/xmlui-org/xmlui/xmlui/mockApiDef.js?lines=false&page={x} to view the full context. # Directory Structure ``` ├── .changeset │ └── config.json ├── .eslintrc.cjs ├── .github │ ├── build-checklist.png │ ├── ISSUE_TEMPLATE │ │ ├── bug_report.md │ │ └── feature_request.md │ └── workflows │ ├── deploy-blog-optimized.yml │ ├── deploy-docs-optimized.yml │ ├── deploy-docs.yml │ ├── prepare-versions.yml │ ├── release-packages.yml │ ├── run-all-tests.yml │ └── run-smoke-tests.yml ├── .gitignore ├── .prettierrc.js ├── .vscode │ ├── launch.json │ └── settings.json ├── blog │ ├── .gitignore │ ├── .gitkeep │ ├── CHANGELOG.md │ ├── extensions.ts │ ├── index.html │ ├── index.ts │ ├── package.json │ ├── public │ │ ├── blog │ │ │ ├── images │ │ │ │ ├── blog-page-component.png │ │ │ │ ├── blog-scrabble.png │ │ │ │ ├── integrated-blog-search.png │ │ │ │ └── lorem-ipsum.png │ │ │ ├── lorem-ipsum.md │ │ │ ├── newest-post.md │ │ │ ├── older-post.md │ │ │ └── welcome-to-the-xmlui-blog.md │ │ ├── mockServiceWorker.js │ │ ├── resources │ │ │ ├── favicon.ico │ │ │ ├── files │ │ │ │ └── for-download │ │ │ │ └── xmlui │ │ │ │ └── xmlui-standalone.umd.js │ │ │ ├── github.svg │ │ │ ├── llms.txt │ │ │ ├── logo-dark.svg │ │ │ ├── logo.svg │ │ │ ├── pg-popout.svg │ │ │ └── xmlui-logo.svg │ │ ├── serve.json │ │ └── web.config │ ├── scripts │ │ ├── download-latest-xmlui.js │ │ ├── generate-rss.js │ │ ├── get-releases.js │ │ └── utils.js │ ├── src │ │ ├── components │ │ │ ├── BlogOverview.xmlui │ │ │ ├── BlogPage.xmlui │ │ │ └── PageNotFound.xmlui │ │ ├── config.ts │ │ ├── Main.xmlui │ │ └── themes │ │ ├── docs-theme.ts │ │ ├── earthtone.ts │ │ ├── xmlui-gray-on-default.ts │ │ ├── xmlui-green-on-default.ts │ │ └── xmlui-orange-on-default.ts │ └── tsconfig.json ├── CONTRIBUTING.md ├── docs │ ├── .gitignore │ ├── CHANGELOG.md │ ├── ComponentRefLinks.txt │ ├── content │ │ ├── _meta.json │ │ ├── components │ │ │ ├── _meta.json │ │ │ ├── _overview.md │ │ │ ├── APICall.md │ │ │ ├── App.md │ │ │ ├── AppHeader.md │ │ │ ├── AppState.md │ │ │ ├── AutoComplete.md │ │ │ ├── Avatar.md │ │ │ ├── Backdrop.md │ │ │ ├── Badge.md │ │ │ ├── BarChart.md │ │ │ ├── Bookmark.md │ │ │ ├── Breakout.md │ │ │ ├── Button.md │ │ │ ├── Card.md │ │ │ ├── Carousel.md │ │ │ ├── ChangeListener.md │ │ │ ├── Checkbox.md │ │ │ ├── CHStack.md │ │ │ ├── ColorPicker.md │ │ │ ├── Column.md │ │ │ ├── ContentSeparator.md │ │ │ ├── CVStack.md │ │ │ ├── DataSource.md │ │ │ ├── DateInput.md │ │ │ ├── DatePicker.md │ │ │ ├── DonutChart.md │ │ │ ├── DropdownMenu.md │ │ │ ├── EmojiSelector.md │ │ │ ├── ExpandableItem.md │ │ │ ├── FileInput.md │ │ │ ├── FileUploadDropZone.md │ │ │ ├── FlowLayout.md │ │ │ ├── Footer.md │ │ │ ├── Form.md │ │ │ ├── FormItem.md │ │ │ ├── FormSection.md │ │ │ ├── Fragment.md │ │ │ ├── H1.md │ │ │ ├── H2.md │ │ │ ├── H3.md │ │ │ ├── H4.md │ │ │ ├── H5.md │ │ │ ├── H6.md │ │ │ ├── Heading.md │ │ │ ├── HSplitter.md │ │ │ ├── HStack.md │ │ │ ├── Icon.md │ │ │ ├── IFrame.md │ │ │ ├── Image.md │ │ │ ├── Items.md │ │ │ ├── LabelList.md │ │ │ ├── Legend.md │ │ │ ├── LineChart.md │ │ │ ├── Link.md │ │ │ ├── List.md │ │ │ ├── Logo.md │ │ │ ├── Markdown.md │ │ │ ├── MenuItem.md │ │ │ ├── MenuSeparator.md │ │ │ ├── ModalDialog.md │ │ │ ├── NavGroup.md │ │ │ ├── NavLink.md │ │ │ ├── NavPanel.md │ │ │ ├── NoResult.md │ │ │ ├── NumberBox.md │ │ │ ├── Option.md │ │ │ ├── Page.md │ │ │ ├── PageMetaTitle.md │ │ │ ├── Pages.md │ │ │ ├── Pagination.md │ │ │ ├── PasswordInput.md │ │ │ ├── PieChart.md │ │ │ ├── ProgressBar.md │ │ │ ├── Queue.md │ │ │ ├── RadioGroup.md │ │ │ ├── RealTimeAdapter.md │ │ │ ├── Redirect.md │ │ │ ├── Select.md │ │ │ ├── Slider.md │ │ │ ├── Slot.md │ │ │ ├── SpaceFiller.md │ │ │ ├── Spinner.md │ │ │ ├── Splitter.md │ │ │ ├── Stack.md │ │ │ ├── StickyBox.md │ │ │ ├── SubMenuItem.md │ │ │ ├── Switch.md │ │ │ ├── TabItem.md │ │ │ ├── Table.md │ │ │ ├── TableOfContents.md │ │ │ ├── Tabs.md │ │ │ ├── Text.md │ │ │ ├── TextArea.md │ │ │ ├── TextBox.md │ │ │ ├── Theme.md │ │ │ ├── TimeInput.md │ │ │ ├── Timer.md │ │ │ ├── ToneChangerButton.md │ │ │ ├── ToneSwitch.md │ │ │ ├── Tooltip.md │ │ │ ├── Tree.md │ │ │ ├── VSplitter.md │ │ │ ├── VStack.md │ │ │ ├── xmlui-animations │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ ├── Animation.md │ │ │ │ ├── FadeAnimation.md │ │ │ │ ├── FadeInAnimation.md │ │ │ │ ├── FadeOutAnimation.md │ │ │ │ ├── ScaleAnimation.md │ │ │ │ └── SlideInAnimation.md │ │ │ ├── xmlui-pdf │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ └── Pdf.md │ │ │ ├── xmlui-spreadsheet │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ └── Spreadsheet.md │ │ │ └── xmlui-website-blocks │ │ │ ├── _meta.json │ │ │ ├── _overview.md │ │ │ ├── Carousel.md │ │ │ ├── HelloMd.md │ │ │ ├── HeroSection.md │ │ │ └── ScrollToTop.md │ │ └── extensions │ │ ├── _meta.json │ │ ├── xmlui-animations │ │ │ ├── _meta.json │ │ │ ├── _overview.md │ │ │ ├── Animation.md │ │ │ ├── FadeAnimation.md │ │ │ ├── FadeInAnimation.md │ │ │ ├── FadeOutAnimation.md │ │ │ ├── ScaleAnimation.md │ │ │ └── SlideInAnimation.md │ │ └── xmlui-website-blocks │ │ ├── _meta.json │ │ ├── _overview.md │ │ ├── Carousel.md │ │ ├── HelloMd.md │ │ ├── HeroSection.md │ │ └── ScrollToTop.md │ ├── extensions.ts │ ├── index.html │ ├── index.ts │ ├── package.json │ ├── public │ │ ├── feed.rss │ │ ├── mockServiceWorker.js │ │ ├── pages │ │ │ ├── _meta.json │ │ │ ├── app-structure.md │ │ │ ├── build-editor-component.md │ │ │ ├── build-hello-world-component.md │ │ │ ├── components-intro.md │ │ │ ├── context-variables.md │ │ │ ├── forms.md │ │ │ ├── globals.md │ │ │ ├── glossary.md │ │ │ ├── helper-tags.md │ │ │ ├── hosted-deployment.md │ │ │ ├── howto │ │ │ │ ├── assign-a-complex-json-literal-to-a-component-variable.md │ │ │ │ ├── chain-a-refetch.md │ │ │ │ ├── debug-a-component.md │ │ │ │ ├── delay-a-datasource-until-another-datasource-is-ready.md │ │ │ │ ├── delegate-a-method.md │ │ │ │ ├── do-custom-form-validation.md │ │ │ │ ├── expose-a-method-from-a-component.md │ │ │ │ ├── filter-and-transform-data-from-an-api.md │ │ │ │ ├── group-items-in-list-by-a-property.md │ │ │ │ ├── handle-background-operations.md │ │ │ │ ├── hide-an-element-until-its-datasource-is-ready.md │ │ │ │ ├── make-a-set-of-equal-width-cards.md │ │ │ │ ├── make-a-table-responsive.md │ │ │ │ ├── modify-a-value-reported-in-a-column.md │ │ │ │ ├── paginate-a-list.md │ │ │ │ ├── pass-data-to-a-modal-dialog.md │ │ │ │ ├── react-to-button-click-not-keystrokes.md │ │ │ │ ├── set-the-initial-value-of-a-select-from-fetched-data.md │ │ │ │ ├── share-a-modaldialog-across-components.md │ │ │ │ ├── sync-selections-between-table-and-list-views.md │ │ │ │ ├── update-ui-optimistically.md │ │ │ │ ├── use-built-in-form-validation.md │ │ │ │ └── use-the-same-modaldialog-to-add-or-edit.md │ │ │ ├── howto.md │ │ │ ├── intro.md │ │ │ ├── layout.md │ │ │ ├── markup.md │ │ │ ├── mcp.md │ │ │ ├── modal-dialogs.md │ │ │ ├── news-and-reviews.md │ │ │ ├── reactive-intro.md │ │ │ ├── refactoring.md │ │ │ ├── routing-and-links.md │ │ │ ├── samples │ │ │ │ ├── color-palette.xmlui │ │ │ │ ├── color-values.xmlui │ │ │ │ ├── shadow-sizes.xmlui │ │ │ │ ├── spacing-sizes.xmlui │ │ │ │ ├── swatch.xmlui │ │ │ │ ├── theme-gallery-brief.xmlui │ │ │ │ └── theme-gallery.xmlui │ │ │ ├── scoping.md │ │ │ ├── scripting.md │ │ │ ├── styles-and-themes │ │ │ │ ├── common-units.md │ │ │ │ ├── layout-props.md │ │ │ │ ├── theme-variable-defaults.md │ │ │ │ ├── theme-variables.md │ │ │ │ └── themes.md │ │ │ ├── template-properties.md │ │ │ ├── test.md │ │ │ ├── tutorial-01.md │ │ │ ├── tutorial-02.md │ │ │ ├── tutorial-03.md │ │ │ ├── tutorial-04.md │ │ │ ├── tutorial-05.md │ │ │ ├── tutorial-06.md │ │ │ ├── tutorial-07.md │ │ │ ├── tutorial-08.md │ │ │ ├── tutorial-09.md │ │ │ ├── tutorial-10.md │ │ │ ├── tutorial-11.md │ │ │ ├── tutorial-12.md │ │ │ ├── universal-properties.md │ │ │ ├── user-defined-components.md │ │ │ ├── vscode.md │ │ │ ├── working-with-markdown.md │ │ │ ├── working-with-text.md │ │ │ ├── xmlui-animations │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ ├── Animation.md │ │ │ │ ├── FadeAnimation.md │ │ │ │ ├── FadeInAnimation.md │ │ │ │ ├── FadeOutAnimation.md │ │ │ │ ├── ScaleAnimation.md │ │ │ │ └── SlideInAnimation.md │ │ │ ├── xmlui-charts │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ ├── BarChart.md │ │ │ │ ├── DonutChart.md │ │ │ │ ├── LabelList.md │ │ │ │ ├── Legend.md │ │ │ │ ├── LineChart.md │ │ │ │ └── PieChart.md │ │ │ ├── xmlui-pdf │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ └── Pdf.md │ │ │ └── xmlui-spreadsheet │ │ │ ├── _meta.json │ │ │ ├── _overview.md │ │ │ └── Spreadsheet.md │ │ ├── resources │ │ │ ├── devdocs │ │ │ │ ├── debug-proxy-object-2.png │ │ │ │ ├── debug-proxy-object.png │ │ │ │ ├── table_editor_01.png │ │ │ │ ├── table_editor_02.png │ │ │ │ ├── table_editor_03.png │ │ │ │ ├── table_editor_04.png │ │ │ │ ├── table_editor_05.png │ │ │ │ ├── table_editor_06.png │ │ │ │ ├── table_editor_07.png │ │ │ │ ├── table_editor_08.png │ │ │ │ ├── table_editor_09.png │ │ │ │ ├── table_editor_10.png │ │ │ │ ├── table_editor_11.png │ │ │ │ ├── table-editor-01.png │ │ │ │ ├── table-editor-02.png │ │ │ │ ├── table-editor-03.png │ │ │ │ ├── table-editor-04.png │ │ │ │ ├── table-editor-06.png │ │ │ │ ├── table-editor-07.png │ │ │ │ ├── table-editor-08.png │ │ │ │ ├── table-editor-09.png │ │ │ │ └── xmlui-rendering-of-tiptap-markdown.png │ │ │ ├── favicon.ico │ │ │ ├── files │ │ │ │ ├── clients.json │ │ │ │ ├── daily-revenue.json │ │ │ │ ├── dashboard-stats.json │ │ │ │ ├── demo.xmlui │ │ │ │ ├── demo.xmlui.xs │ │ │ │ ├── downloads │ │ │ │ │ └── downloads.json │ │ │ │ ├── for-download │ │ │ │ │ ├── index-with-api.html │ │ │ │ │ ├── index.html │ │ │ │ │ ├── mockApi.js │ │ │ │ │ ├── start-darwin.sh │ │ │ │ │ ├── start-linux.sh │ │ │ │ │ ├── start.bat │ │ │ │ │ └── xmlui │ │ │ │ │ └── xmlui-standalone.umd.js │ │ │ │ ├── getting-started │ │ │ │ │ ├── cl-tutorial-final.zip │ │ │ │ │ ├── cl-tutorial.zip │ │ │ │ │ ├── cl-tutorial2.zip │ │ │ │ │ ├── cl-tutorial3.zip │ │ │ │ │ ├── cl-tutorial4.zip │ │ │ │ │ ├── cl-tutorial5.zip │ │ │ │ │ ├── cl-tutorial6.zip │ │ │ │ │ ├── getting-started.zip │ │ │ │ │ ├── hello-xmlui.zip │ │ │ │ │ ├── xmlui-empty.zip │ │ │ │ │ └── xmlui-starter.zip │ │ │ │ ├── howto │ │ │ │ │ └── component-icons │ │ │ │ │ └── up-arrow.svg │ │ │ │ ├── invoices.json │ │ │ │ ├── monthly-status.json │ │ │ │ ├── news-and-reviews.json │ │ │ │ ├── products.json │ │ │ │ ├── releases.json │ │ │ │ ├── tutorials │ │ │ │ │ ├── datasource │ │ │ │ │ │ └── api.ts │ │ │ │ │ └── p2do │ │ │ │ │ ├── api.ts │ │ │ │ │ └── todo-logo.svg │ │ │ │ └── xmlui.json │ │ │ ├── github.svg │ │ │ ├── images │ │ │ │ ├── apiaction-tutorial │ │ │ │ │ ├── add-success.png │ │ │ │ │ ├── apiaction-param.png │ │ │ │ │ ├── change-completed.png │ │ │ │ │ ├── change-in-progress.png │ │ │ │ │ ├── confirm-delete.png │ │ │ │ │ ├── data-error.png │ │ │ │ │ ├── data-progress.png │ │ │ │ │ ├── data-success.png │ │ │ │ │ ├── display-1.png │ │ │ │ │ ├── item-deleted.png │ │ │ │ │ ├── item-updated.png │ │ │ │ │ ├── missing-api-key.png │ │ │ │ │ ├── new-item-added.png │ │ │ │ │ └── test-message.png │ │ │ │ ├── chat-api │ │ │ │ │ └── domain-model.svg │ │ │ │ ├── components │ │ │ │ │ ├── image │ │ │ │ │ │ └── breakfast.jpg │ │ │ │ │ ├── markdown │ │ │ │ │ │ └── colors.png │ │ │ │ │ └── modal │ │ │ │ │ ├── deep_link_dialog_1.jpg │ │ │ │ │ └── deep_link_dialog_2.jpg │ │ │ │ ├── create-apps │ │ │ │ │ ├── collapsed-vertical.png │ │ │ │ │ ├── using-forms-warning-dialog.png │ │ │ │ │ └── using-forms.png │ │ │ │ ├── datasource-tutorial │ │ │ │ │ ├── data-with-header.png │ │ │ │ │ ├── filtered-data.png │ │ │ │ │ ├── filtered-items.png │ │ │ │ │ ├── initial-page-items.png │ │ │ │ │ ├── list-items.png │ │ │ │ │ ├── next-page-items.png │ │ │ │ │ ├── no-data.png │ │ │ │ │ ├── pagination-1.jpg │ │ │ │ │ ├── pagination-1.png │ │ │ │ │ ├── polling-1.png │ │ │ │ │ ├── refetch-data.png │ │ │ │ │ ├── slow-loading.png │ │ │ │ │ ├── test-message.png │ │ │ │ │ ├── Thumbs.db │ │ │ │ │ ├── unconventional-data.png │ │ │ │ │ └── unfiltered-items.png │ │ │ │ ├── flower.jpg │ │ │ │ ├── get-started │ │ │ │ │ ├── add-new-contact.png │ │ │ │ │ ├── app-modified.png │ │ │ │ │ ├── app-start.png │ │ │ │ │ ├── app-with-boxes.png │ │ │ │ │ ├── app-with-toast.png │ │ │ │ │ ├── boilerplate-structure.png │ │ │ │ │ ├── cl-initial.png │ │ │ │ │ ├── cl-start.png │ │ │ │ │ ├── contact-counts.png │ │ │ │ │ ├── contact-dialog-title.png │ │ │ │ │ ├── contact-dialog.png │ │ │ │ │ ├── contact-menus.png │ │ │ │ │ ├── contact-predicates.png │ │ │ │ │ ├── context-menu.png │ │ │ │ │ ├── dashboard-numbers.png │ │ │ │ │ ├── default-contact-list.png │ │ │ │ │ ├── delete-contact.png │ │ │ │ │ ├── delete-task.png │ │ │ │ │ ├── detailed-template.png │ │ │ │ │ ├── edit-contact-details.png │ │ │ │ │ ├── edited-contact-saved.png │ │ │ │ │ ├── empty-sections.png │ │ │ │ │ ├── filter-completed.png │ │ │ │ │ ├── fullwidth-desktop.png │ │ │ │ │ ├── fullwidth-mobile.png │ │ │ │ │ ├── initial-table.png │ │ │ │ │ ├── items-and-badges.png │ │ │ │ │ ├── loading-message.png │ │ │ │ │ ├── new-contact-button.png │ │ │ │ │ ├── new-contact-saved.png │ │ │ │ │ ├── no-empty-sections.png │ │ │ │ │ ├── personal-todo-initial.png │ │ │ │ │ ├── piechart.png │ │ │ │ │ ├── review-today.png │ │ │ │ │ ├── rudimentary-dashboard.png │ │ │ │ │ ├── section-collapsed.png │ │ │ │ │ ├── sectioned-items.png │ │ │ │ │ ├── sections-ordered.png │ │ │ │ │ ├── spacex-list-with-links.png │ │ │ │ │ ├── spacex-list.png │ │ │ │ │ ├── start-personal-todo-1.png │ │ │ │ │ ├── submit-new-contact.png │ │ │ │ │ ├── submit-new-task.png │ │ │ │ │ ├── syntax-highlighting.png │ │ │ │ │ ├── table-with-badge.png │ │ │ │ │ ├── template-with-card.png │ │ │ │ │ ├── test-emulated-api.png │ │ │ │ │ ├── Thumbs.db │ │ │ │ │ ├── todo-logo.png │ │ │ │ │ └── xmlui-tools.png │ │ │ │ ├── HelloApp.png │ │ │ │ ├── HelloApp2.png │ │ │ │ ├── logos │ │ │ │ │ ├── xmlui1.svg │ │ │ │ │ ├── xmlui2.svg │ │ │ │ │ ├── xmlui3.svg │ │ │ │ │ ├── xmlui4.svg │ │ │ │ │ ├── xmlui5.svg │ │ │ │ │ ├── xmlui6.svg │ │ │ │ │ └── xmlui7.svg │ │ │ │ ├── pdf │ │ │ │ │ └── dummy-pdf.jpg │ │ │ │ ├── rendering-engine │ │ │ │ │ ├── AppEngine-flow.svg │ │ │ │ │ ├── Component.svg │ │ │ │ │ ├── CompoundComponent.svg │ │ │ │ │ ├── RootComponent.svg │ │ │ │ │ └── tree-with-containers.svg │ │ │ │ ├── reviewers-guide │ │ │ │ │ ├── AppEngine-flow.svg │ │ │ │ │ └── incbutton-in-action.png │ │ │ │ ├── tools │ │ │ │ │ └── boilerplate-structure.png │ │ │ │ ├── try.svg │ │ │ │ ├── tutorial │ │ │ │ │ ├── app-chat-history.png │ │ │ │ │ ├── app-content-placeholder.png │ │ │ │ │ ├── app-header-and-content.png │ │ │ │ │ ├── app-links-channel-selected.png │ │ │ │ │ ├── app-links-click.png │ │ │ │ │ ├── app-navigation.png │ │ │ │ │ ├── finished-ex01.png │ │ │ │ │ ├── finished-ex02.png │ │ │ │ │ ├── hello.png │ │ │ │ │ ├── splash-screen-advanced.png │ │ │ │ │ ├── splash-screen-after-click.png │ │ │ │ │ ├── splash-screen-centered.png │ │ │ │ │ ├── splash-screen-events.png │ │ │ │ │ ├── splash-screen-expression.png │ │ │ │ │ ├── splash-screen-reuse-after.png │ │ │ │ │ ├── splash-screen-reuse-before.png │ │ │ │ │ └── splash-screen.png │ │ │ │ └── tutorial-01.png │ │ │ ├── llms.txt │ │ │ ├── logo-dark.svg │ │ │ ├── logo.svg │ │ │ ├── pg-popout.svg │ │ │ └── xmlui-logo.svg │ │ ├── serve.json │ │ └── web.config │ ├── scripts │ │ ├── download-latest-xmlui.js │ │ ├── generate-rss.js │ │ ├── get-releases.js │ │ └── utils.js │ ├── src │ │ ├── components │ │ │ ├── BlogOverview.xmlui │ │ │ ├── BlogPage.xmlui │ │ │ ├── Boxes.xmlui │ │ │ ├── Breadcrumb.xmlui │ │ │ ├── ChangeLog.xmlui │ │ │ ├── ColorPalette.xmlui │ │ │ ├── DocumentLinks.xmlui │ │ │ ├── DocumentPage.xmlui │ │ │ ├── DocumentPageNoTOC.xmlui │ │ │ ├── Icons.xmlui │ │ │ ├── IncButton.xmlui │ │ │ ├── IncButton2.xmlui │ │ │ ├── NameValue.xmlui │ │ │ ├── PageNotFound.xmlui │ │ │ ├── PaletteItem.xmlui │ │ │ ├── Palettes.xmlui │ │ │ ├── SectionHeader.xmlui │ │ │ ├── TBD.xmlui │ │ │ ├── Test.xmlui │ │ │ ├── ThemesIntro.xmlui │ │ │ ├── ThousandThemes.xmlui │ │ │ ├── TubeStops.xmlui │ │ │ ├── TubeStops.xmlui.xs │ │ │ └── TwoColumnCode.xmlui │ │ ├── config.ts │ │ ├── Main.xmlui │ │ └── themes │ │ ├── docs-theme.ts │ │ ├── earthtone.ts │ │ ├── xmlui-gray-on-default.ts │ │ ├── xmlui-green-on-default.ts │ │ └── xmlui-orange-on-default.ts │ └── tsconfig.json ├── LICENSE ├── package-lock.json ├── package.json ├── packages │ ├── xmlui-animations │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── Animation.tsx │ │ │ ├── AnimationNative.tsx │ │ │ ├── FadeAnimation.tsx │ │ │ ├── FadeInAnimation.tsx │ │ │ ├── FadeOutAnimation.tsx │ │ │ ├── index.tsx │ │ │ ├── ScaleAnimation.tsx │ │ │ └── SlideInAnimation.tsx │ │ └── tsconfig.json │ ├── xmlui-devtools │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── devtools │ │ │ │ ├── DevTools.tsx │ │ │ │ ├── DevToolsNative.module.scss │ │ │ │ ├── DevToolsNative.tsx │ │ │ │ ├── ModalDialog.module.scss │ │ │ │ ├── ModalDialog.tsx │ │ │ │ ├── ModalVisibilityContext.tsx │ │ │ │ ├── Tooltip.module.scss │ │ │ │ ├── Tooltip.tsx │ │ │ │ └── utils.ts │ │ │ ├── editor │ │ │ │ └── Editor.tsx │ │ │ └── index.tsx │ │ ├── tsconfig.json │ │ └── vite.config-overrides.ts │ ├── xmlui-hello-world │ │ ├── .gitignore │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── HelloWorld.module.scss │ │ │ ├── HelloWorld.tsx │ │ │ ├── HelloWorldNative.tsx │ │ │ └── index.tsx │ │ └── tsconfig.json │ ├── xmlui-os-frames │ │ ├── .gitignore │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── IPhoneFrame.module.scss │ │ │ ├── IPhoneFrame.tsx │ │ │ ├── MacOSAppFrame.module.scss │ │ │ ├── MacOSAppFrame.tsx │ │ │ ├── WindowsAppFrame.module.scss │ │ │ └── WindowsAppFrame.tsx │ │ └── tsconfig.json │ ├── xmlui-pdf │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ ├── components │ │ │ │ └── Pdf.xmlui │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── LazyPdfNative.tsx │ │ │ ├── Pdf.module.scss │ │ │ └── Pdf.tsx │ │ └── tsconfig.json │ ├── xmlui-playground │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── hooks │ │ │ │ ├── usePlayground.ts │ │ │ │ └── useToast.ts │ │ │ ├── index.tsx │ │ │ ├── playground │ │ │ │ ├── Box.module.scss │ │ │ │ ├── Box.tsx │ │ │ │ ├── CodeSelector.tsx │ │ │ │ ├── ConfirmationDialog.module.scss │ │ │ │ ├── ConfirmationDialog.tsx │ │ │ │ ├── Editor.tsx │ │ │ │ ├── Header.module.scss │ │ │ │ ├── Header.tsx │ │ │ │ ├── Playground.tsx │ │ │ │ ├── PlaygroundContent.module.scss │ │ │ │ ├── PlaygroundContent.tsx │ │ │ │ ├── PlaygroundNative.module.scss │ │ │ │ ├── PlaygroundNative.tsx │ │ │ │ ├── Preview.module.scss │ │ │ │ ├── Preview.tsx │ │ │ │ ├── Select.module.scss │ │ │ │ ├── StandalonePlayground.tsx │ │ │ │ ├── StandalonePlaygroundNative.module.scss │ │ │ │ ├── StandalonePlaygroundNative.tsx │ │ │ │ ├── ThemeSwitcher.module.scss │ │ │ │ ├── ThemeSwitcher.tsx │ │ │ │ ├── ToneSwitcher.tsx │ │ │ │ ├── Tooltip.module.scss │ │ │ │ ├── Tooltip.tsx │ │ │ │ └── utils.ts │ │ │ ├── providers │ │ │ │ ├── Toast.module.scss │ │ │ │ └── ToastProvider.tsx │ │ │ ├── state │ │ │ │ └── store.ts │ │ │ ├── themes │ │ │ │ └── theme.ts │ │ │ └── utils │ │ │ └── helpers.ts │ │ └── tsconfig.json │ ├── xmlui-search │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── Search.module.scss │ │ │ └── Search.tsx │ │ └── tsconfig.json │ ├── xmlui-spreadsheet │ │ ├── .gitignore │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── Spreadsheet.tsx │ │ │ └── SpreadsheetNative.tsx │ │ └── tsconfig.json │ └── xmlui-website-blocks │ ├── .gitignore │ ├── CHANGELOG.md │ ├── demo │ │ ├── components │ │ │ ├── HeroBackgroundBreakoutPage.xmlui │ │ │ ├── HeroBackgroundsPage.xmlui │ │ │ ├── HeroContentsPage.xmlui │ │ │ ├── HeroTextAlignPage.xmlui │ │ │ ├── HeroTextPage.xmlui │ │ │ └── HeroTonesPage.xmlui │ │ ├── Main.xmlui │ │ └── themes │ │ └── default.ts │ ├── index.html │ ├── index.ts │ ├── meta │ │ └── componentsMetadata.ts │ ├── package.json │ ├── public │ │ └── resources │ │ ├── building.jpg │ │ └── xmlui-logo.svg │ ├── src │ │ ├── Carousel │ │ │ ├── Carousel.module.scss │ │ │ ├── Carousel.tsx │ │ │ ├── CarouselContext.tsx │ │ │ └── CarouselNative.tsx │ │ ├── FancyButton │ │ │ ├── FancyButton.module.scss │ │ │ ├── FancyButton.tsx │ │ │ └── FancyButton.xmlui │ │ ├── Hello │ │ │ ├── Hello.tsx │ │ │ ├── Hello.xmlui │ │ │ └── Hello.xmlui.xs │ │ ├── HeroSection │ │ │ ├── HeroSection.module.scss │ │ │ ├── HeroSection.tsx │ │ │ └── HeroSectionNative.tsx │ │ ├── index.tsx │ │ ├── ScrollToTop │ │ │ ├── ScrollToTop.module.scss │ │ │ ├── ScrollToTop.tsx │ │ │ └── ScrollToTopNative.tsx │ │ └── vite-env.d.ts │ └── tsconfig.json ├── README.md ├── tools │ ├── codefence │ │ └── xmlui-code-fence-docs.md │ ├── create-app │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── create-app.ts │ │ ├── helpers │ │ │ ├── copy.ts │ │ │ ├── get-pkg-manager.ts │ │ │ ├── git.ts │ │ │ ├── install.ts │ │ │ ├── is-folder-empty.ts │ │ │ ├── is-writeable.ts │ │ │ ├── make-dir.ts │ │ │ └── validate-pkg.ts │ │ ├── index.ts │ │ ├── package.json │ │ ├── templates │ │ │ ├── default │ │ │ │ └── ts │ │ │ │ ├── gitignore │ │ │ │ ├── index.html │ │ │ │ ├── index.ts │ │ │ │ ├── public │ │ │ │ │ ├── mockServiceWorker.js │ │ │ │ │ ├── resources │ │ │ │ │ │ ├── favicon.ico │ │ │ │ │ │ └── xmlui-logo.svg │ │ │ │ │ └── serve.json │ │ │ │ └── src │ │ │ │ ├── components │ │ │ │ │ ├── ApiAware.xmlui │ │ │ │ │ ├── Home.xmlui │ │ │ │ │ ├── IncButton.xmlui │ │ │ │ │ └── PagePanel.xmlui │ │ │ │ ├── config.ts │ │ │ │ └── Main.xmlui │ │ │ ├── index.ts │ │ │ └── types.ts │ │ └── tsconfig.json │ ├── create-xmlui-hello-world │ │ ├── index.js │ │ └── package.json │ └── vscode │ ├── .gitignore │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ ├── .vscodeignore │ ├── build.sh │ ├── CHANGELOG.md │ ├── esbuild.js │ ├── eslint.config.mjs │ ├── formatter-docs.md │ ├── generate-test-sample.sh │ ├── LICENSE.md │ ├── package-lock.json │ ├── package.json │ ├── README.md │ ├── resources │ │ ├── xmlui-logo.png │ │ └── xmlui-markup-syntax-highlighting.png │ ├── src │ │ ├── extension.ts │ │ └── server.ts │ ├── syntaxes │ │ └── xmlui.tmLanguage.json │ ├── test-samples │ │ └── sample.xmlui │ ├── tsconfig.json │ └── tsconfig.tsbuildinfo ├── turbo.json └── xmlui ├── .gitignore ├── bin │ ├── bootstrap.js │ ├── build-lib.ts │ ├── build.ts │ ├── index.ts │ ├── preview.ts │ ├── start.ts │ ├── vite-xmlui-plugin.ts │ └── viteConfig.ts ├── CHANGELOG.md ├── conventions │ ├── component-qa-checklist.md │ ├── copilot-conventions.md │ ├── create-xmlui-components.md │ ├── mermaid.md │ ├── testing-conventions.md │ └── xmlui-in-a-nutshell.md ├── dev-docs │ ├── accessibility.md │ ├── build-system.md │ ├── build-xmlui.md │ ├── containers.md │ ├── glossary.md │ ├── index.md │ ├── next │ │ ├── component-dev-guide.md │ │ ├── configuration-management-enhancement-summary.md │ │ ├── documentation-scripts-refactoring-complete-summary.md │ │ ├── documentation-scripts-refactoring-plan.md │ │ ├── duplicate-pattern-extraction-summary.md │ │ ├── error-handling-standardization-summary.md │ │ ├── generating-component-reference.md │ │ ├── index.md │ │ ├── logging-consistency-implementation-summary.md │ │ ├── project-build.md │ │ ├── project-structure.md │ │ ├── theme-context.md │ │ ├── tiptap-design-considerations.md │ │ ├── working-with-code.md │ │ ├── xmlui-runtime-architecture │ │ └── xmlui-wcag-accessibility-report.md │ ├── react-fundamentals.md │ ├── release-method.md │ ├── standalone-app.md │ ├── state-management.md │ └── xmlui-repo.md ├── package.json ├── playwright.config.ts ├── scripts │ ├── coverage-only.js │ ├── e2e-test-summary.js │ ├── generate-docs │ │ ├── build-downloads-map.mjs │ │ ├── build-pages-map.mjs │ │ ├── components-config.json │ │ ├── configuration-management.mjs │ │ ├── constants.mjs │ │ ├── create-theme-files.mjs │ │ ├── DocsGenerator.mjs │ │ ├── error-handling.mjs │ │ ├── extensions-config.json │ │ ├── folders.mjs │ │ ├── generate-summary-files.mjs │ │ ├── get-docs.mjs │ │ ├── input-handler.mjs │ │ ├── logger.mjs │ │ ├── logging-standards.mjs │ │ ├── MetadataProcessor.mjs │ │ ├── pattern-utilities.mjs │ │ └── utils.mjs │ ├── get-langserver-metadata.mjs │ ├── inline-links.mjs │ └── README-e2e-summary.md ├── src │ ├── abstractions │ │ ├── _conventions.md │ │ ├── ActionDefs.ts │ │ ├── AppContextDefs.ts │ │ ├── ComponentDefs.ts │ │ ├── ContainerDefs.ts │ │ ├── ExtensionDefs.ts │ │ ├── FunctionDefs.ts │ │ ├── RendererDefs.ts │ │ ├── scripting │ │ │ ├── BlockScope.ts │ │ │ ├── Compilation.ts │ │ │ ├── LogicalThread.ts │ │ │ ├── LoopScope.ts │ │ │ ├── modules.ts │ │ │ ├── ScriptParserError.ts │ │ │ ├── Token.ts │ │ │ ├── TryScope.ts │ │ │ └── TryScopeExp.ts │ │ └── ThemingDefs.ts │ ├── components │ │ ├── _conventions.md │ │ ├── abstractions.ts │ │ ├── Accordion │ │ │ ├── Accordion.md │ │ │ ├── Accordion.module.scss │ │ │ ├── Accordion.spec.ts │ │ │ ├── Accordion.tsx │ │ │ ├── AccordionContext.tsx │ │ │ ├── AccordionItem.tsx │ │ │ ├── AccordionItemNative.tsx │ │ │ └── AccordionNative.tsx │ │ ├── Animation │ │ │ └── AnimationNative.tsx │ │ ├── APICall │ │ │ ├── APICall.md │ │ │ ├── APICall.spec.ts │ │ │ ├── APICall.tsx │ │ │ └── APICallNative.tsx │ │ ├── App │ │ │ ├── App.md │ │ │ ├── App.module.scss │ │ │ ├── App.spec.ts │ │ │ ├── App.tsx │ │ │ ├── AppLayoutContext.ts │ │ │ ├── AppNative.tsx │ │ │ ├── AppStateContext.ts │ │ │ ├── doc-resources │ │ │ │ ├── condensed-sticky.xmlui │ │ │ │ ├── condensed.xmlui │ │ │ │ ├── horizontal-sticky.xmlui │ │ │ │ ├── horizontal.xmlui │ │ │ │ ├── vertical-full-header.xmlui │ │ │ │ ├── vertical-sticky.xmlui │ │ │ │ └── vertical.xmlui │ │ │ ├── IndexerContext.ts │ │ │ ├── LinkInfoContext.ts │ │ │ ├── SearchContext.tsx │ │ │ ├── Sheet.module.scss │ │ │ └── Sheet.tsx │ │ ├── AppHeader │ │ │ ├── AppHeader.md │ │ │ ├── AppHeader.module.scss │ │ │ ├── AppHeader.spec.ts │ │ │ ├── AppHeader.tsx │ │ │ └── AppHeaderNative.tsx │ │ ├── AppState │ │ │ ├── AppState.md │ │ │ ├── AppState.spec.ts │ │ │ ├── AppState.tsx │ │ │ └── AppStateNative.tsx │ │ ├── AutoComplete │ │ │ ├── AutoComplete.md │ │ │ ├── AutoComplete.module.scss │ │ │ ├── AutoComplete.spec.ts │ │ │ ├── AutoComplete.tsx │ │ │ ├── AutoCompleteContext.tsx │ │ │ └── AutoCompleteNative.tsx │ │ ├── Avatar │ │ │ ├── Avatar.md │ │ │ ├── Avatar.module.scss │ │ │ ├── Avatar.spec.ts │ │ │ ├── Avatar.tsx │ │ │ └── AvatarNative.tsx │ │ ├── Backdrop │ │ │ ├── Backdrop.md │ │ │ ├── Backdrop.module.scss │ │ │ ├── Backdrop.spec.ts │ │ │ ├── Backdrop.tsx │ │ │ └── BackdropNative.tsx │ │ ├── Badge │ │ │ ├── Badge.md │ │ │ ├── Badge.module.scss │ │ │ ├── Badge.spec.ts │ │ │ ├── Badge.tsx │ │ │ └── BadgeNative.tsx │ │ ├── Bookmark │ │ │ ├── Bookmark.md │ │ │ ├── Bookmark.module.scss │ │ │ ├── Bookmark.spec.ts │ │ │ ├── Bookmark.tsx │ │ │ └── BookmarkNative.tsx │ │ ├── Breakout │ │ │ ├── Breakout.module.scss │ │ │ ├── Breakout.spec.ts │ │ │ ├── Breakout.tsx │ │ │ └── BreakoutNative.tsx │ │ ├── Button │ │ │ ├── Button-style.spec.ts │ │ │ ├── Button.md │ │ │ ├── Button.module.scss │ │ │ ├── Button.spec.ts │ │ │ ├── Button.tsx │ │ │ └── ButtonNative.tsx │ │ ├── Card │ │ │ ├── Card.md │ │ │ ├── Card.module.scss │ │ │ ├── Card.spec.ts │ │ │ ├── Card.tsx │ │ │ └── CardNative.tsx │ │ ├── Carousel │ │ │ ├── Carousel.md │ │ │ ├── Carousel.module.scss │ │ │ ├── Carousel.spec.ts │ │ │ ├── Carousel.tsx │ │ │ ├── CarouselContext.tsx │ │ │ ├── CarouselItem.tsx │ │ │ ├── CarouselItemNative.tsx │ │ │ └── CarouselNative.tsx │ │ ├── ChangeListener │ │ │ ├── ChangeListener.md │ │ │ ├── ChangeListener.spec.ts │ │ │ ├── ChangeListener.tsx │ │ │ └── ChangeListenerNative.tsx │ │ ├── chart-color-schemes.ts │ │ ├── Charts │ │ │ ├── AreaChart │ │ │ │ ├── AreaChart.md │ │ │ │ ├── AreaChart.spec.ts │ │ │ │ ├── AreaChart.tsx │ │ │ │ └── AreaChartNative.tsx │ │ │ ├── BarChart │ │ │ │ ├── BarChart.md │ │ │ │ ├── BarChart.module.scss │ │ │ │ ├── BarChart.spec.ts │ │ │ │ ├── BarChart.tsx │ │ │ │ └── BarChartNative.tsx │ │ │ ├── DonutChart │ │ │ │ ├── DonutChart.spec.ts │ │ │ │ └── DonutChart.tsx │ │ │ ├── LabelList │ │ │ │ ├── LabelList.spec.ts │ │ │ │ ├── LabelList.tsx │ │ │ │ ├── LabelListNative.module.scss │ │ │ │ └── LabelListNative.tsx │ │ │ ├── Legend │ │ │ │ ├── Legend.spec.ts │ │ │ │ ├── Legend.tsx │ │ │ │ └── LegendNative.tsx │ │ │ ├── LineChart │ │ │ │ ├── LineChart.md │ │ │ │ ├── LineChart.module.scss │ │ │ │ ├── LineChart.spec.ts │ │ │ │ ├── LineChart.tsx │ │ │ │ └── LineChartNative.tsx │ │ │ ├── PieChart │ │ │ │ ├── PieChart.md │ │ │ │ ├── PieChart.spec.ts │ │ │ │ ├── PieChart.tsx │ │ │ │ ├── PieChartNative.module.scss │ │ │ │ └── PieChartNative.tsx │ │ │ ├── RadarChart │ │ │ │ ├── RadarChart.md │ │ │ │ ├── RadarChart.spec.ts │ │ │ │ ├── RadarChart.tsx │ │ │ │ └── RadarChartNative.tsx │ │ │ ├── Tooltip │ │ │ │ ├── TooltipContent.module.scss │ │ │ │ ├── TooltipContent.spec.ts │ │ │ │ └── TooltipContent.tsx │ │ │ └── utils │ │ │ ├── abstractions.ts │ │ │ └── ChartProvider.tsx │ │ ├── Checkbox │ │ │ ├── Checkbox.md │ │ │ ├── Checkbox.spec.ts │ │ │ └── Checkbox.tsx │ │ ├── CodeBlock │ │ │ ├── CodeBlock.module.scss │ │ │ ├── CodeBlock.spec.ts │ │ │ ├── CodeBlock.tsx │ │ │ ├── CodeBlockNative.tsx │ │ │ └── highlight-code.ts │ │ ├── collectedComponentMetadata.ts │ │ ├── ColorPicker │ │ │ ├── ColorPicker.md │ │ │ ├── ColorPicker.module.scss │ │ │ ├── ColorPicker.spec.ts │ │ │ ├── ColorPicker.tsx │ │ │ └── ColorPickerNative.tsx │ │ ├── Column │ │ │ ├── Column.md │ │ │ ├── Column.tsx │ │ │ ├── ColumnNative.tsx │ │ │ ├── doc-resources │ │ │ │ └── list-component-data.js │ │ │ └── TableContext.tsx │ │ ├── component-utils.ts │ │ ├── ComponentProvider.tsx │ │ ├── ComponentRegistryContext.tsx │ │ ├── container-helpers.tsx │ │ ├── ContentSeparator │ │ │ ├── ContentSeparator.md │ │ │ ├── ContentSeparator.module.scss │ │ │ ├── ContentSeparator.spec.ts │ │ │ ├── ContentSeparator.tsx │ │ │ └── ContentSeparatorNative.tsx │ │ ├── DataSource │ │ │ ├── DataSource.md │ │ │ └── DataSource.tsx │ │ ├── DateInput │ │ │ ├── DateInput.md │ │ │ ├── DateInput.module.scss │ │ │ ├── DateInput.spec.ts │ │ │ ├── DateInput.tsx │ │ │ └── DateInputNative.tsx │ │ ├── DatePicker │ │ │ ├── DatePicker.md │ │ │ ├── DatePicker.module.scss │ │ │ ├── DatePicker.spec.ts │ │ │ ├── DatePicker.tsx │ │ │ └── DatePickerNative.tsx │ │ ├── DropdownMenu │ │ │ ├── DropdownMenu.md │ │ │ ├── DropdownMenu.module.scss │ │ │ ├── DropdownMenu.spec.ts │ │ │ ├── DropdownMenu.tsx │ │ │ ├── DropdownMenuNative.tsx │ │ │ ├── MenuItem.md │ │ │ └── SubMenuItem.md │ │ ├── EmojiSelector │ │ │ ├── EmojiSelector.md │ │ │ ├── EmojiSelector.spec.ts │ │ │ ├── EmojiSelector.tsx │ │ │ └── EmojiSelectorNative.tsx │ │ ├── ExpandableItem │ │ │ ├── ExpandableItem.module.scss │ │ │ ├── ExpandableItem.spec.ts │ │ │ ├── ExpandableItem.tsx │ │ │ └── ExpandableItemNative.tsx │ │ ├── FileInput │ │ │ ├── FileInput.md │ │ │ ├── FileInput.module.scss │ │ │ ├── FileInput.spec.ts │ │ │ ├── FileInput.tsx │ │ │ └── FileInputNative.tsx │ │ ├── FileUploadDropZone │ │ │ ├── FileUploadDropZone.md │ │ │ ├── FileUploadDropZone.module.scss │ │ │ ├── FileUploadDropZone.spec.ts │ │ │ ├── FileUploadDropZone.tsx │ │ │ └── FileUploadDropZoneNative.tsx │ │ ├── FlowLayout │ │ │ ├── FlowLayout.md │ │ │ ├── FlowLayout.module.scss │ │ │ ├── FlowLayout.spec.ts │ │ │ ├── FlowLayout.spec.ts-snapshots │ │ │ │ └── Edge-cases-boxShadow-is-not-clipped-1-non-smoke-darwin.png │ │ │ ├── FlowLayout.tsx │ │ │ └── FlowLayoutNative.tsx │ │ ├── Footer │ │ │ ├── Footer.md │ │ │ ├── Footer.module.scss │ │ │ ├── Footer.spec.ts │ │ │ ├── Footer.tsx │ │ │ └── FooterNative.tsx │ │ ├── Form │ │ │ ├── Form.md │ │ │ ├── Form.module.scss │ │ │ ├── Form.spec.ts │ │ │ ├── Form.tsx │ │ │ ├── formActions.ts │ │ │ ├── FormContext.ts │ │ │ └── FormNative.tsx │ │ ├── FormItem │ │ │ ├── FormItem.md │ │ │ ├── FormItem.module.scss │ │ │ ├── FormItem.spec.ts │ │ │ ├── FormItem.tsx │ │ │ ├── FormItemNative.tsx │ │ │ ├── HelperText.module.scss │ │ │ ├── HelperText.tsx │ │ │ ├── ItemWithLabel.tsx │ │ │ └── Validations.ts │ │ ├── FormSection │ │ │ ├── FormSection.md │ │ │ ├── FormSection.ts │ │ │ └── FormSection.xmlui │ │ ├── Fragment │ │ │ ├── Fragment.spec.ts │ │ │ └── Fragment.tsx │ │ ├── Heading │ │ │ ├── abstractions.ts │ │ │ ├── H1.md │ │ │ ├── H1.spec.ts │ │ │ ├── H2.md │ │ │ ├── H2.spec.ts │ │ │ ├── H3.md │ │ │ ├── H3.spec.ts │ │ │ ├── H4.md │ │ │ ├── H4.spec.ts │ │ │ ├── H5.md │ │ │ ├── H5.spec.ts │ │ │ ├── H6.md │ │ │ ├── H6.spec.ts │ │ │ ├── Heading.md │ │ │ ├── Heading.module.scss │ │ │ ├── Heading.spec.ts │ │ │ ├── Heading.tsx │ │ │ └── HeadingNative.tsx │ │ ├── HoverCard │ │ │ ├── HoverCard.tsx │ │ │ └── HovercardNative.tsx │ │ ├── HtmlTags │ │ │ ├── HtmlTags.module.scss │ │ │ ├── HtmlTags.spec.ts │ │ │ └── HtmlTags.tsx │ │ ├── Icon │ │ │ ├── AdmonitionDanger.tsx │ │ │ ├── AdmonitionInfo.tsx │ │ │ ├── AdmonitionNote.tsx │ │ │ ├── AdmonitionTip.tsx │ │ │ ├── AdmonitionWarning.tsx │ │ │ ├── ApiIcon.tsx │ │ │ ├── ArrowDropDown.module.scss │ │ │ ├── ArrowDropDown.tsx │ │ │ ├── ArrowDropUp.module.scss │ │ │ ├── ArrowDropUp.tsx │ │ │ ├── ArrowLeft.module.scss │ │ │ ├── ArrowLeft.tsx │ │ │ ├── ArrowRight.module.scss │ │ │ ├── ArrowRight.tsx │ │ │ ├── Attach.tsx │ │ │ ├── Binding.module.scss │ │ │ ├── Binding.tsx │ │ │ ├── BoardIcon.tsx │ │ │ ├── BoxIcon.tsx │ │ │ ├── CheckIcon.tsx │ │ │ ├── ChevronDownIcon.tsx │ │ │ ├── ChevronLeft.tsx │ │ │ ├── ChevronRight.tsx │ │ │ ├── ChevronUpIcon.tsx │ │ │ ├── CodeFileIcon.tsx │ │ │ ├── CodeSandbox.tsx │ │ │ ├── CompactListIcon.tsx │ │ │ ├── ContentCopyIcon.tsx │ │ │ ├── DarkToLightIcon.tsx │ │ │ ├── DatabaseIcon.module.scss │ │ │ ├── DatabaseIcon.tsx │ │ │ ├── DocFileIcon.tsx │ │ │ ├── DocIcon.tsx │ │ │ ├── DotMenuHorizontalIcon.tsx │ │ │ ├── DotMenuIcon.tsx │ │ │ ├── EmailIcon.tsx │ │ │ ├── EmptyFolderIcon.tsx │ │ │ ├── ErrorIcon.tsx │ │ │ ├── ExpressionIcon.tsx │ │ │ ├── FillPlusCricleIcon.tsx │ │ │ ├── FilterIcon.tsx │ │ │ ├── FolderIcon.tsx │ │ │ ├── GlobeIcon.tsx │ │ │ ├── HomeIcon.tsx │ │ │ ├── HyperLinkIcon.tsx │ │ │ ├── Icon.md │ │ │ ├── Icon.module.scss │ │ │ ├── Icon.spec.ts │ │ │ ├── Icon.tsx │ │ │ ├── IconNative.tsx │ │ │ ├── ImageFileIcon.tsx │ │ │ ├── Inspect.tsx │ │ │ ├── LightToDark.tsx │ │ │ ├── LinkIcon.tsx │ │ │ ├── ListIcon.tsx │ │ │ ├── LooseListIcon.tsx │ │ │ ├── MoonIcon.tsx │ │ │ ├── MoreOptionsIcon.tsx │ │ │ ├── NoSortIcon.tsx │ │ │ ├── PDFIcon.tsx │ │ │ ├── PenIcon.tsx │ │ │ ├── PhoneIcon.tsx │ │ │ ├── PhotoIcon.tsx │ │ │ ├── PlusIcon.tsx │ │ │ ├── SearchIcon.tsx │ │ │ ├── ShareIcon.tsx │ │ │ ├── SortAscendingIcon.tsx │ │ │ ├── SortDescendingIcon.tsx │ │ │ ├── StarsIcon.tsx │ │ │ ├── SunIcon.tsx │ │ │ ├── svg │ │ │ │ ├── admonition_danger.svg │ │ │ │ ├── admonition_info.svg │ │ │ │ ├── admonition_note.svg │ │ │ │ ├── admonition_tip.svg │ │ │ │ ├── admonition_warning.svg │ │ │ │ ├── api.svg │ │ │ │ ├── arrow-dropdown.svg │ │ │ │ ├── arrow-left.svg │ │ │ │ ├── arrow-right.svg │ │ │ │ ├── arrow-up.svg │ │ │ │ ├── attach.svg │ │ │ │ ├── binding.svg │ │ │ │ ├── box.svg │ │ │ │ ├── bulb.svg │ │ │ │ ├── code-file.svg │ │ │ │ ├── code-sandbox.svg │ │ │ │ ├── dark_to_light.svg │ │ │ │ ├── database.svg │ │ │ │ ├── doc.svg │ │ │ │ ├── empty-folder.svg │ │ │ │ ├── expression.svg │ │ │ │ ├── eye-closed.svg │ │ │ │ ├── eye-dark.svg │ │ │ │ ├── eye.svg │ │ │ │ ├── file-text.svg │ │ │ │ ├── filter.svg │ │ │ │ ├── folder.svg │ │ │ │ ├── img.svg │ │ │ │ ├── inspect.svg │ │ │ │ ├── light_to_dark.svg │ │ │ │ ├── moon.svg │ │ │ │ ├── pdf.svg │ │ │ │ ├── photo.svg │ │ │ │ ├── share.svg │ │ │ │ ├── stars.svg │ │ │ │ ├── sun.svg │ │ │ │ ├── trending-down.svg │ │ │ │ ├── trending-level.svg │ │ │ │ ├── trending-up.svg │ │ │ │ ├── txt.svg │ │ │ │ ├── unknown-file.svg │ │ │ │ ├── unlink.svg │ │ │ │ └── xls.svg │ │ │ ├── TableDeleteColumnIcon.tsx │ │ │ ├── TableDeleteRowIcon.tsx │ │ │ ├── TableInsertColumnIcon.tsx │ │ │ ├── TableInsertRowIcon.tsx │ │ │ ├── TrashIcon.tsx │ │ │ ├── TrendingDownIcon.tsx │ │ │ ├── TrendingLevelIcon.tsx │ │ │ ├── TrendingUpIcon.tsx │ │ │ ├── TxtIcon.tsx │ │ │ ├── UnknownFileIcon.tsx │ │ │ ├── UnlinkIcon.tsx │ │ │ ├── UserIcon.tsx │ │ │ ├── WarningIcon.tsx │ │ │ └── XlsIcon.tsx │ │ ├── IconProvider.tsx │ │ ├── IconRegistryContext.tsx │ │ ├── IFrame │ │ │ ├── IFrame.md │ │ │ ├── IFrame.module.scss │ │ │ ├── IFrame.spec.ts │ │ │ ├── IFrame.tsx │ │ │ └── IFrameNative.tsx │ │ ├── Image │ │ │ ├── Image.md │ │ │ ├── Image.module.scss │ │ │ ├── Image.spec.ts │ │ │ ├── Image.tsx │ │ │ └── ImageNative.tsx │ │ ├── Input │ │ │ ├── index.ts │ │ │ ├── InputAdornment.module.scss │ │ │ ├── InputAdornment.tsx │ │ │ ├── InputDivider.module.scss │ │ │ ├── InputDivider.tsx │ │ │ ├── InputLabel.module.scss │ │ │ ├── InputLabel.tsx │ │ │ ├── PartialInput.module.scss │ │ │ └── PartialInput.tsx │ │ ├── InspectButton │ │ │ ├── InspectButton.module.scss │ │ │ └── InspectButton.tsx │ │ ├── Items │ │ │ ├── Items.md │ │ │ ├── Items.spec.ts │ │ │ ├── Items.tsx │ │ │ └── ItemsNative.tsx │ │ ├── Link │ │ │ ├── Link.md │ │ │ ├── Link.module.scss │ │ │ ├── Link.spec.ts │ │ │ ├── Link.tsx │ │ │ └── LinkNative.tsx │ │ ├── List │ │ │ ├── doc-resources │ │ │ │ └── list-component-data.js │ │ │ ├── List.md │ │ │ ├── List.module.scss │ │ │ ├── List.spec.ts │ │ │ ├── List.tsx │ │ │ └── ListNative.tsx │ │ ├── Logo │ │ │ ├── doc-resources │ │ │ │ └── xmlui-logo.svg │ │ │ ├── Logo.md │ │ │ ├── Logo.tsx │ │ │ └── LogoNative.tsx │ │ ├── Markdown │ │ │ ├── CodeText.module.scss │ │ │ ├── CodeText.tsx │ │ │ ├── Markdown.md │ │ │ ├── Markdown.module.scss │ │ │ ├── Markdown.spec.ts │ │ │ ├── Markdown.tsx │ │ │ ├── MarkdownNative.tsx │ │ │ ├── parse-binding-expr.ts │ │ │ └── utils.ts │ │ ├── metadata-helpers.ts │ │ ├── ModalDialog │ │ │ ├── ConfirmationModalContextProvider.tsx │ │ │ ├── Dialog.module.scss │ │ │ ├── Dialog.tsx │ │ │ ├── ModalDialog.md │ │ │ ├── ModalDialog.module.scss │ │ │ ├── ModalDialog.spec.ts │ │ │ ├── ModalDialog.tsx │ │ │ ├── ModalDialogNative.tsx │ │ │ └── ModalVisibilityContext.tsx │ │ ├── NavGroup │ │ │ ├── NavGroup.md │ │ │ ├── NavGroup.module.scss │ │ │ ├── NavGroup.spec.ts │ │ │ ├── NavGroup.tsx │ │ │ ├── NavGroupContext.ts │ │ │ └── NavGroupNative.tsx │ │ ├── NavLink │ │ │ ├── NavLink.md │ │ │ ├── NavLink.module.scss │ │ │ ├── NavLink.spec.ts │ │ │ ├── NavLink.tsx │ │ │ └── NavLinkNative.tsx │ │ ├── NavPanel │ │ │ ├── NavPanel.md │ │ │ ├── NavPanel.module.scss │ │ │ ├── NavPanel.spec.ts │ │ │ ├── NavPanel.tsx │ │ │ └── NavPanelNative.tsx │ │ ├── NestedApp │ │ │ ├── AppWithCodeView.module.scss │ │ │ ├── AppWithCodeView.tsx │ │ │ ├── AppWithCodeViewNative.tsx │ │ │ ├── defaultProps.tsx │ │ │ ├── logo.svg │ │ │ ├── NestedApp.module.scss │ │ │ ├── NestedApp.tsx │ │ │ ├── NestedAppNative.tsx │ │ │ ├── Tooltip.module.scss │ │ │ ├── Tooltip.tsx │ │ │ └── utils.ts │ │ ├── NoResult │ │ │ ├── NoResult.md │ │ │ ├── NoResult.module.scss │ │ │ ├── NoResult.spec.ts │ │ │ ├── NoResult.tsx │ │ │ └── NoResultNative.tsx │ │ ├── NumberBox │ │ │ ├── numberbox-abstractions.ts │ │ │ ├── NumberBox.md │ │ │ ├── NumberBox.module.scss │ │ │ ├── NumberBox.spec.ts │ │ │ ├── NumberBox.tsx │ │ │ └── NumberBoxNative.tsx │ │ ├── Option │ │ │ ├── Option.md │ │ │ ├── Option.spec.ts │ │ │ ├── Option.tsx │ │ │ ├── OptionNative.tsx │ │ │ └── OptionTypeProvider.tsx │ │ ├── PageMetaTitle │ │ │ ├── PageMetaTilteNative.tsx │ │ │ ├── PageMetaTitle.md │ │ │ ├── PageMetaTitle.spec.ts │ │ │ └── PageMetaTitle.tsx │ │ ├── Pages │ │ │ ├── Page.md │ │ │ ├── Pages.md │ │ │ ├── Pages.module.scss │ │ │ ├── Pages.tsx │ │ │ └── PagesNative.tsx │ │ ├── Pagination │ │ │ ├── Pagination.md │ │ │ ├── Pagination.module.scss │ │ │ ├── Pagination.spec.ts │ │ │ ├── Pagination.tsx │ │ │ └── PaginationNative.tsx │ │ ├── PositionedContainer │ │ │ ├── PositionedContainer.module.scss │ │ │ ├── PositionedContainer.tsx │ │ │ └── PositionedContainerNative.tsx │ │ ├── ProfileMenu │ │ │ ├── ProfileMenu.module.scss │ │ │ └── ProfileMenu.tsx │ │ ├── ProgressBar │ │ │ ├── ProgressBar.md │ │ │ ├── ProgressBar.module.scss │ │ │ ├── ProgressBar.spec.ts │ │ │ ├── ProgressBar.tsx │ │ │ └── ProgressBarNative.tsx │ │ ├── Queue │ │ │ ├── Queue.md │ │ │ ├── Queue.spec.ts │ │ │ ├── Queue.tsx │ │ │ ├── queueActions.ts │ │ │ └── QueueNative.tsx │ │ ├── RadioGroup │ │ │ ├── RadioGroup.md │ │ │ ├── RadioGroup.module.scss │ │ │ ├── RadioGroup.spec.ts │ │ │ ├── RadioGroup.tsx │ │ │ ├── RadioGroupNative.tsx │ │ │ ├── RadioItem.tsx │ │ │ └── RadioItemNative.tsx │ │ ├── RealTimeAdapter │ │ │ ├── RealTimeAdapter.tsx │ │ │ └── RealTimeAdapterNative.tsx │ │ ├── Redirect │ │ │ ├── Redirect.md │ │ │ ├── Redirect.spec.ts │ │ │ └── Redirect.tsx │ │ ├── ResponsiveBar │ │ │ ├── README.md │ │ │ ├── ResponsiveBar.md │ │ │ ├── ResponsiveBar.module.scss │ │ │ ├── ResponsiveBar.spec.ts │ │ │ ├── ResponsiveBar.tsx │ │ │ └── ResponsiveBarNative.tsx │ │ ├── Select │ │ │ ├── HiddenOption.tsx │ │ │ ├── MultiSelectOption.tsx │ │ │ ├── OptionContext.ts │ │ │ ├── Select.md │ │ │ ├── Select.module.scss │ │ │ ├── Select.spec.ts │ │ │ ├── Select.tsx │ │ │ ├── SelectContext.tsx │ │ │ ├── SelectNative.tsx │ │ │ ├── SelectOption.tsx │ │ │ └── SimpleSelect.tsx │ │ ├── SelectionStore │ │ │ ├── SelectionStore.md │ │ │ ├── SelectionStore.tsx │ │ │ └── SelectionStoreNative.tsx │ │ ├── Slider │ │ │ ├── Slider.md │ │ │ ├── Slider.module.scss │ │ │ ├── Slider.spec.ts │ │ │ ├── Slider.tsx │ │ │ └── SliderNative.tsx │ │ ├── Slot │ │ │ ├── Slot.md │ │ │ ├── Slot.spec.ts │ │ │ └── Slot.ts │ │ ├── SlotItem.tsx │ │ ├── SpaceFiller │ │ │ ├── SpaceFiller.md │ │ │ ├── SpaceFiller.module.scss │ │ │ ├── SpaceFiller.spec.ts │ │ │ ├── SpaceFiller.tsx │ │ │ └── SpaceFillerNative.tsx │ │ ├── Spinner │ │ │ ├── Spinner.md │ │ │ ├── Spinner.module.scss │ │ │ ├── Spinner.spec.ts │ │ │ ├── Spinner.tsx │ │ │ └── SpinnerNative.tsx │ │ ├── Splitter │ │ │ ├── HSplitter.md │ │ │ ├── HSplitter.spec.ts │ │ │ ├── Splitter.md │ │ │ ├── Splitter.module.scss │ │ │ ├── Splitter.spec.ts │ │ │ ├── Splitter.tsx │ │ │ ├── SplitterNative.tsx │ │ │ ├── utils.ts │ │ │ ├── VSplitter.md │ │ │ └── VSplitter.spec.ts │ │ ├── Stack │ │ │ ├── CHStack.md │ │ │ ├── CHStack.spec.ts │ │ │ ├── CVStack.md │ │ │ ├── CVStack.spec.ts │ │ │ ├── HStack.md │ │ │ ├── HStack.spec.ts │ │ │ ├── Stack.md │ │ │ ├── Stack.module.scss │ │ │ ├── Stack.spec.ts │ │ │ ├── Stack.tsx │ │ │ ├── StackNative.tsx │ │ │ ├── VStack.md │ │ │ └── VStack.spec.ts │ │ ├── StickyBox │ │ │ ├── StickyBox.md │ │ │ ├── StickyBox.module.scss │ │ │ ├── StickyBox.tsx │ │ │ └── StickyBoxNative.tsx │ │ ├── Switch │ │ │ ├── Switch.md │ │ │ ├── Switch.spec.ts │ │ │ └── Switch.tsx │ │ ├── Table │ │ │ ├── doc-resources │ │ │ │ └── list-component-data.js │ │ │ ├── react-table-config.d.ts │ │ │ ├── Table.md │ │ │ ├── Table.module.scss │ │ │ ├── Table.spec.ts │ │ │ ├── Table.tsx │ │ │ ├── TableNative.tsx │ │ │ └── useRowSelection.tsx │ │ ├── TableOfContents │ │ │ ├── TableOfContents.module.scss │ │ │ ├── TableOfContents.spec.ts │ │ │ ├── TableOfContents.tsx │ │ │ └── TableOfContentsNative.tsx │ │ ├── Tabs │ │ │ ├── TabContext.tsx │ │ │ ├── TabItem.md │ │ │ ├── TabItem.tsx │ │ │ ├── TabItemNative.tsx │ │ │ ├── Tabs.md │ │ │ ├── Tabs.module.scss │ │ │ ├── Tabs.spec.ts │ │ │ ├── Tabs.tsx │ │ │ └── TabsNative.tsx │ │ ├── Text │ │ │ ├── Text.md │ │ │ ├── Text.module.scss │ │ │ ├── Text.spec.ts │ │ │ ├── Text.tsx │ │ │ └── TextNative.tsx │ │ ├── TextArea │ │ │ ├── TextArea.md │ │ │ ├── TextArea.module.scss │ │ │ ├── TextArea.spec.ts │ │ │ ├── TextArea.tsx │ │ │ ├── TextAreaNative.tsx │ │ │ ├── TextAreaResizable.tsx │ │ │ └── useComposedRef.ts │ │ ├── TextBox │ │ │ ├── TextBox.md │ │ │ ├── TextBox.module.scss │ │ │ ├── TextBox.spec.ts │ │ │ ├── TextBox.tsx │ │ │ └── TextBoxNative.tsx │ │ ├── Theme │ │ │ ├── NotificationToast.tsx │ │ │ ├── Theme.md │ │ │ ├── Theme.module.scss │ │ │ ├── Theme.spec.ts │ │ │ ├── Theme.tsx │ │ │ └── ThemeNative.tsx │ │ ├── TimeInput │ │ │ ├── TimeInput.md │ │ │ ├── TimeInput.module.scss │ │ │ ├── TimeInput.spec.ts │ │ │ ├── TimeInput.tsx │ │ │ ├── TimeInputNative.tsx │ │ │ └── utils.ts │ │ ├── Timer │ │ │ ├── Timer.md │ │ │ ├── Timer.spec.ts │ │ │ ├── Timer.tsx │ │ │ └── TimerNative.tsx │ │ ├── Toggle │ │ │ ├── Toggle.module.scss │ │ │ └── Toggle.tsx │ │ ├── ToneChangerButton │ │ │ ├── ToneChangerButton.md │ │ │ ├── ToneChangerButton.spec.ts │ │ │ └── ToneChangerButton.tsx │ │ ├── ToneSwitch │ │ │ ├── ToneSwitch.md │ │ │ ├── ToneSwitch.module.scss │ │ │ ├── ToneSwitch.spec.ts │ │ │ ├── ToneSwitch.tsx │ │ │ └── ToneSwitchNative.tsx │ │ ├── Tooltip │ │ │ ├── Tooltip.md │ │ │ ├── Tooltip.module.scss │ │ │ ├── Tooltip.spec.ts │ │ │ ├── Tooltip.tsx │ │ │ └── TooltipNative.tsx │ │ ├── Tree │ │ │ ├── testData.ts │ │ │ ├── Tree-dynamic.spec.ts │ │ │ ├── Tree-icons.spec.ts │ │ │ ├── Tree.md │ │ │ ├── Tree.spec.ts │ │ │ ├── TreeComponent.module.scss │ │ │ ├── TreeComponent.tsx │ │ │ └── TreeNative.tsx │ │ ├── TreeDisplay │ │ │ ├── TreeDisplay.md │ │ │ ├── TreeDisplay.module.scss │ │ │ ├── TreeDisplay.tsx │ │ │ └── TreeDisplayNative.tsx │ │ ├── ValidationSummary │ │ │ ├── ValidationSummary.module.scss │ │ │ └── ValidationSummary.tsx │ │ └── VisuallyHidden.tsx │ ├── components-core │ │ ├── abstractions │ │ │ ├── ComponentRenderer.ts │ │ │ ├── LoaderRenderer.ts │ │ │ ├── standalone.ts │ │ │ └── treeAbstractions.ts │ │ ├── action │ │ │ ├── actions.ts │ │ │ ├── APICall.tsx │ │ │ ├── FileDownloadAction.tsx │ │ │ ├── FileUploadAction.tsx │ │ │ ├── NavigateAction.tsx │ │ │ └── TimedAction.tsx │ │ ├── ApiBoundComponent.tsx │ │ ├── appContext │ │ │ ├── date-functions.ts │ │ │ ├── math-function.ts │ │ │ └── misc-utils.ts │ │ ├── AppContext.tsx │ │ ├── behaviors │ │ │ ├── Behavior.tsx │ │ │ ├── BehaviorContext.tsx │ │ │ └── CoreBehaviors.tsx │ │ ├── component-hooks.ts │ │ ├── ComponentDecorator.tsx │ │ ├── ComponentViewer.tsx │ │ ├── CompoundComponent.tsx │ │ ├── constants.ts │ │ ├── DebugViewProvider.tsx │ │ ├── descriptorHelper.ts │ │ ├── devtools │ │ │ ├── InspectorDialog.module.scss │ │ │ ├── InspectorDialog.tsx │ │ │ └── InspectorDialogVisibilityContext.tsx │ │ ├── EngineError.ts │ │ ├── event-handlers.ts │ │ ├── InspectorButton.module.scss │ │ ├── InspectorContext.tsx │ │ ├── interception │ │ │ ├── abstractions.ts │ │ │ ├── ApiInterceptor.ts │ │ │ ├── ApiInterceptorProvider.tsx │ │ │ ├── apiInterceptorWorker.ts │ │ │ ├── Backend.ts │ │ │ ├── Errors.ts │ │ │ ├── IndexedDb.ts │ │ │ ├── initMock.ts │ │ │ ├── InMemoryDb.ts │ │ │ ├── ReadonlyCollection.ts │ │ │ └── useApiInterceptorContext.tsx │ │ ├── loader │ │ │ ├── ApiLoader.tsx │ │ │ ├── DataLoader.tsx │ │ │ ├── ExternalDataLoader.tsx │ │ │ ├── Loader.tsx │ │ │ ├── MockLoaderRenderer.tsx │ │ │ └── PageableLoader.tsx │ │ ├── LoaderComponent.tsx │ │ ├── markup-check.ts │ │ ├── parts.ts │ │ ├── renderers.ts │ │ ├── rendering │ │ │ ├── AppContent.tsx │ │ │ ├── AppRoot.tsx │ │ │ ├── AppWrapper.tsx │ │ │ ├── buildProxy.ts │ │ │ ├── collectFnVarDeps.ts │ │ │ ├── ComponentAdapter.tsx │ │ │ ├── ComponentWrapper.tsx │ │ │ ├── Container.tsx │ │ │ ├── containers.ts │ │ │ ├── ContainerWrapper.tsx │ │ │ ├── ErrorBoundary.module.scss │ │ │ ├── ErrorBoundary.tsx │ │ │ ├── InvalidComponent.module.scss │ │ │ ├── InvalidComponent.tsx │ │ │ ├── nodeUtils.ts │ │ │ ├── reducer.ts │ │ │ ├── renderChild.tsx │ │ │ ├── StandaloneComponent.tsx │ │ │ ├── StateContainer.tsx │ │ │ ├── UnknownComponent.module.scss │ │ │ ├── UnknownComponent.tsx │ │ │ └── valueExtractor.ts │ │ ├── reportEngineError.ts │ │ ├── RestApiProxy.ts │ │ ├── script-runner │ │ │ ├── asyncProxy.ts │ │ │ ├── AttributeValueParser.ts │ │ │ ├── bannedFunctions.ts │ │ │ ├── BindingTreeEvaluationContext.ts │ │ │ ├── eval-tree-async.ts │ │ │ ├── eval-tree-common.ts │ │ │ ├── eval-tree-sync.ts │ │ │ ├── ParameterParser.ts │ │ │ ├── process-statement-async.ts │ │ │ ├── process-statement-common.ts │ │ │ ├── process-statement-sync.ts │ │ │ ├── ScriptingSourceTree.ts │ │ │ ├── simplify-expression.ts │ │ │ ├── statement-queue.ts │ │ │ └── visitors.ts │ │ ├── StandaloneApp.tsx │ │ ├── StandaloneExtensionManager.ts │ │ ├── TableOfContentsContext.tsx │ │ ├── theming │ │ │ ├── _themes.scss │ │ │ ├── component-layout-resolver.ts │ │ │ ├── extendThemeUtils.ts │ │ │ ├── hvar.ts │ │ │ ├── layout-resolver.ts │ │ │ ├── parse-layout-props.ts │ │ │ ├── StyleContext.tsx │ │ │ ├── StyleRegistry.ts │ │ │ ├── ThemeContext.tsx │ │ │ ├── ThemeProvider.tsx │ │ │ ├── themes │ │ │ │ ├── base-utils.ts │ │ │ │ ├── palette.ts │ │ │ │ ├── root.ts │ │ │ │ ├── solid.ts │ │ │ │ ├── theme-colors.ts │ │ │ │ └── xmlui.ts │ │ │ ├── themeVars.module.scss │ │ │ ├── themeVars.ts │ │ │ ├── transformThemeVars.ts │ │ │ └── utils.ts │ │ ├── utils │ │ │ ├── actionUtils.ts │ │ │ ├── audio-utils.ts │ │ │ ├── compound-utils.ts │ │ │ ├── css-utils.ts │ │ │ ├── DataLoaderQueryKeyGenerator.ts │ │ │ ├── date-utils.ts │ │ │ ├── extractParam.ts │ │ │ ├── hooks.tsx │ │ │ ├── LruCache.ts │ │ │ ├── mergeProps.ts │ │ │ ├── misc.ts │ │ │ ├── request-params.ts │ │ │ ├── statementUtils.ts │ │ │ └── treeUtils.ts │ │ └── xmlui-parser.ts │ ├── index-standalone.ts │ ├── index.scss │ ├── index.ts │ ├── language-server │ │ ├── server-common.ts │ │ ├── server-web-worker.ts │ │ ├── server.ts │ │ ├── services │ │ │ ├── common │ │ │ │ ├── docs-generation.ts │ │ │ │ ├── lsp-utils.ts │ │ │ │ ├── metadata-utils.ts │ │ │ │ └── syntax-node-utilities.ts │ │ │ ├── completion.ts │ │ │ ├── diagnostic.ts │ │ │ ├── format.ts │ │ │ └── hover.ts │ │ └── xmlui-metadata-generated.mjs │ ├── logging │ │ ├── LoggerContext.tsx │ │ ├── LoggerInitializer.tsx │ │ ├── LoggerService.ts │ │ └── xmlui.ts │ ├── logo.svg │ ├── parsers │ │ ├── common │ │ │ ├── GenericToken.ts │ │ │ ├── InputStream.ts │ │ │ └── utils.ts │ │ ├── scripting │ │ │ ├── code-behind-collect.ts │ │ │ ├── Lexer.ts │ │ │ ├── modules.ts │ │ │ ├── Parser.ts │ │ │ ├── ParserError.ts │ │ │ ├── ScriptingNodeTypes.ts │ │ │ ├── TokenTrait.ts │ │ │ ├── TokenType.ts │ │ │ └── tree-visitor.ts │ │ ├── style-parser │ │ │ ├── errors.ts │ │ │ ├── source-tree.ts │ │ │ ├── StyleInputStream.ts │ │ │ ├── StyleLexer.ts │ │ │ ├── StyleParser.ts │ │ │ └── tokens.ts │ │ └── xmlui-parser │ │ ├── CharacterCodes.ts │ │ ├── diagnostics.ts │ │ ├── fileExtensions.ts │ │ ├── index.ts │ │ ├── lint.ts │ │ ├── parser.ts │ │ ├── ParserError.ts │ │ ├── scanner.ts │ │ ├── syntax-kind.ts │ │ ├── syntax-node.ts │ │ ├── transform.ts │ │ ├── utils.ts │ │ ├── xmlui-serializer.ts │ │ └── xmlui-tree.ts │ ├── react-app-env.d.ts │ ├── syntax │ │ ├── monaco │ │ │ ├── grammar.monacoLanguage.ts │ │ │ ├── index.ts │ │ │ ├── xmlui-dark.ts │ │ │ ├── xmlui-light.ts │ │ │ └── xmluiscript.monacoLanguage.ts │ │ └── textMate │ │ ├── index.ts │ │ ├── xmlui-dark.json │ │ ├── xmlui-light.json │ │ ├── xmlui.json │ │ └── xmlui.tmLanguage.json │ ├── testing │ │ ├── assertions.ts │ │ ├── component-test-helpers.ts │ │ ├── ComponentDrivers.ts │ │ ├── drivers │ │ │ ├── DateInputDriver.ts │ │ │ ├── ModalDialogDriver.ts │ │ │ ├── NumberBoxDriver.ts │ │ │ ├── TextBoxDriver.ts │ │ │ ├── TimeInputDriver.ts │ │ │ ├── TimerDriver.ts │ │ │ └── TreeDriver.ts │ │ ├── fixtures.ts │ │ ├── infrastructure │ │ │ ├── index.html │ │ │ ├── main.tsx │ │ │ ├── public │ │ │ │ ├── mockServiceWorker.js │ │ │ │ ├── resources │ │ │ │ │ ├── bell.svg │ │ │ │ │ ├── box.svg │ │ │ │ │ ├── doc.svg │ │ │ │ │ ├── eye.svg │ │ │ │ │ ├── flower-640x480.jpg │ │ │ │ │ ├── sun.svg │ │ │ │ │ ├── test-image-100x100.jpg │ │ │ │ │ └── txt.svg │ │ │ │ └── serve.json │ │ │ └── TestBed.tsx │ │ └── themed-app-test-helpers.ts │ └── vite-env.d.ts ├── tests │ ├── components │ │ ├── CodeBlock │ │ │ └── hightlight-code.test.ts │ │ ├── playground-pattern.test.ts │ │ └── Tree │ │ └── Tree-states.test.ts │ ├── components-core │ │ ├── abstractions │ │ │ └── treeAbstractions.test.ts │ │ ├── container │ │ │ └── buildProxy.test.ts │ │ ├── interception │ │ │ ├── orderBy.test.ts │ │ │ ├── ReadOnlyCollection.test.ts │ │ │ └── request-param-converter.test.ts │ │ ├── scripts-runner │ │ │ ├── AttributeValueParser.test.ts │ │ │ ├── eval-tree-arrow-async.test.ts │ │ │ ├── eval-tree-arrow.test.ts │ │ │ ├── eval-tree-func-decl-async.test.ts │ │ │ ├── eval-tree-func-decl.test.ts │ │ │ ├── eval-tree-pre-post.test.ts │ │ │ ├── eval-tree-regression.test.ts │ │ │ ├── eval-tree.test.ts │ │ │ ├── function-proxy.test.ts │ │ │ ├── parser-regression.test.ts │ │ │ ├── process-event.test.ts │ │ │ ├── process-function.test.ts │ │ │ ├── process-implicit-context.test.ts │ │ │ ├── process-statement-asgn.test.ts │ │ │ ├── process-statement-destruct.test.ts │ │ │ ├── process-statement-regs.test.ts │ │ │ ├── process-statement-sync.test.ts │ │ │ ├── process-statement.test.ts │ │ │ ├── process-switch-sync.test.ts │ │ │ ├── process-switch.test.ts │ │ │ ├── process-try-sync.test.ts │ │ │ ├── process-try.test.ts │ │ │ └── test-helpers.ts │ │ ├── test-metadata-handler.ts │ │ ├── theming │ │ │ ├── border-segments.test.ts │ │ │ ├── component-layout.resolver.test.ts │ │ │ ├── layout-property-parser.test.ts │ │ │ ├── layout-resolver.test.ts │ │ │ ├── layout-resolver2.test.ts │ │ │ ├── layout-vp-override.test.ts │ │ │ └── padding-segments.test.ts │ │ └── utils │ │ ├── date-utils.test.ts │ │ ├── format-human-elapsed-time.test.ts │ │ └── LruCache.test.ts │ ├── language-server │ │ ├── completion.test.ts │ │ ├── format.test.ts │ │ ├── hover.test.ts │ │ └── mockData.ts │ └── parsers │ ├── common │ │ └── input-stream.test.ts │ ├── markdown │ │ └── parse-binding-expression.test.ts │ ├── parameter-parser.test.ts │ ├── paremeter-parser.test.ts │ ├── scripting │ │ ├── eval-tree-arrow.test.ts │ │ ├── eval-tree-pre-post.test.ts │ │ ├── eval-tree.test.ts │ │ ├── function-proxy.test.ts │ │ ├── lexer-literals.test.ts │ │ ├── lexer-misc.test.ts │ │ ├── module-parse.test.ts │ │ ├── parser-arrow.test.ts │ │ ├── parser-assignments.test.ts │ │ ├── parser-binary.test.ts │ │ ├── parser-destructuring.test.ts │ │ ├── parser-errors.test.ts │ │ ├── parser-expressions.test.ts │ │ ├── parser-function.test.ts │ │ ├── parser-literals.test.ts │ │ ├── parser-primary.test.ts │ │ ├── parser-regex.test.ts │ │ ├── parser-statements.test.ts │ │ ├── parser-unary.test.ts │ │ ├── process-event.test.ts │ │ ├── process-implicit-context.test.ts │ │ ├── process-statement-asgn.test.ts │ │ ├── process-statement-destruct.test.ts │ │ ├── process-statement-regs.test.ts │ │ ├── process-statement-sync.test.ts │ │ ├── process-statement.test.ts │ │ ├── process-switch-sync.test.ts │ │ ├── process-switch.test.ts │ │ ├── process-try-sync.test.ts │ │ ├── process-try.test.ts │ │ ├── simplify-expression.test.ts │ │ ├── statement-hooks.test.ts │ │ └── test-helpers.ts │ ├── style-parser │ │ ├── generateHvarChain.test.ts │ │ ├── parseHVar.test.ts │ │ ├── parser.test.ts │ │ └── tokens.test.ts │ └── xmlui │ ├── lint.test.ts │ ├── parser.test.ts │ ├── scanner.test.ts │ ├── transform.attr.test.ts │ ├── transform.circular.test.ts │ ├── transform.element.test.ts │ ├── transform.errors.test.ts │ ├── transform.escape.test.ts │ ├── transform.regression.test.ts │ ├── transform.script.test.ts │ ├── transform.test.ts │ └── xmlui.ts ├── tests-e2e │ ├── api-bound-component-regression.spec.ts │ ├── api-call-as-extracted-component.spec.ts │ ├── assign-to-object-or-array-regression.spec.ts │ ├── binding-regression.spec.ts │ ├── children-as-template-context-vars.spec.ts │ ├── compound-component.spec.ts │ ├── context-vars-regression.spec.ts │ ├── data-bindings.spec.ts │ ├── datasource-and-api-usage-in-var.spec.ts │ ├── datasource-direct-binding.spec.ts │ ├── datasource-onLoaded-regression.spec.ts │ ├── modify-array-item-regression.spec.ts │ ├── namespaces.spec.ts │ ├── push-to-array-regression.spec.ts │ ├── screen-breakpoints.spec.ts │ ├── scripting.spec.ts │ ├── state-scope-in-pages.spec.ts │ └── state-var-scopes.spec.ts ├── tsconfig.bin.json ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── vitest.config.ts ``` # Files -------------------------------------------------------------------------------- /xmlui/dev-docs/react-fundamentals.md: -------------------------------------------------------------------------------- ```markdown # React Fundamentals This document is a concise reference for React patterns and hooks used in XMLUI. It assumes you're familiar with [React basics](https://react.dev/learn) and provides practical guidance for reading and maintaining XMLUI source code. ## React Hooks: Quick Overview **What are hooks?** Functions that let you "hook into" React features from function components. They always start with `use` (e.g., `useState`, `useEffect`). **Core hooks in XMLUI:** - **`useState`** - Add local state to components - **`useEffect`** - Run side effects (data fetching, subscriptions, DOM updates) - **`useRef`** - Store mutable values that don't trigger re-renders - **`useMemo`** - Cache expensive calculations - **`useCallback`** - Cache function definitions - **`useReducer`** - Manage complex state with reducer pattern - **`useContext`** - Access context values from providers **Two critical rules:** 1. Only call hooks at the top level (not in loops, conditions, or nested functions) 2. Only call hooks from React functions (components or custom hooks) **Why?** React tracks hooks by call order. Breaking these rules causes state mismatches and bugs. ## Component Rendering Lifecycle React components re-render whenever their state or props change. Understanding this cycle prevents performance issues and unexpected behavior. **The 5-Phase Render Cycle:** 1. **Trigger** → Something requests a render: - Parent component re-renders - Props change - State changes (`useState`, `useReducer`) 2. **Render** → React calls your component function, which returns JSX 3. **Reconciliation** → React's diffing algorithm determines what changed in the virtual DOM 4. **Commit** → React updates the actual DOM with minimal changes 5. **Effects** → React runs effects in order: - `useLayoutEffect` (synchronous, blocks paint) - Browser paints the screen - `useEffect` (asynchronous, after paint) **Key insight:** Re-rendering is cheap (just a function call), but DOM updates are expensive. React optimizes by batching and minimizing DOM changes. **Common performance pitfalls:** ```tsx // ❌ WRONG - Parent re-renders cause all children to re-render function Parent() { const [count, setCount] = useState(0); return ( <div> <ExpensiveChild data={data} /> {/* Re-renders unnecessarily */} <button onClick={() => setCount(c => c + 1)}>Update</button> </div> ); } // ✅ CORRECT - Memoize to prevent unnecessary re-renders const MemoizedChild = React.memo(ExpensiveChild); function Parent() { const [count, setCount] = useState(0); const data = useMemo(() => computeData(), []); // Stable reference return ( <div> <MemoizedChild data={data} /> {/* Only re-renders when data changes */} <button onClick={() => setCount(c => c + 1)}>Update</button> </div> ); } ``` ## Rules of Hooks These rules are enforced by React and ESLint. Violating them causes hard-to-debug errors. **Rule 1: Call hooks at the top level** ```tsx // ❌ WRONG - Conditional hook function Bad({ show }: Props) { if (show) { const [value, setValue] = useState(""); // Hook order changes! } return <div>...</div>; } // ✅ CORRECT - Hook always called function Good({ show }: Props) { const [value, setValue] = useState(""); if (!show) return null; return <div>{value}</div>; } ``` **Rule 2: Only call from React functions** ```tsx // ❌ WRONG - Hook in regular function function getUser() { const [user, setUser] = useState(null); // Not allowed! return user; } // ✅ CORRECT - Hook in component or custom hook function useUser() { const [user, setUser] = useState(null); return user; } function Component() { const user = useUser(); // OK return <div>{user?.name}</div>; } ``` **Why these rules exist:** React stores hook state in a sequential array tied to each component instance. The array index depends on call order. Conditional hooks break this indexing, causing state to be assigned to the wrong hooks. --- ## React State Management Patterns Fundamental patterns for managing state in React, from local component state to shared state across component trees. ### `useState` - Local State **Syntax:** `const [state, setState] = useState(initialValue)` ```tsx // Basic const [count, setCount] = useState(0); // Functional update (when new state depends on old) setCount(prev => prev + 1); // Lazy initialization (expensive initial state) const [data, setData] = useState(() => expensiveComputation()); // Immutable updates setUser(prev => ({ ...prev, name })); setItems(prev => [...prev, newItem]); ``` **Use when:** State is local, updates are simple, no complex transitions. **Consider alternatives:** Multiple components → Context, complex logic → useReducer. --- ### `useReducer` - Complex State Logic **Syntax:** `const [state, dispatch] = useReducer(reducer, initialState)` ```tsx type Action = { type: 'increment' } | { type: 'decrement' } | { type: 'reset' }; function reducer(state: State, action: Action): State { switch (action.type) { case 'increment': return { count: state.count + 1 }; case 'decrement': return { count: state.count - 1 }; case 'reset': return { count: 0 }; default: return state; } } const [state, dispatch] = useReducer(reducer, { count: 0 }); dispatch({ type: 'increment' }); ``` **With Immer (XMLUI pattern):** ```tsx import produce from 'immer'; const reducer = produce((draft, action) => { switch (action.type) { case 'ADD_TODO': draft.todos.push(action.payload); // Direct mutation break; } }); ``` **Use when:** Multiple related state values, complex transitions, want to separate state logic. **Use useState when:** Simple independent values, straightforward updates. --- ### Context API - Avoid Prop Drilling **Purpose:** Share state across component tree without passing props through every level. **Pattern:** Create context → Provider component → Custom hook → Consume ```tsx // 1. Create context const AuthContext = createContext<AuthContext | null>(null); // 2. Custom hook with validation function useAuth() { const context = useContext(AuthContext); if (!context) throw new Error('useAuth must be used within AuthProvider'); return context; } // 3. Provider component function AuthProvider({ children }: Props) { const [user, setUser] = useState<User | null>(null); const value = useMemo(() => ({ user, login: async (creds: Credentials) => { /* ... */ }, logout: () => setUser(null), }), [user]); return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>; } // 4. Consume anywhere in tree function Component() { const { user, logout } = useAuth(); // No prop drilling! return <button onClick={logout}>{user.name}</button>; } ``` **XMLUI Example:** ```tsx // AppLayoutContext - Provides layout state to nested navigation const AppLayoutContext = createContext<IAppLayoutContext | null>(null); export const App = forwardRef(function App(props, ref) { const layoutContextValue = useMemo(() => ({ layout, navPanelVisible, toggleDrawer, }), [layout, navPanelVisible, toggleDrawer]); return ( <AppLayoutContext.Provider value={layoutContextValue}> {content} </AppLayoutContext.Provider> ); }); // NavLink accesses layout directly export const NavLink = forwardRef(function NavLink(props, ref) { const { layout } = useAppLayoutContext(); // No prop drilling! }); ``` **Use when:** Many nested components need access, >3 levels of prop drilling, global state (theme, auth). **Don't use when:** 1-2 levels of nesting (props fine), high-frequency updates (re-renders all consumers). **Performance tip:** Split contexts by update frequency - separate user/theme/settings contexts instead of one combined context. --- ### State Lifting **Pattern:** Move state to common ancestor to share between siblings. ```tsx // ❌ WRONG - Siblings can't communicate <Parent><InputA /><InputB /></Parent> // ✅ CORRECT - Lift state to parent function Parent() { const [value, setValue] = useState(''); return ( <> <InputA value={value} onChange={setValue} /> <InputB value={value} /> </> ); } ``` **XMLUI Pattern:** Container-based state flows down automatically: ```tsx <Stack var.selectedId="{null}"> <Button onClick={"{() => selectedId = 'item1'}" /> <Display value="{selectedId}" /> </Stack> ``` **Use when:** Siblings need to coordinate, parent orchestrates behavior. **Don't use when:** State only used by one component, excessive prop drilling (use context). --- ### Controlled vs Uncontrolled Components **Controlled:** Parent manages state via `value` prop. ```tsx function Controlled({ value, onChange }: Props) { return <input value={value} onChange={e => onChange(e.target.value)} />; } ``` **Uncontrolled:** Component manages own state via `initialValue`. ```tsx function Uncontrolled({ initialValue = '', onDidChange }: Props) { const [value, setValue] = useState(initialValue); return <input value={value} onChange={e => { setValue(e.target.value); onDidChange?.(e.target.value); }} />; } ``` **Hybrid (XMLUI pattern):** Support both modes. ```tsx function Flexible({ value, initialValue = '', onDidChange }: Props) { const [localValue, setLocalValue] = useState(initialValue); useEffect(() => { if (value !== undefined) setLocalValue(value); }, [value]); const handleChange = (e) => { setLocalValue(e.target.value); onDidChange?.(e.target.value); }; return <input value={localValue} onChange={handleChange} />; } ``` **Use controlled:** Validate/format input, value affects other UI, programmatic changes. **Use uncontrolled:** Simple forms (read on submit), performance critical. **Use hybrid:** Reusable component libraries. --- ### Compound Components **Purpose:** Components work together as cohesive unit, sharing state via context. ```tsx const TabsContext = createContext<{ activeTab: string; setActiveTab: (id: string) => void; } | null>(null); function Tabs({ children }: Props) { const [activeTab, setActiveTab] = useState('tab1'); return ( <TabsContext.Provider value={{ activeTab, setActiveTab }}> <div className="tabs">{children}</div> </TabsContext.Provider> ); } function Tab({ id, children }: Props) { const { activeTab, setActiveTab } = useContext(TabsContext)!; return ( <button className={activeTab === id ? 'active' : ''} onClick={() => setActiveTab(id)} > {children} </button> ); } Tabs.Tab = Tab; Tabs.Panel = TabPanel; // Usage - Flexible composition <Tabs> <Tabs.Tab id="tab1">First</Tabs.Tab> <Tabs.Tab id="tab2">Second</Tabs.Tab> <Tabs.Panel id="tab1">Content 1</Tabs.Panel> <Tabs.Panel id="tab2">Content 2</Tabs.Panel> </Tabs> ``` **Use when:** Tightly coupled components (tabs, accordion), need flexible composition, building libraries. **Don't use when:** Simple parent-child, no shared state, prop drilling is simple. --- ### Advanced Patterns **Provider Composition:** ```tsx function AppProviders({ children }: Props) { return ( <ThemeProvider> <AuthProvider> <RouterProvider> {children} </RouterProvider> </AuthProvider> </ThemeProvider> ); } ``` **Async Initialization:** ```tsx function AuthProvider({ children }: Props) { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { checkAuth().then(user => { setUser(user); setLoading(false); }); }, []); if (loading) return <LoadingScreen />; return <AuthContext.Provider value={{ user }}>{children}</AuthContext.Provider>; } ``` **Reducer with Immer (XMLUI):** ```tsx // StateContainer reducer export function createContainerReducer(debugView: IDebugViewContext) { return produce((state: ContainerState, action: ContainerAction) => { switch (action.type) { case ContainerActionKind.COMPONENT_STATE_CHANGED: state[uid] = { ...state[uid], ...action.payload.state }; break; } }); } ``` --- ## React Performance Optimization Patterns This section covers React's performance optimization tools and patterns. **Always profile before optimizing**—premature optimization adds complexity without real benefits. ### Core Optimization Hooks #### `useMemo` - Computation Caching Cache expensive calculations between renders. ```tsx const filtered = useMemo(() => items.filter(item => item.includes(filter)), [items, filter] ); ``` **Use when:** Computation is expensive (>10ms), creating objects/arrays for memoized children, or calculations in dependency arrays. **Don't use when:** Computation is cheap (<1ms), result used only once, or component rarely re-renders. #### `useCallback` - Function Caching Cache function definitions to prevent child re-renders. ```tsx const handleClick = useCallback(() => { doSomething(value); }, [value]); ``` **Use when:** Passing callbacks to memoized children or to dependency arrays. **Don't use when:** Function isn't passed to memoized components or deps arrays. **Note:** `useCallback(fn, deps)` is equivalent to `useMemo(() => fn, deps)`. #### `useTransition` - Non-Urgent Updates Mark state updates as low-priority to keep UI responsive. ```tsx const [isPending, startTransition] = useTransition(); startTransition(() => { setExpensiveState(newValue); // Won't block UI }); ``` **Use when:** Updating expensive state that doesn't need immediate feedback (filtering large lists, complex calculations). #### `memo` - Component Memoization Prevent re-renders when props haven't changed. ```tsx const MemoChild = memo(function MemoChild({ data }: Props) { return <div>{data.value}</div>; }); ``` **Use when:** Component renders frequently with same props, has expensive rendering, or is in large lists. **Don't use when:** Component rarely re-renders, props change every render, or rendering is cheap. **Important:** `memo` only works if props are stable. Use `useMemo`/`useCallback` for object/function props. --- ### Memoization Strategy Pattern **Principle:** `memo` + `useMemo` + `useCallback` work together. `memo` prevents re-renders, but only if props stay stable. Use `useMemo`/`useCallback` to keep props stable. #### The Memoization Cascade ```tsx // ❌ ANTI-PATTERN - memo without stable props const Child = memo(({ data, onClick }: Props) => <div onClick={onClick}>{data.value}</div>); function Parent() { return <Child data={{ value: 123 }} onClick={() => {}} />; // New refs every render! } // ✅ CORRECT - memo with stable props function Parent() { const data = useMemo(() => ({ value: 123 }), []); const onClick = useCallback(() => console.log('clicked'), []); return <Child data={data} onClick={onClick} />; } ``` #### Decision Tree 1. **Performance problem?** No → Don't optimize. Yes → Step 2. 2. **What's the cause?** - Parent re-renders often → Use `memo()` on child - Expensive computation → Use `useMemo()` on calculation - New function props → Use `useCallback()` on function 3. **Passing objects/arrays/functions to memoized component?** Yes → Memoize those too. #### Common Patterns ```tsx // Pattern 1: Context values const contextValue = useMemo(() => ({ state, setState, isLoading: state.status === 'loading', }), [state]); // Pattern 2: Event handlers with deps const handleSearch = useCallback(() => { if (query.length >= minLength) onSearch(query); }, [query, minLength, onSearch]); // Pattern 3: Expensive selectors const filteredData = useMemo(() => { return data.filter(item => item.name.includes(filter)).sort((a, b) => a.name.localeCompare(b.name)); }, [data, filter]); // Pattern 4: Derived state const summary = useMemo(() => ({ total: items.reduce((sum, item) => sum + item.price * item.quantity, 0), itemCount: items.reduce((sum, item) => sum + item.quantity, 0), }), [items]); ``` #### Anti-Patterns ```tsx // ❌ Over-memoization const greeting = useMemo(() => `Hello, ${name}`, [name]); // Too simple! // ❌ Incomplete chain <MemoChild config={{ theme: 'dark' }} />; // New object defeats memo // ❌ Unstable dependencies useMemo(() => formatUser(user), [user]); // user object recreated every render // ✅ Fix: useMemo(() => formatUser(user), [user.id, user.name]); ``` #### Checklist **✅ DO memoize:** - Components rendering frequently with same props - Expensive computations (>10ms) - Objects/arrays/functions passed to memoized children - Context values **❌ DON'T memoize:** - Cheap operations (<1ms) - Values that change every render - Without profiling first --- ### Virtualization Pattern **Purpose:** Render only visible items in large lists by using "windowing." Instead of rendering 10,000 items, render only ~10 visible items. **Libraries:** XMLUI uses two based on component needs: - **virtua** (Tree, List) - Chat interfaces, reverse scrolling, auto-sizing, fixed-size lists - **@tanstack/react-virtual** (Table) - Dynamic measurements, flexible **Library Comparison:** | Feature | virtua | @tanstack/react-virtual | |---------|--------|------------------------| | **Bundle Size** | ~6KB | ~4KB | | **API** | Render props | Hooks | | **Dynamic heights** | Automatic | Automatic | | **Reverse scroll** | ✅ Built-in | Manual | | **Auto-sizing** | ✅ Built-in | Manual | | **XMLUI Usage** | Tree, List | Table | **virtua Example (XMLUI List):** ```tsx import { Virtualizer } from 'virtua'; function ChatList({ messages }: Props) { return ( <Virtualizer count={messages.length}> {(index) => { const msg = messages[index]; return ( <div key={msg.id}> <div>{msg.author}</div> <div>{msg.content}</div> </div> ); }} </Virtualizer> ); } ``` **@tanstack/react-virtual Example:** ```tsx import { useVirtualizer } from '@tanstack/react-virtual'; function DataTable({ rows }: Props) { const tableRef = useRef<HTMLDivElement>(null); const rowVirtualizer = useVirtualizer({ count: rows.length, getScrollElement: () => tableRef.current, estimateSize: () => 30, overscan: 5, }); return ( <div ref={tableRef} style={{ height: '400px', overflow: 'auto' }}> {rowVirtualizer.getVirtualItems().map((virtualRow) => ( <div key={virtualRow.index} ref={(el) => rowVirtualizer.measureElement(el)} style={{ transform: `translateY(${virtualRow.start}px)` }} > {rows[virtualRow.index].content} </div> ))} </div> ); } ``` **Critical Rules:** 1. **Memoize row components** - Use `React.memo()` 2. **Apply transform/style** - Required for positioning (@tanstack) 3. **Memoize data** - Prevent row re-renders 4. **Handle scroll container** - Each library handles sizing differently **Performance Impact:** | Items | Normal | Virtualized | Improvement | |-------|--------|-------------|-------------| | 100 | 50ms | 10ms | 5x faster | | 1,000 | 500ms | 10ms | 50x faster | | 10,000 | 5s | 10ms | 500x faster | **When to Use:** - ✅ >100 items - ✅ Uniform item sizes - ✅ Scrollable datasets - ❌ <100 items (overhead not worth it) - ❌ Already paginated - ❌ Complex/unpredictable heights --- ### Rate Limiting: Debouncing and Throttling **Purpose:** Control the frequency of expensive operations during high-frequency events (user input, scrolling, resizing). **Key Difference:** - **Debouncing**: Wait until activity **stops** (search, autosave) - **Throttling**: Execute at **regular intervals** during activity (scroll, mousemove) ```tsx // User types "search" continuously // DEBOUNCING: Executes ONCE after user stops typing // Timeline: [type...type...type...STOP] → Execute // THROTTLING: Executes EVERY 200ms while typing // Timeline: Execute → [200ms] → Execute → [200ms] → Execute... ``` #### Debouncing Solutions **1. useDeferredValue (React 18+) - Recommended** ```tsx function Search() { const [query, setQuery] = useState(''); const deferredQuery = useDeferredValue(query); const results = useMemo(() => { if (deferredQuery.length < 2) return []; return performSearch(deferredQuery); }, [deferredQuery]); return ( <> <input value={query} onChange={e => setQuery(e.target.value)} /> <ResultsList results={results} /> </> ); } ``` **2. Custom useDebounce Hook** ```tsx function useDebounce<T>(value: T, delay: number = 500): T { const [debouncedValue, setDebouncedValue] = useState<T>(value); useEffect(() => { const timer = setTimeout(() => setDebouncedValue(value), delay); return () => clearTimeout(timer); }, [value, delay]); return debouncedValue; } // Usage const debouncedQuery = useDebounce(query, 300); ``` **3. Lodash debounce** ```tsx const debouncedSave = useMemo( () => debounce((text: string) => saveToServer(text), 1000), [] ); useEffect(() => { return () => debouncedSave.cancel(); }, [debouncedSave]); ``` #### Throttling Solutions **1. Custom useThrottle Hook** ```tsx function useThrottle<T extends (...args: any[]) => any>( callback: T, delay: number = 200 ): T { const lastRun = useRef(Date.now()); return useCallback((...args: Parameters<T>) => { const now = Date.now(); if (now - lastRun.current >= delay) { lastRun.current = now; return callback(...args); } }, [callback, delay]) as T; } // Usage const handleScroll = useThrottle(() => { setScrollPos(window.scrollY); }, 200); ``` **2. Lodash throttle** ```tsx const throttledScroll = useMemo( () => throttle(() => { updateScrollPosition(); }, 200, { leading: true, // Execute on first call trailing: true // Execute after interval ends }), [] ); useEffect(() => { return () => throttledScroll.cancel(); }, [throttledScroll]); ``` #### XMLUI Examples **Debounced Search (Search.tsx):** ```tsx function Search({ data, limit }: Props) { const [inputValue, setInputValue] = useState(""); const debouncedValue = useDeferredValue(inputValue); const results = useMemo(() => { if (debouncedValue.length <= 1) return []; return fuse.search(debouncedValue, { limit }); }, [debouncedValue, limit]); return ( <> <input value={inputValue} onChange={e => setInputValue(e.target.value)} /> <SearchResults results={results} /> </> ); } ``` **Throttled Change Listener (ChangeListenerNative.tsx):** ```tsx function ChangeListener({ listenTo, onChange, throttleWaitInMs = 0 }: Props) { const throttledOnChange = useMemo(() => { if (throttleWaitInMs !== 0 && onChange) { return throttle(onChange, throttleWaitInMs, { leading: true }); } return onChange; }, [onChange, throttleWaitInMs]); useEffect(() => { if (throttledOnChange) { throttledOnChange({ prevValue, newValue: listenTo }); } }, [listenTo, throttledOnChange]); } ``` **Async Throttle for Validation (misc.ts):** ```tsx function asyncThrottle<F extends (...args: any[]) => Promise<any>>( func: F, wait?: number, options?: ThrottleSettings ) { const throttled = throttle( (resolve, reject, args: Parameters<F>) => { void func(...args).then(resolve).catch(reject); }, wait, options ); return (...args: Parameters<F>): ReturnType<F> => new Promise((resolve, reject) => { throttled(resolve, reject, args); }) as ReturnType<F>; } ``` #### Decision Guide | Scenario | Solution | Timing | |----------|----------|--------| | Search input | Debounce | 300ms | | Form validation | Debounce | 500ms | | Autosave | Debounce | 1000ms | | Scroll position | Throttle | 100-200ms | | Window resize | Throttle | 200-300ms | | Mouse tracking | Throttle | 50-100ms | | API rate limiting | Throttle | 500-1000ms | #### Performance Impact | Operation | Without | With (300ms) | Improvement | |-----------|---------|--------------|-------------| | Search (6 chars typed) | 6 API calls | 1 API call | 83% reduction | | Scroll (1s) | ~60 events | 5 events | 92% reduction | | Window resize | ~30 events | 5 events | 83% reduction | #### Critical Rules **1. Always memoize** rate-limited functions: ```tsx // ❌ WRONG - Creates new function every render const handle = debounce(() => search(), 500); // ✅ CORRECT - Memoized const handle = useMemo(() => debounce(() => search(), 500), []); ``` **2. Always cleanup**: ```tsx useEffect(() => { return () => debouncedFn.cancel(); }, [debouncedFn]); ``` **3. Don't rate-limit UI state** - only side effects: ```tsx // ❌ WRONG - UI lags const handleChange = debounce((e) => setValue(e.target.value), 300); // ✅ CORRECT - Immediate UI, debounced side effect const handleChange = (e) => { setValue(e.target.value); // Instant debouncedSearch(e.target.value); // Delayed }; ``` **4. Choose appropriate delays**: - Search: 300ms - Autosave: 1000ms - Scroll/resize: 100-200ms - Mousemove: 50ms #### When to Use **Debouncing:** - ✅ Search, autosave, validation - ✅ Wait for user to finish action - ❌ Don't use for immediate feedback **Throttling:** - ✅ Scroll, resize, mousemove - ✅ Execute during continuous activity - ❌ Don't use when only final value matters #### Resources - [useDeferredValue](https://react.dev/reference/react/useDeferredValue) - React 18 docs - [lodash.debounce](https://lodash.com/docs/#debounce) - Debounce docs - [lodash.throttle](https://lodash.com/docs/#throttle) - Throttle docs - [XMLUI Search](packages/xmlui-search/src/Search.tsx) - Production example --- ## React Event Handling Patterns Patterns for handling user interactions efficiently and correctly in React applications. ### Event Delegation Pattern **Purpose:** Handle events for multiple children at parent level instead of attaching handlers to each child. **Benefits:** - Fewer event listeners (better memory usage) - Works with dynamically added/removed children - Simplifies event handler management ```tsx // ❌ ANTI-PATTERN - Handler on every item function List({ items }: Props) { return ( <ul> {items.map(item => ( <li key={item.id} onClick={() => handleClick(item.id)}> {item.name} </li> ))} </ul> ); } // ✅ CORRECT - Single handler on parent function List({ items }: Props) { const handleClick = (e: React.MouseEvent<HTMLUListElement>) => { const target = e.target as HTMLElement; const li = target.closest('li'); if (li) { const itemId = li.dataset.id; console.log('Clicked item:', itemId); } }; return ( <ul onClick={handleClick}> {items.map(item => ( <li key={item.id} data-id={item.id}> {item.name} </li> ))} </ul> ); } ``` **XMLUI Example (Tree component):** ```tsx function Tree({ items }: Props) { // Single click handler for entire tree const handleTreeClick = useCallback((e: React.MouseEvent) => { const target = e.target as HTMLElement; const treeItem = target.closest('[data-tree-item]'); if (treeItem) { const itemId = treeItem.getAttribute('data-item-id'); const action = target.getAttribute('data-action'); if (action === 'expand') { toggleExpand(itemId); } else if (action === 'select') { selectItem(itemId); } } }, [toggleExpand, selectItem]); return ( <div className="tree" onClick={handleTreeClick}> {renderTreeItems(items)} </div> ); } ``` **When to use:** - Lists with many items (>50) - Dynamic children (added/removed frequently) - Multiple event types on same children - Performance-critical rendering **When NOT to use:** - Few items (<10) - overhead not worth it - Need precise event target info - Event handler logic is complex per-item --- ### Synthetic Event Pattern **Purpose:** React wraps native browser events in `SyntheticEvent` for cross-browser consistency. **Key differences from native events:** | Feature | Native Event | Synthetic Event | |---------|-------------|-----------------| | **Type** | Browser-specific | Unified React type | | **Pooling (React 16)** | No | Yes (reused) | | **Pooling (React 17+)** | No | No (deprecated) | | **Properties** | Browser-specific | Normalized | | **Access after handler** | ✅ Always available | ⚠️ Nullified (React 16 only) | **Basic usage:** ```tsx function Input() { const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { // e is SyntheticEvent, not native Event console.log(e.target.value); // ✅ Works console.log(e.currentTarget); // ✅ Works // Access native event if needed const nativeEvent = e.nativeEvent; }; return <input onChange={handleChange} />; } ``` **Event pooling (React 16 only):** ```tsx // ❌ WRONG - Async access (React 16) function Bad() { const handleClick = (e: React.MouseEvent) => { setTimeout(() => { console.log(e.target); // null in React 16! }, 1000); }; return <button onClick={handleClick}>Click</button>; } // ✅ CORRECT - Persist event (React 16) function Good() { const handleClick = (e: React.MouseEvent) => { e.persist(); // Keep event alive setTimeout(() => { console.log(e.target); // ✅ Works }, 1000); }; return <button onClick={handleClick}>Click</button>; } // ✅ BETTER - Extract values (React 16 & 17+) function Better() { const handleClick = (e: React.MouseEvent) => { const target = e.target; // Capture immediately setTimeout(() => { console.log(target); // ✅ Works in all versions }, 1000); }; return <button onClick={handleClick}>Click</button>; } ``` **Note:** React 17+ removed event pooling, so `e.persist()` is no longer needed. **Common event types:** ```tsx // Mouse events const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {}; const handleDoubleClick = (e: React.MouseEvent) => {}; // Keyboard events const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => { if (e.key === 'Enter') submitForm(); }; // Form events const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {}; const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { e.preventDefault(); }; // Focus events const handleFocus = (e: React.FocusEvent<HTMLInputElement>) => {}; const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {}; // Clipboard events const handleCopy = (e: React.ClipboardEvent) => {}; const handlePaste = (e: React.ClipboardEvent) => { const text = e.clipboardData.getData('text'); }; // Drag events const handleDragStart = (e: React.DragEvent) => {}; const handleDrop = (e: React.DragEvent) => { e.preventDefault(); const data = e.dataTransfer.getData('text'); }; ``` **Accessing native event:** ```tsx function Component() { const handleClick = (e: React.MouseEvent) => { // React synthetic event console.log(e.currentTarget); // Element handler is attached to console.log(e.target); // Element that triggered event // Native browser event const nativeEvent = e.nativeEvent; console.log(nativeEvent); // MouseEvent object }; return <button onClick={handleClick}>Click</button>; } ``` --- ### Event Callback Composition Pattern **Purpose:** Combine multiple event handlers into a single handler, useful for library components that accept user callbacks. **Pattern 1: Sequential execution** ```tsx function composeHandlers<E>(...handlers: Array<((e: E) => void) | undefined>) { return (event: E) => { handlers.forEach(handler => { if (handler) { handler(event); } }); }; } // Usage function Button({ onClick, onClickInternal }: Props) { const handleClick = composeHandlers(onClickInternal, onClick); return <button onClick={handleClick}>Click</button>; } ``` **Pattern 2: Conditional execution (stop on preventDefault)** ```tsx function composeEventHandlers<E extends React.SyntheticEvent>( internalHandler?: (e: E) => void, externalHandler?: (e: E) => void ) { return (event: E) => { internalHandler?.(event); // If internal handler called preventDefault, stop if (!event.defaultPrevented) { externalHandler?.(event); } }; } // Usage - XMLUI pattern function Select({ onChange, onDidChange }: Props) { const handleInternalChange = (e: React.ChangeEvent<HTMLSelectElement>) => { // Internal logic (validation, state updates) validateValue(e.target.value); // Prevent external handler if validation fails if (!isValid) { e.preventDefault(); } }; const handleChange = composeEventHandlers(handleInternalChange, onChange); return <select onChange={handleChange}>...</select>; } ``` **Pattern 3: Merge handlers from props** ```tsx function mergeEventHandlers<T extends React.SyntheticEvent>( ours: ((e: T) => void) | undefined, theirs: ((e: T) => void) | undefined ): ((e: T) => void) | undefined { if (!ours) return theirs; if (!theirs) return ours; return (event: T) => { ours(event); if (!event.defaultPrevented) { theirs(event); } }; } // Usage - Wrapper component function Wrapper({ children, onClick }: Props) { const internalClick = (e: React.MouseEvent) => { console.log('Wrapper clicked'); }; return cloneElement(children, { onClick: mergeEventHandlers(internalClick, children.props.onClick), }); } ``` **XMLUI Example (Container component):** ```tsx function Container({ children, ...props }: Props, ref: Ref<HTMLElement>) { const renderedChild = renderChild(children); if (ref && renderedChild && isValidElement(renderedChild)) { // Merge event handlers from both Container and child const mergedProps = { ...renderedChild.props, onClick: composeEventHandlers(props.onClick, renderedChild.props.onClick), onKeyDown: composeEventHandlers(props.onKeyDown, renderedChild.props.onKeyDown), ref: composeRefs(ref, (renderedChild as any).ref), }; return cloneElement(renderedChild, mergedProps); } return renderedChild; } ``` **Pattern 4: Callback with additional args** ```tsx function withArgs<E, T>( handler: ((e: E, ...args: T[]) => void) | undefined, ...args: T[] ) { if (!handler) return undefined; return (event: E) => { handler(event, ...args); }; } // Usage function List({ items, onItemClick }: Props) { return ( <ul> {items.map(item => ( <li key={item.id} onClick={withArgs(onItemClick, item.id)}> {item.name} </li> ))} </ul> ); } ``` **When to compose handlers:** - Building reusable component libraries - Wrapper components that add behavior - Components with internal + external handlers - Need to call parent handler conditionally **Best practices:** - Always check if handler exists before calling - Respect `preventDefault()` and `stopPropagation()` - Execute internal handlers first - Document composition order clearly --- ## React Lifecycle and Effect Patterns Patterns for managing side effects, synchronization, and component lifecycle in React applications. ### `useEffect` - Side Effects and Lifecycle **Purpose:** Run side effects after render (data fetching, subscriptions, DOM manipulation). **Syntax:** `useEffect(() => { /* effect */ return () => { /* cleanup */ } }, [dependencies])` **Basic usage:** ```tsx function UserProfile({ userId }: Props) { const [user, setUser] = useState(null); useEffect(() => { // Effect runs after render fetch(`/api/users/${userId}`) .then(res => res.json()) .then(data => setUser(data)); }, [userId]); // Re-run when userId changes return <div>{user?.name}</div>; } ``` **With async/await:** ```tsx useEffect(() => { // Can't make callback async directly, use IIFE (async () => { const res = await fetch(`/api/users/${userId}`); const data = await res.json(); setUser(data); })(); }, [userId]); ``` **Use when:** Data fetching, subscriptions, event listeners, DOM manipulation, integrating with non-React libraries. **Avoid when:** Computing derived values (use `useMemo`), handling events (use handlers), initializing state (use initializer). --- ### Effect Cleanup Pattern **Purpose:** Properly clean up subscriptions, timers, and event listeners to prevent memory leaks. **Pattern: Always return cleanup function for subscriptions** ```tsx function Chat({ roomId }: Props) { useEffect(() => { const connection = createConnection(roomId); connection.connect(); // ✅ Cleanup runs before next effect and on unmount return () => { connection.disconnect(); }; }, [roomId]); return <div>Connected to {roomId}</div>; } ``` **Pattern: Cancel async operations** ```tsx function DataComponent({ url }: Props) { const [data, setData] = useState(null); useEffect(() => { let cancelled = false; fetch(url) .then(res => res.json()) .then(data => { if (!cancelled) setData(data); }); // ✅ Prevent state updates after unmount return () => { cancelled = true; }; }, [url]); return <div>{JSON.stringify(data)}</div>; } ``` **Pattern: Remove event listeners** ```tsx function WindowSize() { const [size, setSize] = useState({ width: 0, height: 0 }); useEffect(() => { const handleResize = () => { setSize({ width: window.innerWidth, height: window.innerHeight }); }; window.addEventListener('resize', handleResize); handleResize(); // Initial size // ✅ Always remove listeners return () => window.removeEventListener('resize', handleResize); }, []); return <div>{size.width} x {size.height}</div>; } ``` **Pattern: Clear timers and intervals** ```tsx function Timer() { const [count, setCount] = useState(0); useEffect(() => { const interval = setInterval(() => { setCount(c => c + 1); }, 1000); // ✅ Clear interval on unmount return () => clearInterval(interval); }, []); return <div>{count}s</div>; } ``` **Pattern: Unsubscribe from external stores** ```tsx function ExternalStore({ store }: Props) { const [value, setValue] = useState(store.getValue()); useEffect(() => { const unsubscribe = store.subscribe(newValue => { setValue(newValue); }); // ✅ Unsubscribe when component unmounts return unsubscribe; }, [store]); return <div>{value}</div>; } ``` **Critical rules:** - Return cleanup for subscriptions, listeners, timers - Use cancellation flags for async operations - Cleanup runs before next effect and on unmount - Don't forget to remove event listeners --- ### Effect Dependencies Pattern **Purpose:** Correctly manage dependency arrays to avoid stale closures and unnecessary re-runs. **Anti-pattern: Missing dependencies (stale closure bug)** ```tsx // ❌ WRONG - Stale closure function Counter() { const [count, setCount] = useState(0); useEffect(() => { setInterval(() => { console.log(count); // Always logs 0! }, 1000); }, []); // Missing count return <button onClick={() => setCount(count + 1)}>Increment</button>; } // ✅ CORRECT - Use functional update useEffect(() => { setInterval(() => { setCount(c => c + 1); // Uses latest value }, 1000); }, []); // No dependencies needed ``` **Anti-pattern: Object/array in dependencies** ```tsx // ❌ WRONG - Object recreated every render function Component({ config }: Props) { useEffect(() => { fetchData(config); }, [config]); // Runs every render if config is new object } // ✅ CORRECT - Destructure primitive values useEffect(() => { fetchData(config); }, [config.id, config.filter]); // Only re-run when these change ``` **Pattern: Empty array = run once on mount** ```tsx useEffect(() => { // Initialization logic initializeApp(); return () => { // Cleanup on unmount cleanupApp(); }; }, []); // Runs once on mount, cleanup on unmount ``` **Pattern: No array = run after every render** ```tsx useEffect(() => { // Runs after every render (rarely needed) updateDocumentTitle(`Page - ${count}`); }); // No dependency array ``` **Pattern: Avoid callback dependencies with useRef** ```tsx function Component({ callback }: Props) { const callbackRef = useRef(callback); // Keep ref updated useEffect(() => { callbackRef.current = callback; }, [callback]); useEffect(() => { const interval = setInterval(() => { // ✅ Always uses latest callback callbackRef.current(); }, 1000); return () => clearInterval(interval); }, []); // No callback in deps } ``` **Common mistakes:** ```tsx // ❌ Wrong - Missing dependencies useEffect(() => { doSomething(prop); // prop not in deps }, []); // ❌ Wrong - Object identity useEffect(() => { fetchData(user); }, [user]); // user object changes every render // ❌ Wrong - Function identity useEffect(() => { callback(); }, [callback]); // callback recreated every render // ✅ Correct - Destructure objects useEffect(() => { fetchData({ id: user.id, name: user.name }); }, [user.id, user.name]); // ✅ Correct - Memoize callbacks const memoizedCallback = useCallback(callback, [dep]); useEffect(() => { memoizedCallback(); }, [memoizedCallback]); ``` **ESLint rule:** Always enable `react-hooks/exhaustive-deps` to catch dependency issues. --- ### Layout Effect Pattern **Purpose:** Run effects synchronously after DOM mutations but before browser paint to prevent visual flickering. **When to use `useLayoutEffect`:** **Pattern: DOM measurements before paint** ```tsx function Tooltip() { const [position, setPosition] = useState({ x: 0, y: 0 }); const ref = useRef<HTMLDivElement>(null); // ✅ CORRECT - Measure before paint useLayoutEffect(() => { if (ref.current) { const rect = ref.current.getBoundingClientRect(); setPosition({ x: rect.left + rect.width / 2, y: rect.top - 10, }); } }, []); return <div ref={ref} style={{ left: position.x, top: position.y }}>Tooltip</div>; } // ❌ WRONG - useEffect causes visible flicker useEffect(() => { // DOM measurements happen AFTER paint // User sees element jump from old to new position }, []); ``` **Pattern: Synchronize scroll position** ```tsx function ScrollSync({ targetRef }: Props) { useLayoutEffect(() => { if (targetRef.current) { // ✅ Scroll before paint, no flicker targetRef.current.scrollTop = savedPosition; } }, [targetRef, savedPosition]); } ``` **Pattern: Prevent layout shift** ```tsx function AutoResizeTextarea({ value }: Props) { const ref = useRef<HTMLTextAreaElement>(null); useLayoutEffect(() => { if (ref.current) { // ✅ Adjust height before paint ref.current.style.height = 'auto'; ref.current.style.height = `${ref.current.scrollHeight}px`; } }, [value]); return <textarea ref={ref} value={value} />; } ``` **Pattern: Third-party DOM library integration** ```tsx function Chart({ data }: Props) { const containerRef = useRef<HTMLDivElement>(null); useLayoutEffect(() => { if (containerRef.current) { // ✅ Initialize library before paint const chart = new ChartLibrary(containerRef.current); chart.render(data); return () => chart.destroy(); } }, [data]); return <div ref={containerRef} />; } ``` **Comparison: useEffect vs useLayoutEffect** | Aspect | `useEffect` | `useLayoutEffect` | |--------|------------|-------------------| | **Timing** | After paint (async) | Before paint (sync) | | **Blocks rendering** | ❌ No | ✅ Yes | | **Use for** | Data fetching, subscriptions | DOM measurements, preventing flicker | | **Performance** | Better (non-blocking) | Worse (blocks paint) | | **SSR** | ✅ Works | ⚠️ Warning (no DOM on server) | **When to use each:** - **useEffect**: 99% of cases - data fetching, subscriptions, analytics - **useLayoutEffect**: DOM measurements, scroll sync, preventing visual flicker **Warning:** `useLayoutEffect` blocks visual updates. Only use when you need synchronous DOM access before paint. **SSR consideration:** ```tsx // ⚠️ useLayoutEffect doesn't run on server useLayoutEffect(() => { // This code only runs in browser measureDOM(); }, []); // ✅ Better: Use useEffect for SSR-compatible code useEffect(() => { measureDOM(); }, []); ``` --- ### Insertion Effect Pattern **Purpose:** Insert styles into DOM before layout effects run. Used by CSS-in-JS libraries. **Syntax:** `useInsertionEffect(() => { /* insert styles */ }, [dependencies])` **Effect execution order:** 1. `useInsertionEffect` - Insert styles 2. `useLayoutEffect` - Measure layout (reads styles) 3. Browser paints 4. `useEffect` - Other side effects **Pattern: CSS-in-JS style injection** ```tsx function useCSS(rule: string) { useInsertionEffect(() => { // ✅ Inject styles before layout reads const style = document.createElement('style'); style.textContent = rule; document.head.appendChild(style); return () => document.head.removeChild(style); }, [rule]); } // Usage function Button({ color }: Props) { useCSS(` .button-${color} { background: ${color}; border: 1px solid ${darken(color)}; } `); return <button className={`button-${color}`}>Click</button>; } ``` **Pattern: Dynamic theme injection (XMLUI)** ```tsx function ThemeProvider({ theme, children }: Props) { useInsertionEffect(() => { // ✅ Insert theme CSS before components measure const styleElement = document.createElement('style'); styleElement.id = 'theme-styles'; styleElement.textContent = generateThemeCSS(theme); document.head.appendChild(styleElement); return () => { document.getElementById('theme-styles')?.remove(); }; }, [theme]); return children; } ``` **Pattern: Critical CSS injection** ```tsx function useCriticalCSS(css: string) { useInsertionEffect(() => { // ✅ Inject before any layout calculations const style = document.createElement('style'); style.setAttribute('data-critical', 'true'); style.textContent = css; document.head.insertBefore(style, document.head.firstChild); return () => style.remove(); }, [css]); } ``` **When to use:** - Building CSS-in-JS libraries - Dynamic style generation - Theme system implementation - Critical CSS injection **When NOT to use:** - Regular application code (use `useEffect`) - Static stylesheets (use `<link>` tags) - Non-style DOM manipulation **Note:** Rarely used directly in application code. Primarily for library authors. XMLUI uses this in `StyleContext` for theme style injection. **Comparison with other effects:** ```tsx // ❌ WRONG - useEffect runs too late useEffect(() => { injectStyles(); // Styles added after layout measured }, []); // ❌ WRONG - useLayoutEffect causes double layout useLayoutEffect(() => { injectStyles(); // Layout measured, then styles added, then re-measured }, []); // ✅ CORRECT - useInsertionEffect runs first useInsertionEffect(() => { injectStyles(); // Styles ready before any layout measurement }, []); ``` --- ### Effect Best Practices Summary **1. Always clean up:** ```tsx useEffect(() => { const subscription = subscribe(); return () => subscription.unsubscribe(); // ✅ Cleanup }, []); ``` **2. Handle dependencies correctly:** ```tsx // ✅ Include all dependencies useEffect(() => { doSomething(prop, state); }, [prop, state]); // ✅ Or use functional updates useEffect(() => { setState(prev => prev + 1); }, []); // No state dependency needed ``` **3. Choose the right effect hook:** - `useEffect` - Default choice (async, after paint) - `useLayoutEffect` - DOM measurements, prevent flicker (sync, before paint) - `useInsertionEffect` - CSS-in-JS only (before layout) **4. Avoid common pitfalls:** ```tsx // ❌ Don't use objects in deps useEffect(() => {}, [config]); // Runs every render // ✅ Destructure primitive values useEffect(() => {}, [config.id, config.name]); // ❌ Don't make effect callback async useEffect(async () => {}, []); // Type error // ✅ Use IIFE for async useEffect(() => { (async () => await fetch())(); }, []); ``` **5. Profile before optimizing:** - Most effects are cheap - Don't prematurely optimize with `useLayoutEffect` - Measure actual performance impact --- ## `useRef` - Persistent Mutable References **Purpose:** Store mutable values that persist across renders without triggering re-renders. **Syntax:** `const ref = useRef(initialValue)` ### DOM References ```tsx function TextInput() { const inputRef = useRef<HTMLInputElement>(null); const focusInput = () => { inputRef.current?.focus(); }; return ( <div> <input ref={inputRef} /> <button onClick={focusInput}>Focus</button> </div> ); } ``` ### Storing Mutable Values ```tsx function Timer() { const [count, setCount] = useState(0); const intervalRef = useRef<NodeJS.Timeout>(); useEffect(() => { intervalRef.current = setInterval(() => { setCount(c => c + 1); }, 1000); return () => clearInterval(intervalRef.current); }, []); const stop = () => { clearInterval(intervalRef.current); }; return ( <div> {count} seconds <button onClick={stop}>Stop</button> </div> ); } ``` ### Avoiding Stale Closures ```tsx function Component({ callback }: Props) { const callbackRef = useRef(callback); // Keep ref updated with latest callback useEffect(() => { callbackRef.current = callback; }, [callback]); useEffect(() => { const interval = setInterval(() => { // Always uses latest callback callbackRef.current(); }, 1000); return () => clearInterval(interval); }, []); // No callback dependency needed return <div>Running...</div>; } ``` ### Common Patterns in XMLUI **Previous Value Tracking:** ```tsx function usePrevious<T>(value: T): T | undefined { const ref = useRef<T>(); useEffect(() => { ref.current = value; }, [value]); return ref.current; } function Component({ count }: Props) { const prevCount = usePrevious(count); return <div>Now: {count}, Before: {prevCount}</div>; } ``` ### Key Differences: useState vs useRef | Feature | `useState` | `useRef` | |---------|-----------|---------| | Triggers re-render | ✅ Yes | ❌ No | | Persists across renders | ✅ Yes | ✅ Yes | | Use for UI state | ✅ Yes | ❌ No | | Use for DOM access | ❌ No | ✅ Yes | | Use for mutable timers/intervals | ❌ No | ✅ Yes | --- ## `useId` - Unique ID Generation **Purpose:** Generate stable unique IDs for accessibility attributes. **Syntax:** `const id = useId()` ### Basic Usage ```tsx function FormField({ label }: Props) { const id = useId(); return ( <div> <label htmlFor={id}>{label}</label> <input id={id} /> </div> ); } ``` ### Multiple IDs ```tsx function ComplexForm() { const id = useId(); return ( <div> <label htmlFor={`${id}-name`}>Name</label> <input id={`${id}-name`} aria-describedby={`${id}-name-hint`} /> <span id={`${id}-name-hint`}>Enter your full name</span> <label htmlFor={`${id}-email`}>Email</label> <input id={`${id}-email`} /> </div> ); } ``` **Why not just use a counter?** `useId` generates IDs that are stable across server and client rendering, preventing hydration mismatches. --- ## `forwardRef` - Ref Forwarding to Child Components **Purpose:** Allow parent components to access DOM nodes or component instances of child components by forwarding refs through component boundaries. **Syntax:** `const Component = forwardRef((props, ref) => { ... })` ### Basic Usage ```tsx const TextInput = forwardRef<HTMLInputElement, Props>( function TextInput({ label, ...props }, forwardedRef) { return ( <div> <label>{label}</label> <input ref={forwardedRef} {...props} /> </div> ); } ); // Parent can now access the input element function Form() { const inputRef = useRef<HTMLInputElement>(null); const focusInput = () => { inputRef.current?.focus(); }; return ( <div> <TextInput ref={inputRef} label="Name" /> <button onClick={focusInput}>Focus Input</button> </div> ); } ``` ### TypeScript Generic Syntax ```tsx // Explicitly type both the ref and props const Component = forwardRef<RefType, PropsType>( function Component(props, ref) { return <div ref={ref}>...</div>; } ); // Example with HTMLDivElement const Card = forwardRef<HTMLDivElement, CardProps>( function Card({ children, className }, ref) { return ( <div ref={ref} className={className}> {children} </div> ); } ); ``` **Why explicit typing matters:** Without generic syntax, TypeScript infers types from the function signature, which can lead to several issues: ```tsx // ❌ WRONG - Without explicit generics const Input = forwardRef(function Input(props: Props, ref) { // TypeScript infers ref as: ForwardedRef<unknown> // This means: // 1. No autocomplete for ref.current properties // 2. No type checking when assigning ref to JSX elements // 3. Parent components can pass wrong ref type without errors return <input ref={ref} />; // Type error: ref might not be compatible! }); // Usage - TypeScript won't catch this error: const divRef = useRef<HTMLDivElement>(null); <Input ref={divRef} /> // Should error but doesn't - expecting HTMLInputElement! // ✅ CORRECT - With explicit generics const Input = forwardRef<HTMLInputElement, Props>( function Input(props, ref) { // TypeScript knows ref is: ForwardedRef<HTMLInputElement> // Benefits: // 1. Autocomplete works: ref.current?.focus() // 2. Type checking ensures ref matches JSX element // 3. Parent must pass correct ref type return <input ref={ref} />; // Type safe! } ); // Usage - TypeScript catches the error: const divRef = useRef<HTMLDivElement>(null); <Input ref={divRef} /> // ❌ Type error: expected RefObject<HTMLInputElement> ``` **Key problems without explicit generics:** 1. **Loss of type safety** - Parent can pass incompatible ref types 2. **No IntelliSense** - No autocomplete for `ref.current` properties 3. **Runtime errors** - Type mismatches only discovered at runtime 4. **Harder refactoring** - Changes to ref type don't propagate to consumers **Best practice:** Always specify both generic parameters explicitly in XMLUI components. ### Composing Multiple Refs **The Problem:** Components often need to manage multiple refs pointing to the same DOM element: 1. **Internal ref** - Component's own logic (measurements, animations, focus) 2. **Forwarded ref** - Parent needs access to the DOM element 3. **Third-party refs** - Integration with libraries (Popper, Radix UI, etc.) **The Solution:** Use `composeRefs` from `@radix-ui/react-compose-refs` to merge multiple refs into one. **Why you need to compose refs:** | Use Case | Example | Reason | |----------|---------|--------| | **Internal logic + parent access** | Auto-resize textarea | Component measures scrollHeight, parent needs focus() | | **Library integration** | Popover/Tooltip | Popper needs ref for positioning, parent needs ref for control | | **Wrapper components** | Container with single child | Parent ref applies to child, child has own ref | | **Multiple behaviors** | Draggable element | Drag library needs ref, resize observer needs ref, parent needs ref | **Key differences: Inner vs Forwarded refs:** | Aspect | Inner Ref | Forwarded Ref | |--------|-----------|---------------| | **Created by** | Component itself with `useRef()` | Parent component | | **Purpose** | Internal component logic | Parent needs DOM access | | **Type** | Always `RefObject<T>` | Can be `RefObject<T>`, `RefCallback<T>`, or `null` | | **Guaranteed to exist** | Yes - always has `.current` property | No - parent might not pass a ref | | **When to use** | Component needs DOM access for its own behavior | Expose DOM element to parent | **Example 1: Internal + Forwarded (most common in XMLUI):** ```tsx import { composeRefs } from "@radix-ui/react-compose-refs"; function TextArea({ value, onChange }: Props, forwardedRef: Ref<HTMLTextAreaElement>) { // Inner ref: Component creates and owns this for auto-resize logic const innerRef = useRef<HTMLTextAreaElement>(null); // Compose both refs - textarea element needs both const composedRef = forwardedRef ? composeRefs(innerRef, forwardedRef) : innerRef; useEffect(() => { // ✅ CORRECT: Use innerRef for internal logic // It's guaranteed to exist and have .current property if (innerRef.current) { innerRef.current.style.height = 'auto'; innerRef.current.style.height = `${innerRef.current.scrollHeight}px`; } // ❌ WRONG: Don't use forwardedRef directly // if (forwardedRef?.current) { ... } // Type error: Ref<T> might be a callback! }, [value]); return <textarea ref={composedRef} value={value} onChange={onChange} />; } export const AutoResizeTextArea = forwardRef(TextArea); // Parent usage: function Form() { const textareaRef = useRef<HTMLTextAreaElement>(null); const focusTextarea = () => { textareaRef.current?.focus(); // Parent can access via forwarded ref }; return <AutoResizeTextArea ref={textareaRef} />; // Component auto-resizes via inner ref } ``` **Example 2: Library Integration (Popper + Forwarded):** ```tsx function Select({ options }: Props, forwardedRef: Ref<HTMLButtonElement>) { // Popper library needs a ref for positioning const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null); // Compose library ref setter with forwarded ref const composedRef = forwardedRef ? composeRefs(setReferenceElement, forwardedRef) : setReferenceElement; return ( <> <button ref={composedRef}>Select</button> <Popper referenceElement={referenceElement}> {/* Dropdown content */} </Popper> </> ); } export const SelectComponent = forwardRef(Select); ``` **Example 3: Wrapper Component (Parent + Child refs):** ```tsx function Container({ children }: Props, ref: Ref<HTMLElement>) { const renderedChild = renderChild(children); // If single child, compose parent's ref with child's existing ref if (isValidElement(renderedChild)) { return cloneElement(renderedChild, { ref: composeRefs(ref, (renderedChild as any).ref), }); } return renderedChild; } export const ContainerComponent = forwardRef(Container); ``` **Example 4: Multiple Behaviors (Drag + Resize + Forward):** ```tsx function DraggablePanel(props: Props, forwardedRef: Ref<HTMLDivElement>) { const dragRef = useRef<HTMLDivElement>(null); const resizeObserverRef = useRef<HTMLDivElement>(null); // Compose all three refs const composedRef = composeRefs( dragRef, resizeObserverRef, forwardedRef || null ); useDragLogic(dragRef); useResizeObserver(resizeObserverRef); return <div ref={composedRef}>Draggable and resizable</div>; } ``` **When to compose refs:** - Component needs internal ref AND parent needs access - Integrating with libraries that require refs (Popper, React DnD, etc.) - Wrapping components that need to forward refs to children - Multiple hooks/effects need refs to the same element **How `composeRefs` works:** - Accepts multiple refs (RefObjects, callbacks, or null) - Returns a single callback ref that updates all provided refs - Handles both RefObject (sets `.current`) and callback refs (calls function) - Safely ignores `null`/`undefined` refs ### When to Use forwardRef **Use `forwardRef` when:** - Building reusable components that wrap DOM elements - Parent needs direct DOM access (focus, scroll, measurements) - Integrating with third-party libraries requiring refs - Creating form components that need imperative control **Don't use `forwardRef` when:** - Component doesn't wrap a single DOM element - Refs aren't needed by parent components - You can solve the problem with callbacks/props instead ### Common Mistakes ```tsx // ❌ WRONG - Forgetting to attach ref to DOM element const Bad = forwardRef((props, ref) => { return <div>{props.children}</div>; // ref is ignored! }); // ✅ CORRECT - Always attach ref to actual DOM element const Good = forwardRef((props, ref) => { return <div ref={ref}>{props.children}</div>; }); // ❌ WRONG - Attaching ref to component (won't work) const AlsoBad = forwardRef((props, ref) => { return <CustomComponent ref={ref} />; // CustomComponent must also use forwardRef }); // ✅ CORRECT - Forward through nested components const CustomComponent = forwardRef((props, ref) => { return <div ref={ref}>...</div>; }); const AlsoGood = forwardRef((props, ref) => { return <CustomComponent ref={ref} />; // Works because CustomComponent forwards }); ``` --- ## `createPortal` - Render Outside Hierarchy **Purpose:** Render children into a DOM node outside the parent component's hierarchy. **Syntax:** `createPortal(children, domNode, key?)` ### Basic Usage ```tsx import { createPortal } from 'react-dom'; function Modal({ isOpen, children }: Props) { if (!isOpen) return null; // Render into document.body instead of parent component return createPortal( <div className="modal-overlay"> <div className="modal-content"> {children} </div> </div>, document.body ); } // Usage function App() { return ( <div className="app"> <Modal isOpen={true}> <h1>This renders in document.body, not .app!</h1> </Modal> </div> ); } ``` ### Common Use Cases **1. Tooltips/Popovers (avoid z-index issues):** ```tsx function Tooltip({ targetRef, content }: Props) { return createPortal( <div className="tooltip" style={calculatePosition(targetRef)}> {content} </div>, document.body ); } ``` **2. Notifications/Toasts:** ```tsx function NotificationToast() { const [shouldRender, setShouldRender] = useState(false); useEffect(() => { setShouldRender(true); }, []); if (!shouldRender) return null; return createPortal( <Toaster position="top-right"> {(t) => <ToastBar toast={t} />} </Toaster>, document.body ); } ``` **3. Modal Dialogs:** ```tsx function ModalDialog({ isOpen, children }: Props) { if (!isOpen) return null; return createPortal( <div className="modal-backdrop"> <div className="modal-dialog"> {children} </div> </div>, document.getElementById('modal-root') || document.body ); } ``` **4. Full-Screen Overlays:** ```tsx function FullScreenOverlay({ show, children }: Props) { if (!show) return null; return createPortal( <div className="fullscreen-overlay"> {children} </div>, document.body ); } ``` ### Event Bubbling Still Works ```tsx // Event bubbling works despite DOM hierarchy function Parent() { const handleClick = () => { console.log('Clicked!'); // This fires even though button is portaled }; return ( <div onClick={handleClick}> <PortaledButton /> </div> ); } function PortaledButton() { return createPortal( <button>Click me</button>, document.body ); } ``` ### Common Pattern in XMLUI ```tsx // App component portals theme styles function App({ children }: Props) { return ( <> {children} {createPortal( <style>{themeCSS}</style>, document.head )} </> ); } // Inspector portals debugging UI function Inspector() { return createPortal( <div className="inspector-panel"> {/* Debug tools */} </div>, document.body ); } ``` ### When to Use createPortal **Use `createPortal` when:** - Modals, dialogs, and overlays - Tooltips and popovers - Notifications and toasts - Avoiding parent overflow/z-index issues - Rendering into different parts of DOM (head, body) **Don't use when:** - Normal component rendering is sufficient - No CSS stacking or overflow issues - Adds unnecessary complexity --- ## `Fragment` - Grouping Without DOM Nodes **Purpose:** Group multiple elements without adding extra nodes to the DOM. **Syntax:** `<Fragment>...</Fragment>` or `<>...</>` ### Basic Usage ```tsx // ❌ WRONG - Adds unnecessary div wrapper function List() { return ( <div> <li>Item 1</li> <li>Item 2</li> </div> ); } // ✅ CORRECT - No extra DOM node function List() { return ( <> <li>Item 1</li> <li>Item 2</li> </> ); } ``` ### Short vs Long Syntax ```tsx // Short syntax <> - Use for most cases function Component() { return ( <> <Header /> <Content /> </> ); } // Long syntax <Fragment> - Required when you need a key function List({ items }: Props) { return ( <ul> {items.map(item => ( <Fragment key={item.id}> <li>{item.name}</li> <li>{item.description}</li> </Fragment> ))} </ul> ); } ``` **Limitations of short syntax:** - ❌ Cannot add `key` prop (use `<Fragment key={...}>` instead) - ❌ Cannot add any other props (only `key` is allowed on Fragment) - ✅ Use short syntax everywhere else (cleaner, less verbose) ### Common Use Cases **1. Returning Multiple Elements:** ```tsx function Header() { return ( <> <h1>Title</h1> <nav>Navigation</nav> </> ); } ``` **2. Conditional Rendering:** ```tsx function Component({ showExtra }: Props) { return ( <div> <h1>Always shown</h1> {showExtra && ( <> <p>Extra content</p> <button>Extra button</button> </> )} </div> ); } ``` **3. Table Rows:** ```tsx function TableRows({ data }: Props) { return ( <> {data.map(row => ( <Fragment key={row.id}> <tr> <td>{row.name}</td> <td>{row.value}</td> </tr> {row.hasDetails && ( <tr> <td colSpan={2}>{row.details}</td> </tr> )} </Fragment> ))} </> ); } ``` **4. Avoiding Invalid HTML:** ```tsx // ❌ WRONG - div inside p is invalid HTML function Text() { return ( <p> <div>This is invalid!</div> </p> ); } // ✅ CORRECT - Fragment doesn't create DOM node function Text() { return ( <p> <> <span>This is valid!</span> </> </p> ); } ``` ### When to Use Fragment **Use `Fragment` when:** - Component must return multiple elements - Avoiding wrapper divs that break CSS (flexbox, grid) - Keeping HTML semantically valid - Conditional rendering of multiple elements **Don't use when:** - Single element (no need to wrap) - Wrapper div doesn't cause issues - Need to attach events or refs (Fragment can't have them) --- ## `cloneElement` - Clone and Modify React Elements **Purpose:** Clone a React element and override its props, refs, or children. **Syntax:** `cloneElement(element, props?, ...children?)` ### Basic Usage ```tsx import { cloneElement, isValidElement } from 'react'; function Container({ children }: Props) { if (!isValidElement(children)) { return children; } // Clone child and add extra props return cloneElement(children, { className: 'container-child', style: { padding: '10px' }, }); } // Usage <Container> <div>Original</div> {/* Becomes <div className="container-child" style={{padding: '10px'}}>Original</div> */} </Container> ``` ### Adding Props to Children ```tsx function Animation({ children, duration = 300 }: Props) { if (!isValidElement(children)) { return children; } // Add animation props to child return cloneElement(children, { style: { ...children.props.style, transition: `all ${duration}ms`, }, }); } ``` ### Forwarding Refs Through Clone ```tsx function Wrapper({ children, ...rest }: Props, forwardedRef: Ref<any>) { if (!isValidElement(children)) { return children; } // Clone and forward ref + other props return cloneElement(children, { ...rest, ref: forwardedRef, }); } export const WrapperComponent = forwardRef(Wrapper); ``` ### Common Pattern in XMLUI **Container with single child ref forwarding:** ```tsx function Container({ children }: Props, ref: Ref<HTMLElement>) { const renderedChild = renderChild(children); // If single valid child, compose refs and merge props if (ref && renderedChild && isValidElement(renderedChild)) { return cloneElement(renderedChild, { ref: composeRefs(ref, (renderedChild as any).ref), ...mergeProps(renderedChild.props, rest), }); } return renderedChild; } ``` **Form field with label integration:** ```tsx function ItemWithLabel({ children, label }: Props) { const id = useId(); return ( <div> <label htmlFor={id}>{label}</label> {cloneElement(children as ReactElement, { id, 'aria-labelledby': id, })} </div> ); } ``` ### When to Use cloneElement **Use `cloneElement` when:** - Wrapping components need to add props to children - Forwarding refs through wrapper components - Adding common behavior to arbitrary children - Integrating with child elements you don't control **Don't use when:** - You can pass props directly (prefer explicit props) - You need to modify deeply nested children (use context instead) - Children are not React elements (check with `isValidElement` first) ### Common Mistakes ```tsx // ❌ WRONG - Not checking if child is valid element function Bad({ children }: Props) { return cloneElement(children, { className: 'bad' }); // Crashes if children is string/number } // ✅ CORRECT - Always validate first (see isValidElement section) function Good({ children }: Props) { if (!isValidElement(children)) { return children; } return cloneElement(children, { className: 'good' }); } // ❌ WRONG - Overriding all existing props return cloneElement(child, { style: { color: 'red' } }); // Loses child's existing style // ✅ CORRECT - Merge with existing props return cloneElement(child, { style: { ...child.props.style, color: 'red' }, }); ``` --- ## `isValidElement` - Type Check for React Elements **Purpose:** Check if a value is a valid React element (created with JSX or `createElement`). Always use before `cloneElement`. **Syntax:** `isValidElement(value)` ### Basic Usage ```tsx import { isValidElement } from 'react'; function processChild(child: React.ReactNode) { // child could be anything: string, number, element, array, etc. if (isValidElement(child)) { // TypeScript now knows child is ReactElement console.log(child.props); // ✅ OK - access props safely console.log(child.type); // ✅ OK - access type safely return child; } // Not an element - return as is return child; } ``` ### Common Pattern in XMLUI **Conditional element wrapping:** ```tsx function ConditionalWrapper({ condition, children }: Props) { if (!condition) { return children; } // Only wrap if child is valid element return isValidElement(children) ? <div className="wrapper">{children}</div> : children; } ``` ### What isValidElement Checks ```tsx isValidElement(<div />); // ✅ true - JSX element isValidElement(React.createElement('div')); // ✅ true - created element isValidElement(<Component />); // ✅ true - component element isValidElement('hello'); // ❌ false - string isValidElement(123); // ❌ false - number isValidElement(null); // ❌ false - null isValidElement(undefined); // ❌ false - undefined isValidElement([<div key="1" />]); // ❌ false - array of elements ``` ### When to Use isValidElement **Use `isValidElement` when:** - Before calling `cloneElement` (required to avoid crashes) - Type narrowing for TypeScript (ReactNode → ReactElement) - Validating `children` prop type - Conditional element manipulation **Note:** See `cloneElement` section for examples of using these two functions together. --- ## `flushSync` - Synchronous State Updates **Purpose:** Force React to flush state updates synchronously, bypassing automatic batching. **Syntax:** `flushSync(() => { /* state updates */ })` **Warning:** Use sparingly - breaks React's batching optimization and can hurt performance. ### Basic Usage ```tsx import { flushSync } from 'react-dom'; function Form() { const [value, setValue] = useState(''); const handleSubmit = () => { // Normal: state updates are batched setValue(''); setError(null); // Both updates happen together // With flushSync: update happens immediately flushSync(() => { setValue(''); }); // DOM is updated here, before next line inputRef.current?.focus(); }; } ``` ### When DOM Must Update Immediately ```tsx function Table({ data }: Props) { const [selectedRow, setSelectedRow] = useState(0); const rowRef = useRef<HTMLTableRowElement>(null); const selectRow = (index: number) => { // Must update DOM before scrolling flushSync(() => { setSelectedRow(index); }); // DOM is updated, can now scroll rowRef.current?.scrollIntoView(); }; } ``` ### Common Pattern in XMLUI **Form reset with focus:** ```tsx function Form({ onSubmit }: Props) { const doReset = () => { // Reset all fields }; const handleSuccess = () => { const prevFocused = document.activeElement; // Force synchronous reset before restoring focus flushSync(() => { doReset(); }); // DOM is reset, restore focus if (prevFocused && typeof (prevFocused as HTMLElement).focus === 'function') { (prevFocused as HTMLElement).focus(); } }; } ``` **Table with immediate scroll:** ```tsx function DataTable({ data }: Props) { const handleSort = (column: string) => { // Update sort synchronously before scrolling flushSync(() => { setSortColumn(column); setSortedData(sortData(data, column)); }); // Table is re-rendered, can scroll to top tableRef.current?.scrollTo(0, 0); }; } ``` ### Why flushSync Exists ```tsx // ❌ Problem: Without flushSync function Component() { const [text, setText] = useState(''); const update = () => { setText('new value'); // DOM not updated yet! inputRef.current?.focus(); // Focuses old state }; } // ✅ Solution: With flushSync function Component() { const [text, setText] = useState(''); const update = () => { flushSync(() => { setText('new value'); }); // DOM is updated inputRef.current?.focus(); // Focuses new state }; } ``` ### When to Use flushSync **Use `flushSync` when:** - Need DOM measurements after state change - Synchronizing with third-party libraries - Scrolling after state update - Focus management after state change **Don't use when:** - Normal state updates (let React batch) - Performance-critical code paths - You can solve it with `useLayoutEffect` - Inside render (not allowed) **Performance impact:** ```tsx // ❌ BAD - Multiple flushSync calls data.forEach(item => { flushSync(() => { processItem(item); // Forces re-render each time }); }); // ✅ GOOD - Single batch update const processedItems = data.map(processItem); flushSync(() => { setItems(processedItems); // Single re-render }); ``` --- ## `createRoot` - React 18 Root API **Purpose:** Create a root to render React components into a DOM container (React 18+). **Syntax:** `const root = createRoot(container); root.render(<App />)` ### Basic Usage ```tsx import { createRoot } from 'react-dom/client'; // Old way (React 17) ReactDOM.render(<App />, document.getElementById('root')); // New way (React 18+) const root = createRoot(document.getElementById('root')!); root.render(<App />); ``` ### With TypeScript ```tsx import { createRoot } from 'react-dom/client'; const container = document.getElementById('root'); if (!container) { throw new Error('Root element not found'); } const root = createRoot(container); root.render(<App />); ``` ### Unmounting ```tsx const root = createRoot(container); root.render(<App />); // Later: unmount root.unmount(); ``` ### Common Pattern in XMLUI **Standalone app rendering:** ```tsx function renderStandaloneApp(rootElement: HTMLElement) { let contentRoot: Root; if (!contentRoot) { contentRoot = createRoot(rootElement); } contentRoot.render( <StrictMode> <App /> </StrictMode> ); return contentRoot; } ``` **Shadow DOM rendering:** ```tsx function NestedApp({ children }: Props) { const shadowRef = useRef<ShadowRoot>(null); const contentRootRef = useRef<Root | null>(null); useEffect(() => { if (shadowRef.current && !contentRootRef.current) { // Create root in shadow DOM contentRootRef.current = createRoot(shadowRef.current); contentRootRef.current.render(<NestedContent />); } return () => { contentRootRef.current?.unmount(); }; }, []); } ``` ### Benefits of createRoot (React 18) 1. **Automatic batching** - All updates batched, even in promises/setTimeout 2. **Concurrent features** - Enables `useTransition`, `useDeferredValue`, etc. 3. **Improved hydration** - Better SSR support 4. **Suspense improvements** - Better streaming SSR ### When to Use createRoot **Use `createRoot` when:** - Starting a new React 18+ application - Rendering React into a DOM container - Creating multiple roots in one app - Rendering into shadow DOM **Migration from React 17:** ```tsx // React 17 import ReactDOM from 'react-dom'; ReactDOM.render(<App />, container); ReactDOM.unmountComponentAtNode(container); // React 18 import { createRoot } from 'react-dom/client'; const root = createRoot(container); root.render(<App />); root.unmount(); ``` --- ## React Accessibility Patterns ### ARIA Attributes Pattern **Key ARIA attributes:** `role`, `aria-label`, `aria-labelledby`, `aria-describedby`, `aria-hidden`, `aria-live`, `aria-expanded`, `aria-selected`, `aria-disabled`, `aria-current` **Common patterns:** ```tsx // Icon button with accessible label <button onClick={onClick} aria-label="Close dialog"> <Icon name="close" aria-hidden="true" /> </button> // Form field with error/help text function FormField({ label, error, helpText }: Props) { const id = useId(); return ( <> <label htmlFor={id}>{label}</label> <input id={id} aria-describedby={`${id}-desc`} aria-invalid={!!error} /> <span id={`${id}-desc`} role={error ? "alert" : undefined}> {error || helpText} </span> </> ); } // Accordion/expandable section <button aria-expanded={isOpen} aria-controls={contentId}> {title} </button> <div id={contentId} hidden={!isOpen} role="region"> {children} </div> // Live region for announcements <div role="status" aria-live="polite" aria-atomic="true"> {message} </div> // Modal dialog <div role="dialog" aria-modal="true" aria-labelledby={titleId}> <h2 id={titleId}>{title}</h2> {children} </div> // Tab navigation <div role="tablist"> <button role="tab" aria-selected={isActive} aria-controls={panelId}> {label} </button> </div> <div role="tabpanel" id={panelId} aria-labelledby={tabId}> {content} </div> ``` **Rules:** Use semantic HTML first, add ARIA only when needed, keep attributes in sync with state, test with screen readers. --- ### Focus Management Pattern **Common scenarios:** Auto-focus on mount, focus traps in modals, focus restoration, roving tab index. ```tsx // Auto-focus first element in dialog function Dialog({ isOpen }: Props) { const buttonRef = useRef<HTMLButtonElement>(null); useEffect(() => { if (isOpen) buttonRef.current?.focus(); }, [isOpen]); return <button ref={buttonRef}>Close</button>; } // Focus trap + restoration in modal function Modal({ isOpen, onClose, children }: Props) { const modalRef = useRef<HTMLDivElement>(null); const restoreFocusRef = useRef<HTMLElement | null>(null); useEffect(() => { if (!isOpen) return; restoreFocusRef.current = document.activeElement as HTMLElement; modalRef.current?.querySelector<HTMLElement>('button')?.focus(); return () => restoreFocusRef.current?.focus(); }, [isOpen]); const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Escape') onClose(); // Trap Tab key if (e.key === 'Tab') { const focusable = modalRef.current?.querySelectorAll<HTMLElement>( 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' ); if (!focusable?.length) return; const first = focusable[0]; const last = focusable[focusable.length - 1]; if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); } else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); } } }; return ( <div ref={modalRef} role="dialog" aria-modal="true" onKeyDown={handleKeyDown}> {children} </div> ); } // Focus after delete action function DeleteButton({ itemId, onDelete }: Props) { const handleDelete = () => { const current = document.getElementById(`item-${itemId}`); const next = (current?.nextElementSibling || current?.previousElementSibling) ?.querySelector('button') as HTMLElement; onDelete(itemId); setTimeout(() => next?.focus(), 0); }; return <button onClick={handleDelete}>Delete</button>; } // Roving tab index for lists function RadioGroup({ options, value, onChange }: Props) { const [focusedIndex, setFocusedIndex] = useState(0); const handleKeyDown = (e: React.KeyboardEvent, index: number) => { let newIndex = index; if (e.key === 'ArrowDown') newIndex = (index + 1) % options.length; if (e.key === 'ArrowUp') newIndex = index === 0 ? options.length - 1 : index - 1; if (e.key === 'Home') newIndex = 0; if (e.key === 'End') newIndex = options.length - 1; if (newIndex !== index) { e.preventDefault(); setFocusedIndex(newIndex); } }; return ( <div role="radiogroup"> {options.map((opt, i) => ( <div key={opt.id} role="radio" aria-checked={value === opt.id} tabIndex={focusedIndex === i ? 0 : -1} onClick={() => onChange(opt.id)} onKeyDown={(e) => handleKeyDown(e, i)} onFocus={() => setFocusedIndex(i)} > {opt.label} </div> ))} </div> ); } ``` **Rules:** Always restore focus when closing modals, trap focus within modal contexts, use `focus-visible` for keyboard-only indicators, test thoroughly. --- ### Keyboard Navigation Pattern **Standard keyboard shortcuts:** Escape (close), Tab/Shift+Tab (navigate), Arrow keys (move focus), Enter/Space (activate), Home/End (first/last). ```tsx // Dropdown with full keyboard support function Dropdown({ trigger, items, onSelect }: Props) { const [isOpen, setIsOpen] = useState(false); const [focusedIndex, setFocusedIndex] = useState(0); const itemsRef = useRef<(HTMLButtonElement | null)[]>([]); const handleKeyDown = (e: React.KeyboardEvent) => { switch (e.key) { case 'ArrowDown': e.preventDefault(); if (!isOpen) { setIsOpen(true); } else { const next = (focusedIndex + 1) % items.length; setFocusedIndex(next); itemsRef.current[next]?.focus(); } break; case 'ArrowUp': e.preventDefault(); if (isOpen) { const prev = focusedIndex === 0 ? items.length - 1 : focusedIndex - 1; setFocusedIndex(prev); itemsRef.current[prev]?.focus(); } break; case 'Escape': e.preventDefault(); setIsOpen(false); break; case 'Enter': case ' ': e.preventDefault(); if (isOpen) { onSelect(items[focusedIndex]); setIsOpen(false); } else { setIsOpen(true); } break; } }; return ( <div onKeyDown={handleKeyDown}> <button onClick={() => setIsOpen(!isOpen)} aria-expanded={isOpen}> {trigger} </button> {isOpen && ( <ul role="menu"> {items.map((item, i) => ( <li key={item.id} role="none"> <button ref={el => itemsRef.current[i] = el} role="menuitem" onClick={() => { onSelect(item); setIsOpen(false); }} onFocus={() => setFocusedIndex(i)} > {item.label} </button> </li> ))} </ul> )} </div> ); } // Global keyboard shortcuts hook function useKeyboardShortcuts(shortcuts: Record<string, () => void>) { useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { const keys: string[] = []; if (e.ctrlKey || e.metaKey) keys.push('Ctrl'); if (e.shiftKey) keys.push('Shift'); if (e.altKey) keys.push('Alt'); keys.push(e.key.toUpperCase()); const handler = shortcuts[keys.join('+')]; if (handler) { e.preventDefault(); handler(); } }; document.addEventListener('keydown', handleKeyDown); return () => document.removeEventListener('keydown', handleKeyDown); }, [shortcuts]); } // Usage: Editor with shortcuts function Editor() { useKeyboardShortcuts({ 'Ctrl+S': handleSave, 'Ctrl+Z': handleUndo, 'Ctrl+Shift+Z': handleRedo, }); return <div>Editor</div>; } // Data table with arrow key navigation function DataTable({ columns, rows }: Props) { const [focusedCell, setFocusedCell] = useState({ row: 0, col: 0 }); const cellRefs = useRef<(HTMLTableCellElement | null)[][]>([]); const handleKeyDown = (e: React.KeyboardEvent, rowIndex: number, colIndex: number) => { let newRow = rowIndex, newCol = colIndex; if (e.key === 'ArrowUp') newRow = Math.max(0, rowIndex - 1); if (e.key === 'ArrowDown') newRow = Math.min(rows.length - 1, rowIndex + 1); if (e.key === 'ArrowLeft') newCol = Math.max(0, colIndex - 1); if (e.key === 'ArrowRight') newCol = Math.min(columns.length - 1, colIndex + 1); if (e.key === 'Home') newCol = 0; if (e.key === 'End') newCol = columns.length - 1; if (newRow !== rowIndex || newCol !== colIndex) { e.preventDefault(); setFocusedCell({ row: newRow, col: newCol }); cellRefs.current[newRow]?.[newCol]?.focus(); } }; return ( <table> <tbody> {rows.map((row, ri) => ( <tr key={row.id}> {columns.map((col, ci) => ( <td key={col.id} ref={el => { if (!cellRefs.current[ri]) cellRefs.current[ri] = []; cellRefs.current[ri][ci] = el; }} tabIndex={focusedCell.row === ri && focusedCell.col === ci ? 0 : -1} onKeyDown={e => handleKeyDown(e, ri, ci)} onFocus={() => setFocusedCell({ row: ri, col: ci })} > {row[col.id]} </td> ))} </tr> ))} </tbody> </table> ); } ``` **Rules:** Support standard shortcuts (Escape, Tab, Arrows), don't override browser/OS shortcuts, provide visible focus feedback, test keyboard-only navigation. --- ### Accessibility Best Practices Summary **Key principles:** - Use semantic HTML first (`<button>`, `<nav>`, `<main>`) - Add ARIA only when semantic HTML isn't enough - All interactive elements must be keyboard accessible - Provide visible focus indicators (use `:focus-visible`) - Keep ARIA attributes in sync with visual state - Restore focus after closing modals/dialogs - Test with screen readers and keyboard only **Testing checklist:** - [ ] Navigate entire app with keyboard only - [ ] Focus indicators visible and high contrast - [ ] Screen reader announces all content correctly - [ ] Color not the only state indicator - [ ] Text contrast ≥ 4.5:1 for normal text - [ ] Interactive elements have accessible names - [ ] Form fields have associated labels - [ ] Error messages are announced **Tools:** [axe DevTools](https://www.deque.com/axe/devtools/), [WAVE](https://wave.webaim.org/), [Lighthouse](https://developers.google.com/web/tools/lighthouse), screen readers (NVDA, JAWS, VoiceOver) ```