This is page 173 of 181. Use http://codebase.md/xmlui-org/xmlui/tools/vscode/resources/%7BimageSrc%7D?lines=true&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.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
│ ├── layout-changes.md
│ ├── 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
│ │ │ ├── rss.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
│ │ └── blog-theme.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
│ │ │ │ ├── make-navpanel-width-responsive.md
│ │ │ │ ├── modify-a-value-reported-in-a-column.md
│ │ │ │ ├── paginate-a-list.md
│ │ │ │ ├── pass-data-to-a-modal-dialog.md
│ │ │ │ ├── react-to-button-click-not-keystrokes.md
│ │ │ │ ├── set-the-initial-value-of-a-select-from-fetched-data.md
│ │ │ │ ├── share-a-modaldialog-across-components.md
│ │ │ │ ├── sync-selections-between-table-and-list-views.md
│ │ │ │ ├── update-ui-optimistically.md
│ │ │ │ ├── use-built-in-form-validation.md
│ │ │ │ └── use-the-same-modaldialog-to-add-or-edit.md
│ │ │ ├── howto.md
│ │ │ ├── intro.md
│ │ │ ├── layout.md
│ │ │ ├── markup.md
│ │ │ ├── mcp.md
│ │ │ ├── modal-dialogs.md
│ │ │ ├── news-and-reviews.md
│ │ │ ├── reactive-intro.md
│ │ │ ├── refactoring.md
│ │ │ ├── routing-and-links.md
│ │ │ ├── samples
│ │ │ │ ├── color-palette.xmlui
│ │ │ │ ├── color-values.xmlui
│ │ │ │ ├── shadow-sizes.xmlui
│ │ │ │ ├── spacing-sizes.xmlui
│ │ │ │ ├── swatch.xmlui
│ │ │ │ ├── theme-gallery-brief.xmlui
│ │ │ │ └── theme-gallery.xmlui
│ │ │ ├── scoping.md
│ │ │ ├── scripting.md
│ │ │ ├── styles-and-themes
│ │ │ │ ├── common-units.md
│ │ │ │ ├── layout-props.md
│ │ │ │ ├── theme-variable-defaults.md
│ │ │ │ ├── theme-variables.md
│ │ │ │ └── themes.md
│ │ │ ├── template-properties.md
│ │ │ ├── test.md
│ │ │ ├── tutorial-01.md
│ │ │ ├── tutorial-02.md
│ │ │ ├── tutorial-03.md
│ │ │ ├── tutorial-04.md
│ │ │ ├── tutorial-05.md
│ │ │ ├── tutorial-06.md
│ │ │ ├── tutorial-07.md
│ │ │ ├── tutorial-08.md
│ │ │ ├── tutorial-09.md
│ │ │ ├── tutorial-10.md
│ │ │ ├── tutorial-11.md
│ │ │ ├── tutorial-12.md
│ │ │ ├── universal-properties.md
│ │ │ ├── user-defined-components.md
│ │ │ ├── vscode.md
│ │ │ ├── working-with-markdown.md
│ │ │ ├── working-with-text.md
│ │ │ ├── xmlui-animations
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── Animation.md
│ │ │ │ ├── FadeAnimation.md
│ │ │ │ ├── FadeInAnimation.md
│ │ │ │ ├── FadeOutAnimation.md
│ │ │ │ ├── ScaleAnimation.md
│ │ │ │ └── SlideInAnimation.md
│ │ │ ├── xmlui-charts
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── BarChart.md
│ │ │ │ ├── DonutChart.md
│ │ │ │ ├── LabelList.md
│ │ │ │ ├── Legend.md
│ │ │ │ ├── LineChart.md
│ │ │ │ └── PieChart.md
│ │ │ ├── xmlui-pdf
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Pdf.md
│ │ │ └── xmlui-spreadsheet
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ └── Spreadsheet.md
│ │ ├── resources
│ │ │ ├── devdocs
│ │ │ │ ├── debug-proxy-object-2.png
│ │ │ │ ├── debug-proxy-object.png
│ │ │ │ ├── table_editor_01.png
│ │ │ │ ├── table_editor_02.png
│ │ │ │ ├── table_editor_03.png
│ │ │ │ ├── table_editor_04.png
│ │ │ │ ├── table_editor_05.png
│ │ │ │ ├── table_editor_06.png
│ │ │ │ ├── table_editor_07.png
│ │ │ │ ├── table_editor_08.png
│ │ │ │ ├── table_editor_09.png
│ │ │ │ ├── table_editor_10.png
│ │ │ │ ├── table_editor_11.png
│ │ │ │ ├── table-editor-01.png
│ │ │ │ ├── table-editor-02.png
│ │ │ │ ├── table-editor-03.png
│ │ │ │ ├── table-editor-04.png
│ │ │ │ ├── table-editor-06.png
│ │ │ │ ├── table-editor-07.png
│ │ │ │ ├── table-editor-08.png
│ │ │ │ ├── table-editor-09.png
│ │ │ │ └── xmlui-rendering-of-tiptap-markdown.png
│ │ │ ├── favicon.ico
│ │ │ ├── files
│ │ │ │ ├── clients.json
│ │ │ │ ├── daily-revenue.json
│ │ │ │ ├── dashboard-stats.json
│ │ │ │ ├── demo.xmlui
│ │ │ │ ├── demo.xmlui.xs
│ │ │ │ ├── downloads
│ │ │ │ │ └── downloads.json
│ │ │ │ ├── for-download
│ │ │ │ │ ├── index-with-api.html
│ │ │ │ │ ├── index.html
│ │ │ │ │ ├── mockApi.js
│ │ │ │ │ ├── start-darwin.sh
│ │ │ │ │ ├── start-linux.sh
│ │ │ │ │ ├── start.bat
│ │ │ │ │ └── xmlui
│ │ │ │ │ └── xmlui-standalone.umd.js
│ │ │ │ ├── getting-started
│ │ │ │ │ ├── cl-tutorial-final.zip
│ │ │ │ │ ├── cl-tutorial.zip
│ │ │ │ │ ├── cl-tutorial2.zip
│ │ │ │ │ ├── cl-tutorial3.zip
│ │ │ │ │ ├── cl-tutorial4.zip
│ │ │ │ │ ├── cl-tutorial5.zip
│ │ │ │ │ ├── cl-tutorial6.zip
│ │ │ │ │ ├── getting-started.zip
│ │ │ │ │ ├── hello-xmlui.zip
│ │ │ │ │ ├── xmlui-empty.zip
│ │ │ │ │ └── xmlui-starter.zip
│ │ │ │ ├── howto
│ │ │ │ │ └── component-icons
│ │ │ │ │ └── up-arrow.svg
│ │ │ │ ├── invoices.json
│ │ │ │ ├── monthly-status.json
│ │ │ │ ├── news-and-reviews.json
│ │ │ │ ├── products.json
│ │ │ │ ├── releases.json
│ │ │ │ ├── tutorials
│ │ │ │ │ ├── datasource
│ │ │ │ │ │ └── api.ts
│ │ │ │ │ └── p2do
│ │ │ │ │ ├── api.ts
│ │ │ │ │ └── todo-logo.svg
│ │ │ │ └── xmlui.json
│ │ │ ├── github.svg
│ │ │ ├── images
│ │ │ │ ├── apiaction-tutorial
│ │ │ │ │ ├── add-success.png
│ │ │ │ │ ├── apiaction-param.png
│ │ │ │ │ ├── change-completed.png
│ │ │ │ │ ├── change-in-progress.png
│ │ │ │ │ ├── confirm-delete.png
│ │ │ │ │ ├── data-error.png
│ │ │ │ │ ├── data-progress.png
│ │ │ │ │ ├── data-success.png
│ │ │ │ │ ├── display-1.png
│ │ │ │ │ ├── item-deleted.png
│ │ │ │ │ ├── item-updated.png
│ │ │ │ │ ├── missing-api-key.png
│ │ │ │ │ ├── new-item-added.png
│ │ │ │ │ └── test-message.png
│ │ │ │ ├── chat-api
│ │ │ │ │ └── domain-model.svg
│ │ │ │ ├── components
│ │ │ │ │ ├── image
│ │ │ │ │ │ └── breakfast.jpg
│ │ │ │ │ ├── markdown
│ │ │ │ │ │ └── colors.png
│ │ │ │ │ └── modal
│ │ │ │ │ ├── deep_link_dialog_1.jpg
│ │ │ │ │ └── deep_link_dialog_2.jpg
│ │ │ │ ├── create-apps
│ │ │ │ │ ├── collapsed-vertical.png
│ │ │ │ │ ├── using-forms-warning-dialog.png
│ │ │ │ │ └── using-forms.png
│ │ │ │ ├── datasource-tutorial
│ │ │ │ │ ├── data-with-header.png
│ │ │ │ │ ├── filtered-data.png
│ │ │ │ │ ├── filtered-items.png
│ │ │ │ │ ├── initial-page-items.png
│ │ │ │ │ ├── list-items.png
│ │ │ │ │ ├── next-page-items.png
│ │ │ │ │ ├── no-data.png
│ │ │ │ │ ├── pagination-1.jpg
│ │ │ │ │ ├── pagination-1.png
│ │ │ │ │ ├── polling-1.png
│ │ │ │ │ ├── refetch-data.png
│ │ │ │ │ ├── slow-loading.png
│ │ │ │ │ ├── test-message.png
│ │ │ │ │ ├── Thumbs.db
│ │ │ │ │ ├── unconventional-data.png
│ │ │ │ │ └── unfiltered-items.png
│ │ │ │ ├── flower.jpg
│ │ │ │ ├── get-started
│ │ │ │ │ ├── add-new-contact.png
│ │ │ │ │ ├── app-modified.png
│ │ │ │ │ ├── app-start.png
│ │ │ │ │ ├── app-with-boxes.png
│ │ │ │ │ ├── app-with-toast.png
│ │ │ │ │ ├── boilerplate-structure.png
│ │ │ │ │ ├── cl-initial.png
│ │ │ │ │ ├── cl-start.png
│ │ │ │ │ ├── contact-counts.png
│ │ │ │ │ ├── contact-dialog-title.png
│ │ │ │ │ ├── contact-dialog.png
│ │ │ │ │ ├── contact-menus.png
│ │ │ │ │ ├── contact-predicates.png
│ │ │ │ │ ├── context-menu.png
│ │ │ │ │ ├── dashboard-numbers.png
│ │ │ │ │ ├── default-contact-list.png
│ │ │ │ │ ├── delete-contact.png
│ │ │ │ │ ├── delete-task.png
│ │ │ │ │ ├── detailed-template.png
│ │ │ │ │ ├── edit-contact-details.png
│ │ │ │ │ ├── edited-contact-saved.png
│ │ │ │ │ ├── empty-sections.png
│ │ │ │ │ ├── filter-completed.png
│ │ │ │ │ ├── fullwidth-desktop.png
│ │ │ │ │ ├── fullwidth-mobile.png
│ │ │ │ │ ├── initial-table.png
│ │ │ │ │ ├── items-and-badges.png
│ │ │ │ │ ├── loading-message.png
│ │ │ │ │ ├── new-contact-button.png
│ │ │ │ │ ├── new-contact-saved.png
│ │ │ │ │ ├── no-empty-sections.png
│ │ │ │ │ ├── personal-todo-initial.png
│ │ │ │ │ ├── piechart.png
│ │ │ │ │ ├── review-today.png
│ │ │ │ │ ├── rudimentary-dashboard.png
│ │ │ │ │ ├── section-collapsed.png
│ │ │ │ │ ├── sectioned-items.png
│ │ │ │ │ ├── sections-ordered.png
│ │ │ │ │ ├── spacex-list-with-links.png
│ │ │ │ │ ├── spacex-list.png
│ │ │ │ │ ├── start-personal-todo-1.png
│ │ │ │ │ ├── submit-new-contact.png
│ │ │ │ │ ├── submit-new-task.png
│ │ │ │ │ ├── syntax-highlighting.png
│ │ │ │ │ ├── table-with-badge.png
│ │ │ │ │ ├── template-with-card.png
│ │ │ │ │ ├── test-emulated-api.png
│ │ │ │ │ ├── Thumbs.db
│ │ │ │ │ ├── todo-logo.png
│ │ │ │ │ └── xmlui-tools.png
│ │ │ │ ├── HelloApp.png
│ │ │ │ ├── HelloApp2.png
│ │ │ │ ├── logos
│ │ │ │ │ ├── xmlui1.svg
│ │ │ │ │ ├── xmlui2.svg
│ │ │ │ │ ├── xmlui3.svg
│ │ │ │ │ ├── xmlui4.svg
│ │ │ │ │ ├── xmlui5.svg
│ │ │ │ │ ├── xmlui6.svg
│ │ │ │ │ └── xmlui7.svg
│ │ │ │ ├── pdf
│ │ │ │ │ └── dummy-pdf.jpg
│ │ │ │ ├── rendering-engine
│ │ │ │ │ ├── AppEngine-flow.svg
│ │ │ │ │ ├── Component.svg
│ │ │ │ │ ├── CompoundComponent.svg
│ │ │ │ │ ├── RootComponent.svg
│ │ │ │ │ └── tree-with-containers.svg
│ │ │ │ ├── reviewers-guide
│ │ │ │ │ ├── AppEngine-flow.svg
│ │ │ │ │ └── incbutton-in-action.png
│ │ │ │ ├── tools
│ │ │ │ │ └── boilerplate-structure.png
│ │ │ │ ├── try.svg
│ │ │ │ ├── tutorial
│ │ │ │ │ ├── app-chat-history.png
│ │ │ │ │ ├── app-content-placeholder.png
│ │ │ │ │ ├── app-header-and-content.png
│ │ │ │ │ ├── app-links-channel-selected.png
│ │ │ │ │ ├── app-links-click.png
│ │ │ │ │ ├── app-navigation.png
│ │ │ │ │ ├── finished-ex01.png
│ │ │ │ │ ├── finished-ex02.png
│ │ │ │ │ ├── hello.png
│ │ │ │ │ ├── splash-screen-advanced.png
│ │ │ │ │ ├── splash-screen-after-click.png
│ │ │ │ │ ├── splash-screen-centered.png
│ │ │ │ │ ├── splash-screen-events.png
│ │ │ │ │ ├── splash-screen-expression.png
│ │ │ │ │ ├── splash-screen-reuse-after.png
│ │ │ │ │ ├── splash-screen-reuse-before.png
│ │ │ │ │ └── splash-screen.png
│ │ │ │ └── tutorial-01.png
│ │ │ ├── llms.txt
│ │ │ ├── logo-dark.svg
│ │ │ ├── logo.svg
│ │ │ ├── pg-popout.svg
│ │ │ └── xmlui-logo.svg
│ │ ├── serve.json
│ │ └── web.config
│ ├── scripts
│ │ ├── download-latest-xmlui.js
│ │ ├── generate-rss.js
│ │ ├── get-releases.js
│ │ └── utils.js
│ ├── src
│ │ ├── components
│ │ │ ├── BlogOverview.xmlui
│ │ │ ├── BlogPage.xmlui
│ │ │ ├── Boxes.xmlui
│ │ │ ├── Breadcrumb.xmlui
│ │ │ ├── ChangeLog.xmlui
│ │ │ ├── ColorPalette.xmlui
│ │ │ ├── DocumentLinks.xmlui
│ │ │ ├── DocumentPage.xmlui
│ │ │ ├── DocumentPageNoTOC.xmlui
│ │ │ ├── Icons.xmlui
│ │ │ ├── IncButton.xmlui
│ │ │ ├── IncButton2.xmlui
│ │ │ ├── NameValue.xmlui
│ │ │ ├── PageNotFound.xmlui
│ │ │ ├── PaletteItem.xmlui
│ │ │ ├── Palettes.xmlui
│ │ │ ├── SectionHeader.xmlui
│ │ │ ├── TBD.xmlui
│ │ │ ├── Test.xmlui
│ │ │ ├── ThemesIntro.xmlui
│ │ │ ├── ThousandThemes.xmlui
│ │ │ ├── TubeStops.xmlui
│ │ │ ├── TubeStops.xmlui.xs
│ │ │ └── TwoColumnCode.xmlui
│ │ ├── config.ts
│ │ ├── Main.xmlui
│ │ └── themes
│ │ ├── docs-theme.ts
│ │ ├── earthtone.ts
│ │ ├── xmlui-gray-on-default.ts
│ │ ├── xmlui-green-on-default.ts
│ │ └── xmlui-orange-on-default.ts
│ └── tsconfig.json
├── LICENSE
├── package-lock.json
├── package.json
├── packages
│ ├── xmlui-animations
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ ├── src
│ │ │ ├── Animation.tsx
│ │ │ ├── AnimationNative.tsx
│ │ │ ├── FadeAnimation.tsx
│ │ │ ├── FadeInAnimation.tsx
│ │ │ ├── FadeOutAnimation.tsx
│ │ │ ├── index.tsx
│ │ │ ├── ScaleAnimation.tsx
│ │ │ └── SlideInAnimation.tsx
│ │ └── tsconfig.json
│ ├── xmlui-devtools
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ ├── src
│ │ │ ├── devtools
│ │ │ │ ├── DevTools.tsx
│ │ │ │ ├── DevToolsNative.module.scss
│ │ │ │ ├── DevToolsNative.tsx
│ │ │ │ ├── ModalDialog.module.scss
│ │ │ │ ├── ModalDialog.tsx
│ │ │ │ ├── ModalVisibilityContext.tsx
│ │ │ │ ├── Tooltip.module.scss
│ │ │ │ ├── Tooltip.tsx
│ │ │ │ └── utils.ts
│ │ │ ├── editor
│ │ │ │ └── Editor.tsx
│ │ │ └── index.tsx
│ │ ├── tsconfig.json
│ │ └── vite.config-overrides.ts
│ ├── xmlui-hello-world
│ │ ├── .gitignore
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ ├── src
│ │ │ ├── HelloWorld.module.scss
│ │ │ ├── HelloWorld.tsx
│ │ │ ├── HelloWorldNative.tsx
│ │ │ └── index.tsx
│ │ └── tsconfig.json
│ ├── xmlui-os-frames
│ │ ├── .gitignore
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ ├── src
│ │ │ ├── index.tsx
│ │ │ ├── IPhoneFrame.module.scss
│ │ │ ├── IPhoneFrame.tsx
│ │ │ ├── MacOSAppFrame.module.scss
│ │ │ ├── MacOSAppFrame.tsx
│ │ │ ├── WindowsAppFrame.module.scss
│ │ │ └── WindowsAppFrame.tsx
│ │ └── tsconfig.json
│ ├── xmlui-pdf
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ ├── components
│ │ │ │ └── Pdf.xmlui
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ ├── src
│ │ │ ├── index.tsx
│ │ │ ├── LazyPdfNative.tsx
│ │ │ ├── Pdf.module.scss
│ │ │ └── Pdf.tsx
│ │ └── tsconfig.json
│ ├── xmlui-playground
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ ├── src
│ │ │ ├── hooks
│ │ │ │ ├── usePlayground.ts
│ │ │ │ └── useToast.ts
│ │ │ ├── index.tsx
│ │ │ ├── playground
│ │ │ │ ├── Box.module.scss
│ │ │ │ ├── Box.tsx
│ │ │ │ ├── CodeSelector.tsx
│ │ │ │ ├── ConfirmationDialog.module.scss
│ │ │ │ ├── ConfirmationDialog.tsx
│ │ │ │ ├── Editor.tsx
│ │ │ │ ├── Header.module.scss
│ │ │ │ ├── Header.tsx
│ │ │ │ ├── Playground.tsx
│ │ │ │ ├── PlaygroundContent.module.scss
│ │ │ │ ├── PlaygroundContent.tsx
│ │ │ │ ├── PlaygroundNative.module.scss
│ │ │ │ ├── PlaygroundNative.tsx
│ │ │ │ ├── Preview.module.scss
│ │ │ │ ├── Preview.tsx
│ │ │ │ ├── Select.module.scss
│ │ │ │ ├── StandalonePlayground.tsx
│ │ │ │ ├── StandalonePlaygroundNative.module.scss
│ │ │ │ ├── StandalonePlaygroundNative.tsx
│ │ │ │ ├── ThemeSwitcher.module.scss
│ │ │ │ ├── ThemeSwitcher.tsx
│ │ │ │ ├── ToneSwitcher.tsx
│ │ │ │ ├── Tooltip.module.scss
│ │ │ │ ├── Tooltip.tsx
│ │ │ │ └── utils.ts
│ │ │ ├── providers
│ │ │ │ ├── Toast.module.scss
│ │ │ │ └── ToastProvider.tsx
│ │ │ ├── state
│ │ │ │ └── store.ts
│ │ │ ├── themes
│ │ │ │ └── theme.ts
│ │ │ └── utils
│ │ │ └── helpers.ts
│ │ └── tsconfig.json
│ ├── xmlui-search
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ ├── src
│ │ │ ├── index.tsx
│ │ │ ├── Search.module.scss
│ │ │ └── Search.tsx
│ │ └── tsconfig.json
│ ├── xmlui-spreadsheet
│ │ ├── .gitignore
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ ├── src
│ │ │ ├── index.tsx
│ │ │ ├── Spreadsheet.tsx
│ │ │ └── SpreadsheetNative.tsx
│ │ └── tsconfig.json
│ └── xmlui-website-blocks
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── demo
│ │ ├── components
│ │ │ ├── HeroBackgroundBreakoutPage.xmlui
│ │ │ ├── HeroBackgroundsPage.xmlui
│ │ │ ├── HeroContentsPage.xmlui
│ │ │ ├── HeroTextAlignPage.xmlui
│ │ │ ├── HeroTextPage.xmlui
│ │ │ └── HeroTonesPage.xmlui
│ │ ├── Main.xmlui
│ │ └── themes
│ │ └── default.ts
│ ├── index.html
│ ├── index.ts
│ ├── meta
│ │ └── componentsMetadata.ts
│ ├── package.json
│ ├── public
│ │ └── resources
│ │ ├── building.jpg
│ │ └── xmlui-logo.svg
│ ├── src
│ │ ├── Carousel
│ │ │ ├── Carousel.module.scss
│ │ │ ├── Carousel.tsx
│ │ │ ├── CarouselContext.tsx
│ │ │ └── CarouselNative.tsx
│ │ ├── FancyButton
│ │ │ ├── FancyButton.module.scss
│ │ │ ├── FancyButton.tsx
│ │ │ └── FancyButton.xmlui
│ │ ├── Hello
│ │ │ ├── Hello.tsx
│ │ │ ├── Hello.xmlui
│ │ │ └── Hello.xmlui.xs
│ │ ├── HeroSection
│ │ │ ├── HeroSection.module.scss
│ │ │ ├── HeroSection.tsx
│ │ │ └── HeroSectionNative.tsx
│ │ ├── index.tsx
│ │ ├── ScrollToTop
│ │ │ ├── ScrollToTop.module.scss
│ │ │ ├── ScrollToTop.tsx
│ │ │ └── ScrollToTopNative.tsx
│ │ └── vite-env.d.ts
│ └── tsconfig.json
├── README.md
├── tools
│ ├── codefence
│ │ └── xmlui-code-fence-docs.md
│ ├── create-app
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── create-app.ts
│ │ ├── helpers
│ │ │ ├── copy.ts
│ │ │ ├── get-pkg-manager.ts
│ │ │ ├── git.ts
│ │ │ ├── install.ts
│ │ │ ├── is-folder-empty.ts
│ │ │ ├── is-writeable.ts
│ │ │ ├── make-dir.ts
│ │ │ └── validate-pkg.ts
│ │ ├── index.ts
│ │ ├── package.json
│ │ ├── templates
│ │ │ ├── default
│ │ │ │ └── ts
│ │ │ │ ├── gitignore
│ │ │ │ ├── index.html
│ │ │ │ ├── index.ts
│ │ │ │ ├── public
│ │ │ │ │ ├── mockServiceWorker.js
│ │ │ │ │ ├── resources
│ │ │ │ │ │ ├── favicon.ico
│ │ │ │ │ │ └── xmlui-logo.svg
│ │ │ │ │ └── serve.json
│ │ │ │ └── src
│ │ │ │ ├── components
│ │ │ │ │ ├── ApiAware.xmlui
│ │ │ │ │ ├── Home.xmlui
│ │ │ │ │ ├── IncButton.xmlui
│ │ │ │ │ └── PagePanel.xmlui
│ │ │ │ ├── config.ts
│ │ │ │ └── Main.xmlui
│ │ │ ├── index.ts
│ │ │ └── types.ts
│ │ └── tsconfig.json
│ ├── create-xmlui-hello-world
│ │ ├── index.js
│ │ └── package.json
│ └── vscode
│ ├── .gitignore
│ ├── .vscode
│ │ ├── launch.json
│ │ └── tasks.json
│ ├── .vscodeignore
│ ├── build.sh
│ ├── CHANGELOG.md
│ ├── esbuild.js
│ ├── eslint.config.mjs
│ ├── formatter-docs.md
│ ├── generate-test-sample.sh
│ ├── LICENSE.md
│ ├── package-lock.json
│ ├── package.json
│ ├── README.md
│ ├── resources
│ │ ├── xmlui-logo.png
│ │ └── xmlui-markup-syntax-highlighting.png
│ ├── src
│ │ ├── extension.ts
│ │ └── server.ts
│ ├── syntaxes
│ │ └── xmlui.tmLanguage.json
│ ├── test-samples
│ │ └── sample.xmlui
│ ├── tsconfig.json
│ └── tsconfig.tsbuildinfo
├── turbo.json
└── xmlui
├── .gitignore
├── bin
│ ├── bootstrap.js
│ ├── build-lib.ts
│ ├── build.ts
│ ├── index.ts
│ ├── preview.ts
│ ├── start.ts
│ ├── vite-xmlui-plugin.ts
│ └── viteConfig.ts
├── CHANGELOG.md
├── conventions
│ ├── component-qa-checklist.md
│ ├── copilot-conventions.md
│ ├── create-xmlui-components.md
│ ├── mermaid.md
│ ├── testing-conventions.md
│ └── xmlui-in-a-nutshell.md
├── dev-docs
│ ├── accessibility.md
│ ├── build-system.md
│ ├── build-xmlui.md
│ ├── component-behaviors.md
│ ├── components-with-options.md
│ ├── containers.md
│ ├── data-operations.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
│ ├── ud-components.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
│ │ │ ├── OptionContext.ts
│ │ │ ├── Select.md
│ │ │ ├── Select.module.scss
│ │ │ ├── Select.spec.ts
│ │ │ ├── Select.tsx
│ │ │ ├── SelectContext.tsx
│ │ │ └── SelectNative.tsx
│ │ ├── SelectionStore
│ │ │ ├── SelectionStore.md
│ │ │ ├── SelectionStore.tsx
│ │ │ └── SelectionStoreNative.tsx
│ │ ├── Slider
│ │ │ ├── Slider.md
│ │ │ ├── Slider.module.scss
│ │ │ ├── Slider.spec.ts
│ │ │ ├── Slider.tsx
│ │ │ └── SliderNative.tsx
│ │ ├── Slot
│ │ │ ├── Slot.md
│ │ │ ├── Slot.spec.ts
│ │ │ └── Slot.ts
│ │ ├── SlotItem.tsx
│ │ ├── SpaceFiller
│ │ │ ├── SpaceFiller.md
│ │ │ ├── SpaceFiller.module.scss
│ │ │ ├── SpaceFiller.spec.ts
│ │ │ ├── SpaceFiller.tsx
│ │ │ └── SpaceFillerNative.tsx
│ │ ├── Spinner
│ │ │ ├── Spinner.md
│ │ │ ├── Spinner.module.scss
│ │ │ ├── Spinner.spec.ts
│ │ │ ├── Spinner.tsx
│ │ │ └── SpinnerNative.tsx
│ │ ├── Splitter
│ │ │ ├── HSplitter.md
│ │ │ ├── HSplitter.spec.ts
│ │ │ ├── Splitter.md
│ │ │ ├── Splitter.module.scss
│ │ │ ├── Splitter.spec.ts
│ │ │ ├── Splitter.tsx
│ │ │ ├── SplitterNative.tsx
│ │ │ ├── utils.ts
│ │ │ ├── VSplitter.md
│ │ │ └── VSplitter.spec.ts
│ │ ├── Stack
│ │ │ ├── CHStack.md
│ │ │ ├── CHStack.spec.ts
│ │ │ ├── CVStack.md
│ │ │ ├── CVStack.spec.ts
│ │ │ ├── HStack.md
│ │ │ ├── HStack.spec.ts
│ │ │ ├── Stack.md
│ │ │ ├── Stack.module.scss
│ │ │ ├── Stack.spec.ts
│ │ │ ├── Stack.tsx
│ │ │ ├── StackNative.tsx
│ │ │ ├── VStack.md
│ │ │ └── VStack.spec.ts
│ │ ├── StickyBox
│ │ │ ├── StickyBox.md
│ │ │ ├── StickyBox.module.scss
│ │ │ ├── StickyBox.tsx
│ │ │ └── StickyBoxNative.tsx
│ │ ├── Switch
│ │ │ ├── Switch.md
│ │ │ ├── Switch.spec.ts
│ │ │ └── Switch.tsx
│ │ ├── Table
│ │ │ ├── doc-resources
│ │ │ │ └── list-component-data.js
│ │ │ ├── react-table-config.d.ts
│ │ │ ├── Table.md
│ │ │ ├── Table.module.scss
│ │ │ ├── Table.spec.ts
│ │ │ ├── Table.tsx
│ │ │ ├── TableNative.tsx
│ │ │ └── useRowSelection.tsx
│ │ ├── TableOfContents
│ │ │ ├── TableOfContents.module.scss
│ │ │ ├── TableOfContents.spec.ts
│ │ │ ├── TableOfContents.tsx
│ │ │ └── TableOfContentsNative.tsx
│ │ ├── Tabs
│ │ │ ├── TabContext.tsx
│ │ │ ├── TabItem.md
│ │ │ ├── TabItem.tsx
│ │ │ ├── TabItemNative.tsx
│ │ │ ├── Tabs.md
│ │ │ ├── Tabs.module.scss
│ │ │ ├── Tabs.spec.ts
│ │ │ ├── Tabs.tsx
│ │ │ └── TabsNative.tsx
│ │ ├── Text
│ │ │ ├── Text.md
│ │ │ ├── Text.module.scss
│ │ │ ├── Text.spec.ts
│ │ │ ├── Text.tsx
│ │ │ └── TextNative.tsx
│ │ ├── TextArea
│ │ │ ├── TextArea.md
│ │ │ ├── TextArea.module.scss
│ │ │ ├── TextArea.spec.ts
│ │ │ ├── TextArea.tsx
│ │ │ ├── TextAreaNative.tsx
│ │ │ ├── TextAreaResizable.tsx
│ │ │ └── useComposedRef.ts
│ │ ├── TextBox
│ │ │ ├── TextBox.md
│ │ │ ├── TextBox.module.scss
│ │ │ ├── TextBox.spec.ts
│ │ │ ├── TextBox.tsx
│ │ │ └── TextBoxNative.tsx
│ │ ├── Theme
│ │ │ ├── NotificationToast.tsx
│ │ │ ├── Theme.md
│ │ │ ├── Theme.module.scss
│ │ │ ├── Theme.spec.ts
│ │ │ ├── Theme.tsx
│ │ │ └── ThemeNative.tsx
│ │ ├── TimeInput
│ │ │ ├── TimeInput.md
│ │ │ ├── TimeInput.module.scss
│ │ │ ├── TimeInput.spec.ts
│ │ │ ├── TimeInput.tsx
│ │ │ ├── TimeInputNative.tsx
│ │ │ └── utils.ts
│ │ ├── Timer
│ │ │ ├── Timer.md
│ │ │ ├── Timer.spec.ts
│ │ │ ├── Timer.tsx
│ │ │ └── TimerNative.tsx
│ │ ├── Toggle
│ │ │ ├── Toggle.module.scss
│ │ │ └── Toggle.tsx
│ │ ├── ToneChangerButton
│ │ │ ├── ToneChangerButton.md
│ │ │ ├── ToneChangerButton.spec.ts
│ │ │ └── ToneChangerButton.tsx
│ │ ├── ToneSwitch
│ │ │ ├── ToneSwitch.md
│ │ │ ├── ToneSwitch.module.scss
│ │ │ ├── ToneSwitch.spec.ts
│ │ │ ├── ToneSwitch.tsx
│ │ │ └── ToneSwitchNative.tsx
│ │ ├── Tooltip
│ │ │ ├── Tooltip.md
│ │ │ ├── Tooltip.module.scss
│ │ │ ├── Tooltip.spec.ts
│ │ │ ├── Tooltip.tsx
│ │ │ └── TooltipNative.tsx
│ │ ├── Tree
│ │ │ ├── testData.ts
│ │ │ ├── Tree-dynamic.spec.ts
│ │ │ ├── Tree-icons.spec.ts
│ │ │ ├── Tree.md
│ │ │ ├── Tree.spec.ts
│ │ │ ├── TreeComponent.module.scss
│ │ │ ├── TreeComponent.tsx
│ │ │ └── TreeNative.tsx
│ │ ├── TreeDisplay
│ │ │ ├── TreeDisplay.md
│ │ │ ├── TreeDisplay.module.scss
│ │ │ ├── TreeDisplay.tsx
│ │ │ └── TreeDisplayNative.tsx
│ │ ├── ValidationSummary
│ │ │ ├── ValidationSummary.module.scss
│ │ │ └── ValidationSummary.tsx
│ │ └── VisuallyHidden.tsx
│ ├── components-core
│ │ ├── abstractions
│ │ │ ├── ComponentRenderer.ts
│ │ │ ├── LoaderRenderer.ts
│ │ │ ├── standalone.ts
│ │ │ └── treeAbstractions.ts
│ │ ├── action
│ │ │ ├── actions.ts
│ │ │ ├── APICall.tsx
│ │ │ ├── FileDownloadAction.tsx
│ │ │ ├── FileUploadAction.tsx
│ │ │ ├── NavigateAction.tsx
│ │ │ └── TimedAction.tsx
│ │ ├── ApiBoundComponent.tsx
│ │ ├── appContext
│ │ │ ├── date-functions.ts
│ │ │ ├── math-function.ts
│ │ │ └── misc-utils.ts
│ │ ├── AppContext.tsx
│ │ ├── behaviors
│ │ │ ├── Behavior.tsx
│ │ │ └── CoreBehaviors.tsx
│ │ ├── component-hooks.ts
│ │ ├── ComponentDecorator.tsx
│ │ ├── ComponentViewer.tsx
│ │ ├── CompoundComponent.tsx
│ │ ├── constants.ts
│ │ ├── DebugViewProvider.tsx
│ │ ├── descriptorHelper.ts
│ │ ├── devtools
│ │ │ ├── InspectorDialog.module.scss
│ │ │ ├── InspectorDialog.tsx
│ │ │ └── InspectorDialogVisibilityContext.tsx
│ │ ├── EngineError.ts
│ │ ├── event-handlers.ts
│ │ ├── InspectorButton.module.scss
│ │ ├── InspectorContext.tsx
│ │ ├── interception
│ │ │ ├── abstractions.ts
│ │ │ ├── ApiInterceptor.ts
│ │ │ ├── ApiInterceptorProvider.tsx
│ │ │ ├── apiInterceptorWorker.ts
│ │ │ ├── Backend.ts
│ │ │ ├── Errors.ts
│ │ │ ├── IndexedDb.ts
│ │ │ ├── initMock.ts
│ │ │ ├── InMemoryDb.ts
│ │ │ ├── ReadonlyCollection.ts
│ │ │ └── useApiInterceptorContext.tsx
│ │ ├── loader
│ │ │ ├── ApiLoader.tsx
│ │ │ ├── DataLoader.tsx
│ │ │ ├── ExternalDataLoader.tsx
│ │ │ ├── Loader.tsx
│ │ │ ├── MockLoaderRenderer.tsx
│ │ │ └── PageableLoader.tsx
│ │ ├── LoaderComponent.tsx
│ │ ├── markup-check.ts
│ │ ├── parts.ts
│ │ ├── renderers.ts
│ │ ├── rendering
│ │ │ ├── AppContent.tsx
│ │ │ ├── AppRoot.tsx
│ │ │ ├── AppWrapper.tsx
│ │ │ ├── buildProxy.ts
│ │ │ ├── collectFnVarDeps.ts
│ │ │ ├── ComponentAdapter.tsx
│ │ │ ├── ComponentWrapper.tsx
│ │ │ ├── Container.tsx
│ │ │ ├── containers.ts
│ │ │ ├── ContainerWrapper.tsx
│ │ │ ├── ErrorBoundary.module.scss
│ │ │ ├── ErrorBoundary.tsx
│ │ │ ├── InvalidComponent.module.scss
│ │ │ ├── InvalidComponent.tsx
│ │ │ ├── nodeUtils.ts
│ │ │ ├── reducer.ts
│ │ │ ├── renderChild.tsx
│ │ │ ├── StandaloneComponent.tsx
│ │ │ ├── StateContainer.tsx
│ │ │ ├── UnknownComponent.module.scss
│ │ │ ├── UnknownComponent.tsx
│ │ │ └── valueExtractor.ts
│ │ ├── reportEngineError.ts
│ │ ├── RestApiProxy.ts
│ │ ├── script-runner
│ │ │ ├── asyncProxy.ts
│ │ │ ├── AttributeValueParser.ts
│ │ │ ├── bannedFunctions.ts
│ │ │ ├── BindingTreeEvaluationContext.ts
│ │ │ ├── eval-tree-async.ts
│ │ │ ├── eval-tree-common.ts
│ │ │ ├── eval-tree-sync.ts
│ │ │ ├── ParameterParser.ts
│ │ │ ├── process-statement-async.ts
│ │ │ ├── process-statement-common.ts
│ │ │ ├── process-statement-sync.ts
│ │ │ ├── ScriptingSourceTree.ts
│ │ │ ├── simplify-expression.ts
│ │ │ ├── statement-queue.ts
│ │ │ └── visitors.ts
│ │ ├── StandaloneApp.tsx
│ │ ├── StandaloneExtensionManager.ts
│ │ ├── TableOfContentsContext.tsx
│ │ ├── theming
│ │ │ ├── _themes.scss
│ │ │ ├── component-layout-resolver.ts
│ │ │ ├── extendThemeUtils.ts
│ │ │ ├── hvar.ts
│ │ │ ├── layout-resolver.ts
│ │ │ ├── parse-layout-props.ts
│ │ │ ├── StyleContext.tsx
│ │ │ ├── StyleRegistry.ts
│ │ │ ├── ThemeContext.tsx
│ │ │ ├── ThemeProvider.tsx
│ │ │ ├── themes
│ │ │ │ ├── base-utils.ts
│ │ │ │ ├── palette.ts
│ │ │ │ ├── root.ts
│ │ │ │ ├── solid.ts
│ │ │ │ ├── theme-colors.ts
│ │ │ │ └── xmlui.ts
│ │ │ ├── themeVars.module.scss
│ │ │ ├── themeVars.ts
│ │ │ ├── transformThemeVars.ts
│ │ │ └── utils.ts
│ │ ├── utils
│ │ │ ├── actionUtils.ts
│ │ │ ├── audio-utils.ts
│ │ │ ├── base64-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
1 | # React Fundamentals
2 |
3 | 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.
4 |
5 | ## React Hooks: Quick Overview
6 |
7 | **What are hooks?** Functions that let you "hook into" React features from function components. They always start with `use` (e.g., `useState`, `useEffect`).
8 |
9 | **Core hooks in XMLUI:**
10 | - **`useState`** - Add local state to components
11 | - **`useEffect`** - Run side effects (data fetching, subscriptions, DOM updates)
12 | - **`useRef`** - Store mutable values that don't trigger re-renders
13 | - **`useMemo`** - Cache expensive calculations
14 | - **`useCallback`** - Cache function definitions
15 | - **`useReducer`** - Manage complex state with reducer pattern
16 | - **`useContext`** - Access context values from providers
17 |
18 | **Two critical rules:**
19 | 1. Only call hooks at the top level (not in loops, conditions, or nested functions)
20 | 2. Only call hooks from React functions (components or custom hooks)
21 |
22 | **Why?** React tracks hooks by call order. Breaking these rules causes state mismatches and bugs.
23 |
24 | ## Component Rendering Lifecycle
25 |
26 | React components re-render whenever their state or props change. Understanding this cycle prevents performance issues and unexpected behavior.
27 |
28 | **The 5-Phase Render Cycle:**
29 |
30 | 1. **Trigger** → Something requests a render:
31 | - Parent component re-renders
32 | - Props change
33 | - State changes (`useState`, `useReducer`)
34 |
35 | 2. **Render** → React calls your component function, which returns JSX
36 |
37 | 3. **Reconciliation** → React's diffing algorithm determines what changed in the virtual DOM
38 |
39 | 4. **Commit** → React updates the actual DOM with minimal changes
40 |
41 | 5. **Effects** → React runs effects in order:
42 | - `useLayoutEffect` (synchronous, blocks paint)
43 | - Browser paints the screen
44 | - `useEffect` (asynchronous, after paint)
45 |
46 | **Key insight:** Re-rendering is cheap (just a function call), but DOM updates are expensive. React optimizes by batching and minimizing DOM changes.
47 |
48 | **Common performance pitfalls:**
49 | ```tsx
50 | // ❌ WRONG - Parent re-renders cause all children to re-render
51 | function Parent() {
52 | const [count, setCount] = useState(0);
53 | return (
54 | <div>
55 | <ExpensiveChild data={data} /> {/* Re-renders unnecessarily */}
56 | <button onClick={() => setCount(c => c + 1)}>Update</button>
57 | </div>
58 | );
59 | }
60 |
61 | // ✅ CORRECT - Memoize to prevent unnecessary re-renders
62 | const MemoizedChild = React.memo(ExpensiveChild);
63 |
64 | function Parent() {
65 | const [count, setCount] = useState(0);
66 | const data = useMemo(() => computeData(), []); // Stable reference
67 | return (
68 | <div>
69 | <MemoizedChild data={data} /> {/* Only re-renders when data changes */}
70 | <button onClick={() => setCount(c => c + 1)}>Update</button>
71 | </div>
72 | );
73 | }
74 | ```
75 |
76 | ## Rules of Hooks
77 |
78 | These rules are enforced by React and ESLint. Violating them causes hard-to-debug errors.
79 |
80 | **Rule 1: Call hooks at the top level**
81 | ```tsx
82 | // ❌ WRONG - Conditional hook
83 | function Bad({ show }: Props) {
84 | if (show) {
85 | const [value, setValue] = useState(""); // Hook order changes!
86 | }
87 | return <div>...</div>;
88 | }
89 |
90 | // ✅ CORRECT - Hook always called
91 | function Good({ show }: Props) {
92 | const [value, setValue] = useState("");
93 | if (!show) return null;
94 | return <div>{value}</div>;
95 | }
96 | ```
97 |
98 | **Rule 2: Only call from React functions**
99 | ```tsx
100 | // ❌ WRONG - Hook in regular function
101 | function getUser() {
102 | const [user, setUser] = useState(null); // Not allowed!
103 | return user;
104 | }
105 |
106 | // ✅ CORRECT - Hook in component or custom hook
107 | function useUser() {
108 | const [user, setUser] = useState(null);
109 | return user;
110 | }
111 |
112 | function Component() {
113 | const user = useUser(); // OK
114 | return <div>{user?.name}</div>;
115 | }
116 | ```
117 |
118 | **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.
119 |
120 | ---
121 |
122 | ## React State Management Patterns
123 |
124 | Fundamental patterns for managing state in React, from local component state to shared state across component trees.
125 |
126 | ### `useState` - Local State
127 |
128 | **Syntax:** `const [state, setState] = useState(initialValue)`
129 |
130 | ```tsx
131 | // Basic
132 | const [count, setCount] = useState(0);
133 |
134 | // Functional update (when new state depends on old)
135 | setCount(prev => prev + 1);
136 |
137 | // Lazy initialization (expensive initial state)
138 | const [data, setData] = useState(() => expensiveComputation());
139 |
140 | // Immutable updates
141 | setUser(prev => ({ ...prev, name }));
142 | setItems(prev => [...prev, newItem]);
143 | ```
144 |
145 | **Use when:** State is local, updates are simple, no complex transitions.
146 | **Consider alternatives:** Multiple components → Context, complex logic → useReducer.
147 |
148 | ---
149 |
150 | ### `useReducer` - Complex State Logic
151 |
152 | **Syntax:** `const [state, dispatch] = useReducer(reducer, initialState)`
153 |
154 | ```tsx
155 | type Action = { type: 'increment' } | { type: 'decrement' } | { type: 'reset' };
156 |
157 | function reducer(state: State, action: Action): State {
158 | switch (action.type) {
159 | case 'increment': return { count: state.count + 1 };
160 | case 'decrement': return { count: state.count - 1 };
161 | case 'reset': return { count: 0 };
162 | default: return state;
163 | }
164 | }
165 |
166 | const [state, dispatch] = useReducer(reducer, { count: 0 });
167 | dispatch({ type: 'increment' });
168 | ```
169 |
170 | **With Immer (XMLUI pattern):**
171 | ```tsx
172 | import produce from 'immer';
173 |
174 | const reducer = produce((draft, action) => {
175 | switch (action.type) {
176 | case 'ADD_TODO':
177 | draft.todos.push(action.payload); // Direct mutation
178 | break;
179 | }
180 | });
181 | ```
182 |
183 | **Use when:** Multiple related state values, complex transitions, want to separate state logic.
184 | **Use useState when:** Simple independent values, straightforward updates.
185 |
186 | ---
187 |
188 | ### Context API - Avoid Prop Drilling
189 |
190 | **Purpose:** Share state across component tree without passing props through every level.
191 |
192 | **Pattern:** Create context → Provider component → Custom hook → Consume
193 |
194 | ```tsx
195 | // 1. Create context
196 | const AuthContext = createContext<AuthContext | null>(null);
197 |
198 | // 2. Custom hook with validation
199 | function useAuth() {
200 | const context = useContext(AuthContext);
201 | if (!context) throw new Error('useAuth must be used within AuthProvider');
202 | return context;
203 | }
204 |
205 | // 3. Provider component
206 | function AuthProvider({ children }: Props) {
207 | const [user, setUser] = useState<User | null>(null);
208 |
209 | const value = useMemo(() => ({
210 | user,
211 | login: async (creds: Credentials) => { /* ... */ },
212 | logout: () => setUser(null),
213 | }), [user]);
214 |
215 | return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
216 | }
217 |
218 | // 4. Consume anywhere in tree
219 | function Component() {
220 | const { user, logout } = useAuth(); // No prop drilling!
221 | return <button onClick={logout}>{user.name}</button>;
222 | }
223 | ```
224 |
225 | **XMLUI Example:**
226 | ```tsx
227 | // AppLayoutContext - Provides layout state to nested navigation
228 | const AppLayoutContext = createContext<IAppLayoutContext | null>(null);
229 |
230 | export const App = forwardRef(function App(props, ref) {
231 | const layoutContextValue = useMemo(() => ({
232 | layout,
233 | navPanelVisible,
234 | toggleDrawer,
235 | }), [layout, navPanelVisible, toggleDrawer]);
236 |
237 | return (
238 | <AppLayoutContext.Provider value={layoutContextValue}>
239 | {content}
240 | </AppLayoutContext.Provider>
241 | );
242 | });
243 |
244 | // NavLink accesses layout directly
245 | export const NavLink = forwardRef(function NavLink(props, ref) {
246 | const { layout } = useAppLayoutContext();
247 | // No prop drilling!
248 | });
249 | ```
250 |
251 | **Use when:** Many nested components need access, >3 levels of prop drilling, global state (theme, auth).
252 | **Don't use when:** 1-2 levels of nesting (props fine), high-frequency updates (re-renders all consumers).
253 |
254 | **Performance tip:** Split contexts by update frequency - separate user/theme/settings contexts instead of one combined context.
255 |
256 | ---
257 |
258 | ### State Lifting
259 |
260 | **Pattern:** Move state to common ancestor to share between siblings.
261 |
262 | ```tsx
263 | // ❌ WRONG - Siblings can't communicate
264 | <Parent><InputA /><InputB /></Parent>
265 |
266 | // ✅ CORRECT - Lift state to parent
267 | function Parent() {
268 | const [value, setValue] = useState('');
269 | return (
270 | <>
271 | <InputA value={value} onChange={setValue} />
272 | <InputB value={value} />
273 | </>
274 | );
275 | }
276 | ```
277 |
278 | **XMLUI Pattern:** Container-based state flows down automatically:
279 | ```tsx
280 | <Stack var.selectedId="{null}">
281 | <Button onClick={"{() => selectedId = 'item1'}" />
282 | <Display value="{selectedId}" />
283 | </Stack>
284 | ```
285 |
286 | **Use when:** Siblings need to coordinate, parent orchestrates behavior.
287 | **Don't use when:** State only used by one component, excessive prop drilling (use context).
288 |
289 | ---
290 |
291 | ### Controlled vs Uncontrolled Components
292 |
293 | **Controlled:** Parent manages state via `value` prop.
294 | ```tsx
295 | function Controlled({ value, onChange }: Props) {
296 | return <input value={value} onChange={e => onChange(e.target.value)} />;
297 | }
298 | ```
299 |
300 | **Uncontrolled:** Component manages own state via `initialValue`.
301 | ```tsx
302 | function Uncontrolled({ initialValue = '', onDidChange }: Props) {
303 | const [value, setValue] = useState(initialValue);
304 | return <input value={value} onChange={e => { setValue(e.target.value); onDidChange?.(e.target.value); }} />;
305 | }
306 | ```
307 |
308 | **Hybrid (XMLUI pattern):** Support both modes.
309 | ```tsx
310 | function Flexible({ value, initialValue = '', onDidChange }: Props) {
311 | const [localValue, setLocalValue] = useState(initialValue);
312 |
313 | useEffect(() => {
314 | if (value !== undefined) setLocalValue(value);
315 | }, [value]);
316 |
317 | const handleChange = (e) => {
318 | setLocalValue(e.target.value);
319 | onDidChange?.(e.target.value);
320 | };
321 |
322 | return <input value={localValue} onChange={handleChange} />;
323 | }
324 | ```
325 |
326 | **Use controlled:** Validate/format input, value affects other UI, programmatic changes.
327 | **Use uncontrolled:** Simple forms (read on submit), performance critical.
328 | **Use hybrid:** Reusable component libraries.
329 |
330 | ---
331 |
332 | ### Compound Components
333 |
334 | **Purpose:** Components work together as cohesive unit, sharing state via context.
335 |
336 | ```tsx
337 | const TabsContext = createContext<{
338 | activeTab: string;
339 | setActiveTab: (id: string) => void;
340 | } | null>(null);
341 |
342 | function Tabs({ children }: Props) {
343 | const [activeTab, setActiveTab] = useState('tab1');
344 | return (
345 | <TabsContext.Provider value={{ activeTab, setActiveTab }}>
346 | <div className="tabs">{children}</div>
347 | </TabsContext.Provider>
348 | );
349 | }
350 |
351 | function Tab({ id, children }: Props) {
352 | const { activeTab, setActiveTab } = useContext(TabsContext)!;
353 | return (
354 | <button
355 | className={activeTab === id ? 'active' : ''}
356 | onClick={() => setActiveTab(id)}
357 | >
358 | {children}
359 | </button>
360 | );
361 | }
362 |
363 | Tabs.Tab = Tab;
364 | Tabs.Panel = TabPanel;
365 |
366 | // Usage - Flexible composition
367 | <Tabs>
368 | <Tabs.Tab id="tab1">First</Tabs.Tab>
369 | <Tabs.Tab id="tab2">Second</Tabs.Tab>
370 | <Tabs.Panel id="tab1">Content 1</Tabs.Panel>
371 | <Tabs.Panel id="tab2">Content 2</Tabs.Panel>
372 | </Tabs>
373 | ```
374 |
375 | **Use when:** Tightly coupled components (tabs, accordion), need flexible composition, building libraries.
376 | **Don't use when:** Simple parent-child, no shared state, prop drilling is simple.
377 |
378 | ---
379 |
380 | ### Advanced Patterns
381 |
382 | **Provider Composition:**
383 | ```tsx
384 | function AppProviders({ children }: Props) {
385 | return (
386 | <ThemeProvider>
387 | <AuthProvider>
388 | <RouterProvider>
389 | {children}
390 | </RouterProvider>
391 | </AuthProvider>
392 | </ThemeProvider>
393 | );
394 | }
395 | ```
396 |
397 | **Async Initialization:**
398 | ```tsx
399 | function AuthProvider({ children }: Props) {
400 | const [user, setUser] = useState(null);
401 | const [loading, setLoading] = useState(true);
402 |
403 | useEffect(() => {
404 | checkAuth().then(user => { setUser(user); setLoading(false); });
405 | }, []);
406 |
407 | if (loading) return <LoadingScreen />;
408 | return <AuthContext.Provider value={{ user }}>{children}</AuthContext.Provider>;
409 | }
410 | ```
411 |
412 | **Reducer with Immer (XMLUI):**
413 | ```tsx
414 | // StateContainer reducer
415 | export function createContainerReducer(debugView: IDebugViewContext) {
416 | return produce((state: ContainerState, action: ContainerAction) => {
417 | switch (action.type) {
418 | case ContainerActionKind.COMPONENT_STATE_CHANGED:
419 | state[uid] = { ...state[uid], ...action.payload.state };
420 | break;
421 | }
422 | });
423 | }
424 | ```
425 |
426 | ---
427 |
428 | ## React Performance Optimization Patterns
429 |
430 | This section covers React's performance optimization tools and patterns. **Always profile before optimizing**—premature optimization adds complexity without real benefits.
431 |
432 | ### Core Optimization Hooks
433 |
434 | #### `useMemo` - Computation Caching
435 |
436 | Cache expensive calculations between renders.
437 |
438 | ```tsx
439 | const filtered = useMemo(() =>
440 | items.filter(item => item.includes(filter)),
441 | [items, filter]
442 | );
443 | ```
444 |
445 | **Use when:** Computation is expensive (>10ms), creating objects/arrays for memoized children, or calculations in dependency arrays.
446 | **Don't use when:** Computation is cheap (<1ms), result used only once, or component rarely re-renders.
447 |
448 | #### `useCallback` - Function Caching
449 |
450 | Cache function definitions to prevent child re-renders.
451 |
452 | ```tsx
453 | const handleClick = useCallback(() => {
454 | doSomething(value);
455 | }, [value]);
456 | ```
457 |
458 | **Use when:** Passing callbacks to memoized children or to dependency arrays.
459 | **Don't use when:** Function isn't passed to memoized components or deps arrays.
460 |
461 | **Note:** `useCallback(fn, deps)` is equivalent to `useMemo(() => fn, deps)`.
462 |
463 | #### `useTransition` - Non-Urgent Updates
464 |
465 | Mark state updates as low-priority to keep UI responsive.
466 |
467 | ```tsx
468 | const [isPending, startTransition] = useTransition();
469 |
470 | startTransition(() => {
471 | setExpensiveState(newValue); // Won't block UI
472 | });
473 | ```
474 |
475 | **Use when:** Updating expensive state that doesn't need immediate feedback (filtering large lists, complex calculations).
476 |
477 | #### `memo` - Component Memoization
478 |
479 | Prevent re-renders when props haven't changed.
480 |
481 | ```tsx
482 | const MemoChild = memo(function MemoChild({ data }: Props) {
483 | return <div>{data.value}</div>;
484 | });
485 | ```
486 |
487 | **Use when:** Component renders frequently with same props, has expensive rendering, or is in large lists.
488 | **Don't use when:** Component rarely re-renders, props change every render, or rendering is cheap.
489 |
490 | **Important:** `memo` only works if props are stable. Use `useMemo`/`useCallback` for object/function props.
491 |
492 | ---
493 |
494 | ### Memoization Strategy Pattern
495 |
496 | **Principle:** `memo` + `useMemo` + `useCallback` work together. `memo` prevents re-renders, but only if props stay stable. Use `useMemo`/`useCallback` to keep props stable.
497 |
498 | #### The Memoization Cascade
499 |
500 | ```tsx
501 | // ❌ ANTI-PATTERN - memo without stable props
502 | const Child = memo(({ data, onClick }: Props) => <div onClick={onClick}>{data.value}</div>);
503 |
504 | function Parent() {
505 | return <Child data={{ value: 123 }} onClick={() => {}} />; // New refs every render!
506 | }
507 |
508 | // ✅ CORRECT - memo with stable props
509 | function Parent() {
510 | const data = useMemo(() => ({ value: 123 }), []);
511 | const onClick = useCallback(() => console.log('clicked'), []);
512 | return <Child data={data} onClick={onClick} />;
513 | }
514 | ```
515 |
516 | #### Decision Tree
517 |
518 | 1. **Performance problem?** No → Don't optimize. Yes → Step 2.
519 | 2. **What's the cause?**
520 | - Parent re-renders often → Use `memo()` on child
521 | - Expensive computation → Use `useMemo()` on calculation
522 | - New function props → Use `useCallback()` on function
523 | 3. **Passing objects/arrays/functions to memoized component?** Yes → Memoize those too.
524 |
525 | #### Common Patterns
526 |
527 | ```tsx
528 | // Pattern 1: Context values
529 | const contextValue = useMemo(() => ({
530 | state,
531 | setState,
532 | isLoading: state.status === 'loading',
533 | }), [state]);
534 |
535 | // Pattern 2: Event handlers with deps
536 | const handleSearch = useCallback(() => {
537 | if (query.length >= minLength) onSearch(query);
538 | }, [query, minLength, onSearch]);
539 |
540 | // Pattern 3: Expensive selectors
541 | const filteredData = useMemo(() => {
542 | return data.filter(item => item.name.includes(filter)).sort((a, b) => a.name.localeCompare(b.name));
543 | }, [data, filter]);
544 |
545 | // Pattern 4: Derived state
546 | const summary = useMemo(() => ({
547 | total: items.reduce((sum, item) => sum + item.price * item.quantity, 0),
548 | itemCount: items.reduce((sum, item) => sum + item.quantity, 0),
549 | }), [items]);
550 | ```
551 |
552 | #### Anti-Patterns
553 |
554 | ```tsx
555 | // ❌ Over-memoization
556 | const greeting = useMemo(() => `Hello, ${name}`, [name]); // Too simple!
557 |
558 | // ❌ Incomplete chain
559 | <MemoChild config={{ theme: 'dark' }} />; // New object defeats memo
560 |
561 | // ❌ Unstable dependencies
562 | useMemo(() => formatUser(user), [user]); // user object recreated every render
563 | // ✅ Fix: useMemo(() => formatUser(user), [user.id, user.name]);
564 | ```
565 |
566 | #### Checklist
567 |
568 | **✅ DO memoize:**
569 | - Components rendering frequently with same props
570 | - Expensive computations (>10ms)
571 | - Objects/arrays/functions passed to memoized children
572 | - Context values
573 |
574 | **❌ DON'T memoize:**
575 | - Cheap operations (<1ms)
576 | - Values that change every render
577 | - Without profiling first
578 |
579 | ---
580 |
581 | ### Virtualization Pattern
582 |
583 | **Purpose:** Render only visible items in large lists by using "windowing." Instead of rendering 10,000 items, render only ~10 visible items.
584 |
585 | **Libraries:** XMLUI uses two based on component needs:
586 | - **virtua** (Tree, List) - Chat interfaces, reverse scrolling, auto-sizing, fixed-size lists
587 | - **@tanstack/react-virtual** (Table) - Dynamic measurements, flexible
588 |
589 | **Library Comparison:**
590 |
591 | | Feature | virtua | @tanstack/react-virtual |
592 | |---------|--------|------------------------|
593 | | **Bundle Size** | ~6KB | ~4KB |
594 | | **API** | Render props | Hooks |
595 | | **Dynamic heights** | Automatic | Automatic |
596 | | **Reverse scroll** | ✅ Built-in | Manual |
597 | | **Auto-sizing** | ✅ Built-in | Manual |
598 | | **XMLUI Usage** | Tree, List | Table |
599 |
600 | **virtua Example (XMLUI List):**
601 |
602 | ```tsx
603 | import { Virtualizer } from 'virtua';
604 |
605 | function ChatList({ messages }: Props) {
606 | return (
607 | <Virtualizer count={messages.length}>
608 | {(index) => {
609 | const msg = messages[index];
610 | return (
611 | <div key={msg.id}>
612 | <div>{msg.author}</div>
613 | <div>{msg.content}</div>
614 | </div>
615 | );
616 | }}
617 | </Virtualizer>
618 | );
619 | }
620 | ```
621 |
622 | **@tanstack/react-virtual Example:**
623 |
624 | ```tsx
625 | import { useVirtualizer } from '@tanstack/react-virtual';
626 |
627 | function DataTable({ rows }: Props) {
628 | const tableRef = useRef<HTMLDivElement>(null);
629 |
630 | const rowVirtualizer = useVirtualizer({
631 | count: rows.length,
632 | getScrollElement: () => tableRef.current,
633 | estimateSize: () => 30,
634 | overscan: 5,
635 | });
636 |
637 | return (
638 | <div ref={tableRef} style={{ height: '400px', overflow: 'auto' }}>
639 | {rowVirtualizer.getVirtualItems().map((virtualRow) => (
640 | <div
641 | key={virtualRow.index}
642 | ref={(el) => rowVirtualizer.measureElement(el)}
643 | style={{ transform: `translateY(${virtualRow.start}px)` }}
644 | >
645 | {rows[virtualRow.index].content}
646 | </div>
647 | ))}
648 | </div>
649 | );
650 | }
651 | ```
652 |
653 | **Critical Rules:**
654 | 1. **Memoize row components** - Use `React.memo()`
655 | 2. **Apply transform/style** - Required for positioning (@tanstack)
656 | 3. **Memoize data** - Prevent row re-renders
657 | 4. **Handle scroll container** - Each library handles sizing differently
658 |
659 | **Performance Impact:**
660 |
661 | | Items | Normal | Virtualized | Improvement |
662 | |-------|--------|-------------|-------------|
663 | | 100 | 50ms | 10ms | 5x faster |
664 | | 1,000 | 500ms | 10ms | 50x faster |
665 | | 10,000 | 5s | 10ms | 500x faster |
666 |
667 | **When to Use:**
668 | - ✅ >100 items
669 | - ✅ Uniform item sizes
670 | - ✅ Scrollable datasets
671 | - ❌ <100 items (overhead not worth it)
672 | - ❌ Already paginated
673 | - ❌ Complex/unpredictable heights
674 |
675 | ---
676 |
677 | ### Rate Limiting: Debouncing and Throttling
678 |
679 | **Purpose:** Control the frequency of expensive operations during high-frequency events (user input, scrolling, resizing).
680 |
681 | **Key Difference:**
682 | - **Debouncing**: Wait until activity **stops** (search, autosave)
683 | - **Throttling**: Execute at **regular intervals** during activity (scroll, mousemove)
684 |
685 | ```tsx
686 | // User types "search" continuously
687 |
688 | // DEBOUNCING: Executes ONCE after user stops typing
689 | // Timeline: [type...type...type...STOP] → Execute
690 |
691 | // THROTTLING: Executes EVERY 200ms while typing
692 | // Timeline: Execute → [200ms] → Execute → [200ms] → Execute...
693 | ```
694 |
695 | #### Debouncing Solutions
696 |
697 | **1. useDeferredValue (React 18+) - Recommended**
698 |
699 | ```tsx
700 | function Search() {
701 | const [query, setQuery] = useState('');
702 | const deferredQuery = useDeferredValue(query);
703 |
704 | const results = useMemo(() => {
705 | if (deferredQuery.length < 2) return [];
706 | return performSearch(deferredQuery);
707 | }, [deferredQuery]);
708 |
709 | return (
710 | <>
711 | <input value={query} onChange={e => setQuery(e.target.value)} />
712 | <ResultsList results={results} />
713 | </>
714 | );
715 | }
716 | ```
717 |
718 | **2. Custom useDebounce Hook**
719 |
720 | ```tsx
721 | function useDebounce<T>(value: T, delay: number = 500): T {
722 | const [debouncedValue, setDebouncedValue] = useState<T>(value);
723 |
724 | useEffect(() => {
725 | const timer = setTimeout(() => setDebouncedValue(value), delay);
726 | return () => clearTimeout(timer);
727 | }, [value, delay]);
728 |
729 | return debouncedValue;
730 | }
731 |
732 | // Usage
733 | const debouncedQuery = useDebounce(query, 300);
734 | ```
735 |
736 | **3. Lodash debounce**
737 |
738 | ```tsx
739 | const debouncedSave = useMemo(
740 | () => debounce((text: string) => saveToServer(text), 1000),
741 | []
742 | );
743 |
744 | useEffect(() => {
745 | return () => debouncedSave.cancel();
746 | }, [debouncedSave]);
747 | ```
748 |
749 | #### Throttling Solutions
750 |
751 | **1. Custom useThrottle Hook**
752 |
753 | ```tsx
754 | function useThrottle<T extends (...args: any[]) => any>(
755 | callback: T,
756 | delay: number = 200
757 | ): T {
758 | const lastRun = useRef(Date.now());
759 |
760 | return useCallback((...args: Parameters<T>) => {
761 | const now = Date.now();
762 | if (now - lastRun.current >= delay) {
763 | lastRun.current = now;
764 | return callback(...args);
765 | }
766 | }, [callback, delay]) as T;
767 | }
768 |
769 | // Usage
770 | const handleScroll = useThrottle(() => {
771 | setScrollPos(window.scrollY);
772 | }, 200);
773 | ```
774 |
775 | **2. Lodash throttle**
776 |
777 | ```tsx
778 | const throttledScroll = useMemo(
779 | () => throttle(() => {
780 | updateScrollPosition();
781 | }, 200, {
782 | leading: true, // Execute on first call
783 | trailing: true // Execute after interval ends
784 | }),
785 | []
786 | );
787 |
788 | useEffect(() => {
789 | return () => throttledScroll.cancel();
790 | }, [throttledScroll]);
791 | ```
792 |
793 | #### XMLUI Examples
794 |
795 | **Debounced Search (Search.tsx):**
796 | ```tsx
797 | function Search({ data, limit }: Props) {
798 | const [inputValue, setInputValue] = useState("");
799 | const debouncedValue = useDeferredValue(inputValue);
800 |
801 | const results = useMemo(() => {
802 | if (debouncedValue.length <= 1) return [];
803 | return fuse.search(debouncedValue, { limit });
804 | }, [debouncedValue, limit]);
805 |
806 | return (
807 | <>
808 | <input value={inputValue} onChange={e => setInputValue(e.target.value)} />
809 | <SearchResults results={results} />
810 | </>
811 | );
812 | }
813 | ```
814 |
815 | **Throttled Change Listener (ChangeListenerNative.tsx):**
816 | ```tsx
817 | function ChangeListener({ listenTo, onChange, throttleWaitInMs = 0 }: Props) {
818 | const throttledOnChange = useMemo(() => {
819 | if (throttleWaitInMs !== 0 && onChange) {
820 | return throttle(onChange, throttleWaitInMs, { leading: true });
821 | }
822 | return onChange;
823 | }, [onChange, throttleWaitInMs]);
824 |
825 | useEffect(() => {
826 | if (throttledOnChange) {
827 | throttledOnChange({ prevValue, newValue: listenTo });
828 | }
829 | }, [listenTo, throttledOnChange]);
830 | }
831 | ```
832 |
833 | **Async Throttle for Validation (misc.ts):**
834 | ```tsx
835 | function asyncThrottle<F extends (...args: any[]) => Promise<any>>(
836 | func: F,
837 | wait?: number,
838 | options?: ThrottleSettings
839 | ) {
840 | const throttled = throttle(
841 | (resolve, reject, args: Parameters<F>) => {
842 | void func(...args).then(resolve).catch(reject);
843 | },
844 | wait,
845 | options
846 | );
847 |
848 | return (...args: Parameters<F>): ReturnType<F> =>
849 | new Promise((resolve, reject) => {
850 | throttled(resolve, reject, args);
851 | }) as ReturnType<F>;
852 | }
853 | ```
854 |
855 | #### Decision Guide
856 |
857 | | Scenario | Solution | Timing |
858 | |----------|----------|--------|
859 | | Search input | Debounce | 300ms |
860 | | Form validation | Debounce | 500ms |
861 | | Autosave | Debounce | 1000ms |
862 | | Scroll position | Throttle | 100-200ms |
863 | | Window resize | Throttle | 200-300ms |
864 | | Mouse tracking | Throttle | 50-100ms |
865 | | API rate limiting | Throttle | 500-1000ms |
866 |
867 | #### Performance Impact
868 |
869 | | Operation | Without | With (300ms) | Improvement |
870 | |-----------|---------|--------------|-------------|
871 | | Search (6 chars typed) | 6 API calls | 1 API call | 83% reduction |
872 | | Scroll (1s) | ~60 events | 5 events | 92% reduction |
873 | | Window resize | ~30 events | 5 events | 83% reduction |
874 |
875 | #### Critical Rules
876 |
877 | **1. Always memoize** rate-limited functions:
878 | ```tsx
879 | // ❌ WRONG - Creates new function every render
880 | const handle = debounce(() => search(), 500);
881 |
882 | // ✅ CORRECT - Memoized
883 | const handle = useMemo(() => debounce(() => search(), 500), []);
884 | ```
885 |
886 | **2. Always cleanup**:
887 | ```tsx
888 | useEffect(() => {
889 | return () => debouncedFn.cancel();
890 | }, [debouncedFn]);
891 | ```
892 |
893 | **3. Don't rate-limit UI state** - only side effects:
894 | ```tsx
895 | // ❌ WRONG - UI lags
896 | const handleChange = debounce((e) => setValue(e.target.value), 300);
897 |
898 | // ✅ CORRECT - Immediate UI, debounced side effect
899 | const handleChange = (e) => {
900 | setValue(e.target.value); // Instant
901 | debouncedSearch(e.target.value); // Delayed
902 | };
903 | ```
904 |
905 | **4. Choose appropriate delays**:
906 | - Search: 300ms
907 | - Autosave: 1000ms
908 | - Scroll/resize: 100-200ms
909 | - Mousemove: 50ms
910 |
911 | #### When to Use
912 |
913 | **Debouncing:**
914 | - ✅ Search, autosave, validation
915 | - ✅ Wait for user to finish action
916 | - ❌ Don't use for immediate feedback
917 |
918 | **Throttling:**
919 | - ✅ Scroll, resize, mousemove
920 | - ✅ Execute during continuous activity
921 | - ❌ Don't use when only final value matters
922 |
923 | #### Resources
924 |
925 | - [useDeferredValue](https://react.dev/reference/react/useDeferredValue) - React 18 docs
926 | - [lodash.debounce](https://lodash.com/docs/#debounce) - Debounce docs
927 | - [lodash.throttle](https://lodash.com/docs/#throttle) - Throttle docs
928 | - [XMLUI Search](packages/xmlui-search/src/Search.tsx) - Production example
929 |
930 | ---
931 |
932 | ## React Event Handling Patterns
933 |
934 | Patterns for handling user interactions efficiently and correctly in React applications.
935 |
936 | ### Event Delegation Pattern
937 |
938 | **Purpose:** Handle events for multiple children at parent level instead of attaching handlers to each child.
939 |
940 | **Benefits:**
941 | - Fewer event listeners (better memory usage)
942 | - Works with dynamically added/removed children
943 | - Simplifies event handler management
944 |
945 | ```tsx
946 | // ❌ ANTI-PATTERN - Handler on every item
947 | function List({ items }: Props) {
948 | return (
949 | <ul>
950 | {items.map(item => (
951 | <li key={item.id} onClick={() => handleClick(item.id)}>
952 | {item.name}
953 | </li>
954 | ))}
955 | </ul>
956 | );
957 | }
958 |
959 | // ✅ CORRECT - Single handler on parent
960 | function List({ items }: Props) {
961 | const handleClick = (e: React.MouseEvent<HTMLUListElement>) => {
962 | const target = e.target as HTMLElement;
963 | const li = target.closest('li');
964 | if (li) {
965 | const itemId = li.dataset.id;
966 | console.log('Clicked item:', itemId);
967 | }
968 | };
969 |
970 | return (
971 | <ul onClick={handleClick}>
972 | {items.map(item => (
973 | <li key={item.id} data-id={item.id}>
974 | {item.name}
975 | </li>
976 | ))}
977 | </ul>
978 | );
979 | }
980 | ```
981 |
982 | **XMLUI Example (Tree component):**
983 | ```tsx
984 | function Tree({ items }: Props) {
985 | // Single click handler for entire tree
986 | const handleTreeClick = useCallback((e: React.MouseEvent) => {
987 | const target = e.target as HTMLElement;
988 | const treeItem = target.closest('[data-tree-item]');
989 |
990 | if (treeItem) {
991 | const itemId = treeItem.getAttribute('data-item-id');
992 | const action = target.getAttribute('data-action');
993 |
994 | if (action === 'expand') {
995 | toggleExpand(itemId);
996 | } else if (action === 'select') {
997 | selectItem(itemId);
998 | }
999 | }
1000 | }, [toggleExpand, selectItem]);
1001 |
1002 | return (
1003 | <div className="tree" onClick={handleTreeClick}>
1004 | {renderTreeItems(items)}
1005 | </div>
1006 | );
1007 | }
1008 | ```
1009 |
1010 | **When to use:**
1011 | - Lists with many items (>50)
1012 | - Dynamic children (added/removed frequently)
1013 | - Multiple event types on same children
1014 | - Performance-critical rendering
1015 |
1016 | **When NOT to use:**
1017 | - Few items (<10) - overhead not worth it
1018 | - Need precise event target info
1019 | - Event handler logic is complex per-item
1020 |
1021 | ---
1022 |
1023 | ### Synthetic Event Pattern
1024 |
1025 | **Purpose:** React wraps native browser events in `SyntheticEvent` for cross-browser consistency.
1026 |
1027 | **Key differences from native events:**
1028 |
1029 | | Feature | Native Event | Synthetic Event |
1030 | |---------|-------------|-----------------|
1031 | | **Type** | Browser-specific | Unified React type |
1032 | | **Pooling (React 16)** | No | Yes (reused) |
1033 | | **Pooling (React 17+)** | No | No (deprecated) |
1034 | | **Properties** | Browser-specific | Normalized |
1035 | | **Access after handler** | ✅ Always available | ⚠️ Nullified (React 16 only) |
1036 |
1037 | **Basic usage:**
1038 | ```tsx
1039 | function Input() {
1040 | const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
1041 | // e is SyntheticEvent, not native Event
1042 | console.log(e.target.value); // ✅ Works
1043 | console.log(e.currentTarget); // ✅ Works
1044 |
1045 | // Access native event if needed
1046 | const nativeEvent = e.nativeEvent;
1047 | };
1048 |
1049 | return <input onChange={handleChange} />;
1050 | }
1051 | ```
1052 |
1053 | **Event pooling (React 16 only):**
1054 | ```tsx
1055 | // ❌ WRONG - Async access (React 16)
1056 | function Bad() {
1057 | const handleClick = (e: React.MouseEvent) => {
1058 | setTimeout(() => {
1059 | console.log(e.target); // null in React 16!
1060 | }, 1000);
1061 | };
1062 | return <button onClick={handleClick}>Click</button>;
1063 | }
1064 |
1065 | // ✅ CORRECT - Persist event (React 16)
1066 | function Good() {
1067 | const handleClick = (e: React.MouseEvent) => {
1068 | e.persist(); // Keep event alive
1069 | setTimeout(() => {
1070 | console.log(e.target); // ✅ Works
1071 | }, 1000);
1072 | };
1073 | return <button onClick={handleClick}>Click</button>;
1074 | }
1075 |
1076 | // ✅ BETTER - Extract values (React 16 & 17+)
1077 | function Better() {
1078 | const handleClick = (e: React.MouseEvent) => {
1079 | const target = e.target; // Capture immediately
1080 | setTimeout(() => {
1081 | console.log(target); // ✅ Works in all versions
1082 | }, 1000);
1083 | };
1084 | return <button onClick={handleClick}>Click</button>;
1085 | }
1086 | ```
1087 |
1088 | **Note:** React 17+ removed event pooling, so `e.persist()` is no longer needed.
1089 |
1090 | **Common event types:**
1091 | ```tsx
1092 | // Mouse events
1093 | const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {};
1094 | const handleDoubleClick = (e: React.MouseEvent) => {};
1095 |
1096 | // Keyboard events
1097 | const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
1098 | if (e.key === 'Enter') submitForm();
1099 | };
1100 |
1101 | // Form events
1102 | const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {};
1103 | const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
1104 | e.preventDefault();
1105 | };
1106 |
1107 | // Focus events
1108 | const handleFocus = (e: React.FocusEvent<HTMLInputElement>) => {};
1109 | const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {};
1110 |
1111 | // Clipboard events
1112 | const handleCopy = (e: React.ClipboardEvent) => {};
1113 | const handlePaste = (e: React.ClipboardEvent) => {
1114 | const text = e.clipboardData.getData('text');
1115 | };
1116 |
1117 | // Drag events
1118 | const handleDragStart = (e: React.DragEvent) => {};
1119 | const handleDrop = (e: React.DragEvent) => {
1120 | e.preventDefault();
1121 | const data = e.dataTransfer.getData('text');
1122 | };
1123 | ```
1124 |
1125 | **Accessing native event:**
1126 | ```tsx
1127 | function Component() {
1128 | const handleClick = (e: React.MouseEvent) => {
1129 | // React synthetic event
1130 | console.log(e.currentTarget); // Element handler is attached to
1131 | console.log(e.target); // Element that triggered event
1132 |
1133 | // Native browser event
1134 | const nativeEvent = e.nativeEvent;
1135 | console.log(nativeEvent); // MouseEvent object
1136 | };
1137 |
1138 | return <button onClick={handleClick}>Click</button>;
1139 | }
1140 | ```
1141 |
1142 | ---
1143 |
1144 | ### Event Callback Composition Pattern
1145 |
1146 | **Purpose:** Combine multiple event handlers into a single handler, useful for library components that accept user callbacks.
1147 |
1148 | **Pattern 1: Sequential execution**
1149 | ```tsx
1150 | function composeHandlers<E>(...handlers: Array<((e: E) => void) | undefined>) {
1151 | return (event: E) => {
1152 | handlers.forEach(handler => {
1153 | if (handler) {
1154 | handler(event);
1155 | }
1156 | });
1157 | };
1158 | }
1159 |
1160 | // Usage
1161 | function Button({ onClick, onClickInternal }: Props) {
1162 | const handleClick = composeHandlers(onClickInternal, onClick);
1163 | return <button onClick={handleClick}>Click</button>;
1164 | }
1165 | ```
1166 |
1167 | **Pattern 2: Conditional execution (stop on preventDefault)**
1168 | ```tsx
1169 | function composeEventHandlers<E extends React.SyntheticEvent>(
1170 | internalHandler?: (e: E) => void,
1171 | externalHandler?: (e: E) => void
1172 | ) {
1173 | return (event: E) => {
1174 | internalHandler?.(event);
1175 |
1176 | // If internal handler called preventDefault, stop
1177 | if (!event.defaultPrevented) {
1178 | externalHandler?.(event);
1179 | }
1180 | };
1181 | }
1182 |
1183 | // Usage - XMLUI pattern
1184 | function Select({ onChange, onDidChange }: Props) {
1185 | const handleInternalChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
1186 | // Internal logic (validation, state updates)
1187 | validateValue(e.target.value);
1188 |
1189 | // Prevent external handler if validation fails
1190 | if (!isValid) {
1191 | e.preventDefault();
1192 | }
1193 | };
1194 |
1195 | const handleChange = composeEventHandlers(handleInternalChange, onChange);
1196 |
1197 | return <select onChange={handleChange}>...</select>;
1198 | }
1199 | ```
1200 |
1201 | **Pattern 3: Merge handlers from props**
1202 | ```tsx
1203 | function mergeEventHandlers<T extends React.SyntheticEvent>(
1204 | ours: ((e: T) => void) | undefined,
1205 | theirs: ((e: T) => void) | undefined
1206 | ): ((e: T) => void) | undefined {
1207 | if (!ours) return theirs;
1208 | if (!theirs) return ours;
1209 |
1210 | return (event: T) => {
1211 | ours(event);
1212 | if (!event.defaultPrevented) {
1213 | theirs(event);
1214 | }
1215 | };
1216 | }
1217 |
1218 | // Usage - Wrapper component
1219 | function Wrapper({ children, onClick }: Props) {
1220 | const internalClick = (e: React.MouseEvent) => {
1221 | console.log('Wrapper clicked');
1222 | };
1223 |
1224 | return cloneElement(children, {
1225 | onClick: mergeEventHandlers(internalClick, children.props.onClick),
1226 | });
1227 | }
1228 | ```
1229 |
1230 | **XMLUI Example (Container component):**
1231 | ```tsx
1232 | function Container({ children, ...props }: Props, ref: Ref<HTMLElement>) {
1233 | const renderedChild = renderChild(children);
1234 |
1235 | if (ref && renderedChild && isValidElement(renderedChild)) {
1236 | // Merge event handlers from both Container and child
1237 | const mergedProps = {
1238 | ...renderedChild.props,
1239 | onClick: composeEventHandlers(props.onClick, renderedChild.props.onClick),
1240 | onKeyDown: composeEventHandlers(props.onKeyDown, renderedChild.props.onKeyDown),
1241 | ref: composeRefs(ref, (renderedChild as any).ref),
1242 | };
1243 |
1244 | return cloneElement(renderedChild, mergedProps);
1245 | }
1246 |
1247 | return renderedChild;
1248 | }
1249 | ```
1250 |
1251 | **Pattern 4: Callback with additional args**
1252 | ```tsx
1253 | function withArgs<E, T>(
1254 | handler: ((e: E, ...args: T[]) => void) | undefined,
1255 | ...args: T[]
1256 | ) {
1257 | if (!handler) return undefined;
1258 |
1259 | return (event: E) => {
1260 | handler(event, ...args);
1261 | };
1262 | }
1263 |
1264 | // Usage
1265 | function List({ items, onItemClick }: Props) {
1266 | return (
1267 | <ul>
1268 | {items.map(item => (
1269 | <li key={item.id} onClick={withArgs(onItemClick, item.id)}>
1270 | {item.name}
1271 | </li>
1272 | ))}
1273 | </ul>
1274 | );
1275 | }
1276 | ```
1277 |
1278 | **When to compose handlers:**
1279 | - Building reusable component libraries
1280 | - Wrapper components that add behavior
1281 | - Components with internal + external handlers
1282 | - Need to call parent handler conditionally
1283 |
1284 | **Best practices:**
1285 | - Always check if handler exists before calling
1286 | - Respect `preventDefault()` and `stopPropagation()`
1287 | - Execute internal handlers first
1288 | - Document composition order clearly
1289 |
1290 | ---
1291 |
1292 | ## React Lifecycle and Effect Patterns
1293 |
1294 | Patterns for managing side effects, synchronization, and component lifecycle in React applications.
1295 |
1296 | ### `useEffect` - Side Effects and Lifecycle
1297 |
1298 | **Purpose:** Run side effects after render (data fetching, subscriptions, DOM manipulation).
1299 |
1300 | **Syntax:** `useEffect(() => { /* effect */ return () => { /* cleanup */ } }, [dependencies])`
1301 |
1302 | **Basic usage:**
1303 | ```tsx
1304 | function UserProfile({ userId }: Props) {
1305 | const [user, setUser] = useState(null);
1306 |
1307 | useEffect(() => {
1308 | // Effect runs after render
1309 | fetch(`/api/users/${userId}`)
1310 | .then(res => res.json())
1311 | .then(data => setUser(data));
1312 | }, [userId]); // Re-run when userId changes
1313 |
1314 | return <div>{user?.name}</div>;
1315 | }
1316 | ```
1317 |
1318 | **With async/await:**
1319 | ```tsx
1320 | useEffect(() => {
1321 | // Can't make callback async directly, use IIFE
1322 | (async () => {
1323 | const res = await fetch(`/api/users/${userId}`);
1324 | const data = await res.json();
1325 | setUser(data);
1326 | })();
1327 | }, [userId]);
1328 | ```
1329 |
1330 | **Use when:** Data fetching, subscriptions, event listeners, DOM manipulation, integrating with non-React libraries.
1331 | **Avoid when:** Computing derived values (use `useMemo`), handling events (use handlers), initializing state (use initializer).
1332 |
1333 | ---
1334 |
1335 | ### Effect Cleanup Pattern
1336 |
1337 | **Purpose:** Properly clean up subscriptions, timers, and event listeners to prevent memory leaks.
1338 |
1339 | **Pattern: Always return cleanup function for subscriptions**
1340 | ```tsx
1341 | function Chat({ roomId }: Props) {
1342 | useEffect(() => {
1343 | const connection = createConnection(roomId);
1344 | connection.connect();
1345 |
1346 | // ✅ Cleanup runs before next effect and on unmount
1347 | return () => {
1348 | connection.disconnect();
1349 | };
1350 | }, [roomId]);
1351 |
1352 | return <div>Connected to {roomId}</div>;
1353 | }
1354 | ```
1355 |
1356 | **Pattern: Cancel async operations**
1357 | ```tsx
1358 | function DataComponent({ url }: Props) {
1359 | const [data, setData] = useState(null);
1360 |
1361 | useEffect(() => {
1362 | let cancelled = false;
1363 |
1364 | fetch(url)
1365 | .then(res => res.json())
1366 | .then(data => {
1367 | if (!cancelled) setData(data);
1368 | });
1369 |
1370 | // ✅ Prevent state updates after unmount
1371 | return () => { cancelled = true; };
1372 | }, [url]);
1373 |
1374 | return <div>{JSON.stringify(data)}</div>;
1375 | }
1376 | ```
1377 |
1378 | **Pattern: Remove event listeners**
1379 | ```tsx
1380 | function WindowSize() {
1381 | const [size, setSize] = useState({ width: 0, height: 0 });
1382 |
1383 | useEffect(() => {
1384 | const handleResize = () => {
1385 | setSize({ width: window.innerWidth, height: window.innerHeight });
1386 | };
1387 |
1388 | window.addEventListener('resize', handleResize);
1389 | handleResize(); // Initial size
1390 |
1391 | // ✅ Always remove listeners
1392 | return () => window.removeEventListener('resize', handleResize);
1393 | }, []);
1394 |
1395 | return <div>{size.width} x {size.height}</div>;
1396 | }
1397 | ```
1398 |
1399 | **Pattern: Clear timers and intervals**
1400 | ```tsx
1401 | function Timer() {
1402 | const [count, setCount] = useState(0);
1403 |
1404 | useEffect(() => {
1405 | const interval = setInterval(() => {
1406 | setCount(c => c + 1);
1407 | }, 1000);
1408 |
1409 | // ✅ Clear interval on unmount
1410 | return () => clearInterval(interval);
1411 | }, []);
1412 |
1413 | return <div>{count}s</div>;
1414 | }
1415 | ```
1416 |
1417 | **Pattern: Unsubscribe from external stores**
1418 | ```tsx
1419 | function ExternalStore({ store }: Props) {
1420 | const [value, setValue] = useState(store.getValue());
1421 |
1422 | useEffect(() => {
1423 | const unsubscribe = store.subscribe(newValue => {
1424 | setValue(newValue);
1425 | });
1426 |
1427 | // ✅ Unsubscribe when component unmounts
1428 | return unsubscribe;
1429 | }, [store]);
1430 |
1431 | return <div>{value}</div>;
1432 | }
1433 | ```
1434 |
1435 | **Critical rules:**
1436 | - Return cleanup for subscriptions, listeners, timers
1437 | - Use cancellation flags for async operations
1438 | - Cleanup runs before next effect and on unmount
1439 | - Don't forget to remove event listeners
1440 |
1441 | ---
1442 |
1443 | ### Effect Dependencies Pattern
1444 |
1445 | **Purpose:** Correctly manage dependency arrays to avoid stale closures and unnecessary re-runs.
1446 |
1447 | **Anti-pattern: Missing dependencies (stale closure bug)**
1448 | ```tsx
1449 | // ❌ WRONG - Stale closure
1450 | function Counter() {
1451 | const [count, setCount] = useState(0);
1452 |
1453 | useEffect(() => {
1454 | setInterval(() => {
1455 | console.log(count); // Always logs 0!
1456 | }, 1000);
1457 | }, []); // Missing count
1458 |
1459 | return <button onClick={() => setCount(count + 1)}>Increment</button>;
1460 | }
1461 |
1462 | // ✅ CORRECT - Use functional update
1463 | useEffect(() => {
1464 | setInterval(() => {
1465 | setCount(c => c + 1); // Uses latest value
1466 | }, 1000);
1467 | }, []); // No dependencies needed
1468 | ```
1469 |
1470 | **Anti-pattern: Object/array in dependencies**
1471 | ```tsx
1472 | // ❌ WRONG - Object recreated every render
1473 | function Component({ config }: Props) {
1474 | useEffect(() => {
1475 | fetchData(config);
1476 | }, [config]); // Runs every render if config is new object
1477 | }
1478 |
1479 | // ✅ CORRECT - Destructure primitive values
1480 | useEffect(() => {
1481 | fetchData(config);
1482 | }, [config.id, config.filter]); // Only re-run when these change
1483 | ```
1484 |
1485 | **Pattern: Empty array = run once on mount**
1486 | ```tsx
1487 | useEffect(() => {
1488 | // Initialization logic
1489 | initializeApp();
1490 |
1491 | return () => {
1492 | // Cleanup on unmount
1493 | cleanupApp();
1494 | };
1495 | }, []); // Runs once on mount, cleanup on unmount
1496 | ```
1497 |
1498 | **Pattern: No array = run after every render**
1499 | ```tsx
1500 | useEffect(() => {
1501 | // Runs after every render (rarely needed)
1502 | updateDocumentTitle(`Page - ${count}`);
1503 | }); // No dependency array
1504 | ```
1505 |
1506 | **Pattern: Avoid callback dependencies with useRef**
1507 | ```tsx
1508 | function Component({ callback }: Props) {
1509 | const callbackRef = useRef(callback);
1510 |
1511 | // Keep ref updated
1512 | useEffect(() => {
1513 | callbackRef.current = callback;
1514 | }, [callback]);
1515 |
1516 | useEffect(() => {
1517 | const interval = setInterval(() => {
1518 | // ✅ Always uses latest callback
1519 | callbackRef.current();
1520 | }, 1000);
1521 |
1522 | return () => clearInterval(interval);
1523 | }, []); // No callback in deps
1524 | }
1525 | ```
1526 |
1527 | **Common mistakes:**
1528 | ```tsx
1529 | // ❌ Wrong - Missing dependencies
1530 | useEffect(() => {
1531 | doSomething(prop); // prop not in deps
1532 | }, []);
1533 |
1534 | // ❌ Wrong - Object identity
1535 | useEffect(() => {
1536 | fetchData(user);
1537 | }, [user]); // user object changes every render
1538 |
1539 | // ❌ Wrong - Function identity
1540 | useEffect(() => {
1541 | callback();
1542 | }, [callback]); // callback recreated every render
1543 |
1544 | // ✅ Correct - Destructure objects
1545 | useEffect(() => {
1546 | fetchData({ id: user.id, name: user.name });
1547 | }, [user.id, user.name]);
1548 |
1549 | // ✅ Correct - Memoize callbacks
1550 | const memoizedCallback = useCallback(callback, [dep]);
1551 | useEffect(() => {
1552 | memoizedCallback();
1553 | }, [memoizedCallback]);
1554 | ```
1555 |
1556 | **ESLint rule:** Always enable `react-hooks/exhaustive-deps` to catch dependency issues.
1557 |
1558 | ---
1559 |
1560 | ### Layout Effect Pattern
1561 |
1562 | **Purpose:** Run effects synchronously after DOM mutations but before browser paint to prevent visual flickering.
1563 |
1564 | **When to use `useLayoutEffect`:**
1565 |
1566 | **Pattern: DOM measurements before paint**
1567 | ```tsx
1568 | function Tooltip() {
1569 | const [position, setPosition] = useState({ x: 0, y: 0 });
1570 | const ref = useRef<HTMLDivElement>(null);
1571 |
1572 | // ✅ CORRECT - Measure before paint
1573 | useLayoutEffect(() => {
1574 | if (ref.current) {
1575 | const rect = ref.current.getBoundingClientRect();
1576 | setPosition({
1577 | x: rect.left + rect.width / 2,
1578 | y: rect.top - 10,
1579 | });
1580 | }
1581 | }, []);
1582 |
1583 | return <div ref={ref} style={{ left: position.x, top: position.y }}>Tooltip</div>;
1584 | }
1585 |
1586 | // ❌ WRONG - useEffect causes visible flicker
1587 | useEffect(() => {
1588 | // DOM measurements happen AFTER paint
1589 | // User sees element jump from old to new position
1590 | }, []);
1591 | ```
1592 |
1593 | **Pattern: Synchronize scroll position**
1594 | ```tsx
1595 | function ScrollSync({ targetRef }: Props) {
1596 | useLayoutEffect(() => {
1597 | if (targetRef.current) {
1598 | // ✅ Scroll before paint, no flicker
1599 | targetRef.current.scrollTop = savedPosition;
1600 | }
1601 | }, [targetRef, savedPosition]);
1602 | }
1603 | ```
1604 |
1605 | **Pattern: Prevent layout shift**
1606 | ```tsx
1607 | function AutoResizeTextarea({ value }: Props) {
1608 | const ref = useRef<HTMLTextAreaElement>(null);
1609 |
1610 | useLayoutEffect(() => {
1611 | if (ref.current) {
1612 | // ✅ Adjust height before paint
1613 | ref.current.style.height = 'auto';
1614 | ref.current.style.height = `${ref.current.scrollHeight}px`;
1615 | }
1616 | }, [value]);
1617 |
1618 | return <textarea ref={ref} value={value} />;
1619 | }
1620 | ```
1621 |
1622 | **Pattern: Third-party DOM library integration**
1623 | ```tsx
1624 | function Chart({ data }: Props) {
1625 | const containerRef = useRef<HTMLDivElement>(null);
1626 |
1627 | useLayoutEffect(() => {
1628 | if (containerRef.current) {
1629 | // ✅ Initialize library before paint
1630 | const chart = new ChartLibrary(containerRef.current);
1631 | chart.render(data);
1632 |
1633 | return () => chart.destroy();
1634 | }
1635 | }, [data]);
1636 |
1637 | return <div ref={containerRef} />;
1638 | }
1639 | ```
1640 |
1641 | **Comparison: useEffect vs useLayoutEffect**
1642 |
1643 | | Aspect | `useEffect` | `useLayoutEffect` |
1644 | |--------|------------|-------------------|
1645 | | **Timing** | After paint (async) | Before paint (sync) |
1646 | | **Blocks rendering** | ❌ No | ✅ Yes |
1647 | | **Use for** | Data fetching, subscriptions | DOM measurements, preventing flicker |
1648 | | **Performance** | Better (non-blocking) | Worse (blocks paint) |
1649 | | **SSR** | ✅ Works | ⚠️ Warning (no DOM on server) |
1650 |
1651 | **When to use each:**
1652 | - **useEffect**: 99% of cases - data fetching, subscriptions, analytics
1653 | - **useLayoutEffect**: DOM measurements, scroll sync, preventing visual flicker
1654 |
1655 | **Warning:** `useLayoutEffect` blocks visual updates. Only use when you need synchronous DOM access before paint.
1656 |
1657 | **SSR consideration:**
1658 | ```tsx
1659 | // ⚠️ useLayoutEffect doesn't run on server
1660 | useLayoutEffect(() => {
1661 | // This code only runs in browser
1662 | measureDOM();
1663 | }, []);
1664 |
1665 | // ✅ Better: Use useEffect for SSR-compatible code
1666 | useEffect(() => {
1667 | measureDOM();
1668 | }, []);
1669 | ```
1670 |
1671 | ---
1672 |
1673 | ### Insertion Effect Pattern
1674 |
1675 | **Purpose:** Insert styles into DOM before layout effects run. Used by CSS-in-JS libraries.
1676 |
1677 | **Syntax:** `useInsertionEffect(() => { /* insert styles */ }, [dependencies])`
1678 |
1679 | **Effect execution order:**
1680 | 1. `useInsertionEffect` - Insert styles
1681 | 2. `useLayoutEffect` - Measure layout (reads styles)
1682 | 3. Browser paints
1683 | 4. `useEffect` - Other side effects
1684 |
1685 | **Pattern: CSS-in-JS style injection**
1686 | ```tsx
1687 | function useCSS(rule: string) {
1688 | useInsertionEffect(() => {
1689 | // ✅ Inject styles before layout reads
1690 | const style = document.createElement('style');
1691 | style.textContent = rule;
1692 | document.head.appendChild(style);
1693 |
1694 | return () => document.head.removeChild(style);
1695 | }, [rule]);
1696 | }
1697 |
1698 | // Usage
1699 | function Button({ color }: Props) {
1700 | useCSS(`
1701 | .button-${color} {
1702 | background: ${color};
1703 | border: 1px solid ${darken(color)};
1704 | }
1705 | `);
1706 |
1707 | return <button className={`button-${color}`}>Click</button>;
1708 | }
1709 | ```
1710 |
1711 | **Pattern: Dynamic theme injection (XMLUI)**
1712 | ```tsx
1713 | function ThemeProvider({ theme, children }: Props) {
1714 | useInsertionEffect(() => {
1715 | // ✅ Insert theme CSS before components measure
1716 | const styleElement = document.createElement('style');
1717 | styleElement.id = 'theme-styles';
1718 | styleElement.textContent = generateThemeCSS(theme);
1719 | document.head.appendChild(styleElement);
1720 |
1721 | return () => {
1722 | document.getElementById('theme-styles')?.remove();
1723 | };
1724 | }, [theme]);
1725 |
1726 | return children;
1727 | }
1728 | ```
1729 |
1730 | **Pattern: Critical CSS injection**
1731 | ```tsx
1732 | function useCriticalCSS(css: string) {
1733 | useInsertionEffect(() => {
1734 | // ✅ Inject before any layout calculations
1735 | const style = document.createElement('style');
1736 | style.setAttribute('data-critical', 'true');
1737 | style.textContent = css;
1738 | document.head.insertBefore(style, document.head.firstChild);
1739 |
1740 | return () => style.remove();
1741 | }, [css]);
1742 | }
1743 | ```
1744 |
1745 | **When to use:**
1746 | - Building CSS-in-JS libraries
1747 | - Dynamic style generation
1748 | - Theme system implementation
1749 | - Critical CSS injection
1750 |
1751 | **When NOT to use:**
1752 | - Regular application code (use `useEffect`)
1753 | - Static stylesheets (use `<link>` tags)
1754 | - Non-style DOM manipulation
1755 |
1756 | **Note:** Rarely used directly in application code. Primarily for library authors. XMLUI uses this in `StyleContext` for theme style injection.
1757 |
1758 | **Comparison with other effects:**
1759 |
1760 | ```tsx
1761 | // ❌ WRONG - useEffect runs too late
1762 | useEffect(() => {
1763 | injectStyles(); // Styles added after layout measured
1764 | }, []);
1765 |
1766 | // ❌ WRONG - useLayoutEffect causes double layout
1767 | useLayoutEffect(() => {
1768 | injectStyles(); // Layout measured, then styles added, then re-measured
1769 | }, []);
1770 |
1771 | // ✅ CORRECT - useInsertionEffect runs first
1772 | useInsertionEffect(() => {
1773 | injectStyles(); // Styles ready before any layout measurement
1774 | }, []);
1775 | ```
1776 |
1777 | ---
1778 |
1779 | ### Effect Best Practices Summary
1780 |
1781 | **1. Always clean up:**
1782 | ```tsx
1783 | useEffect(() => {
1784 | const subscription = subscribe();
1785 | return () => subscription.unsubscribe(); // ✅ Cleanup
1786 | }, []);
1787 | ```
1788 |
1789 | **2. Handle dependencies correctly:**
1790 | ```tsx
1791 | // ✅ Include all dependencies
1792 | useEffect(() => {
1793 | doSomething(prop, state);
1794 | }, [prop, state]);
1795 |
1796 | // ✅ Or use functional updates
1797 | useEffect(() => {
1798 | setState(prev => prev + 1);
1799 | }, []); // No state dependency needed
1800 | ```
1801 |
1802 | **3. Choose the right effect hook:**
1803 | - `useEffect` - Default choice (async, after paint)
1804 | - `useLayoutEffect` - DOM measurements, prevent flicker (sync, before paint)
1805 | - `useInsertionEffect` - CSS-in-JS only (before layout)
1806 |
1807 | **4. Avoid common pitfalls:**
1808 | ```tsx
1809 | // ❌ Don't use objects in deps
1810 | useEffect(() => {}, [config]); // Runs every render
1811 |
1812 | // ✅ Destructure primitive values
1813 | useEffect(() => {}, [config.id, config.name]);
1814 |
1815 | // ❌ Don't make effect callback async
1816 | useEffect(async () => {}, []); // Type error
1817 |
1818 | // ✅ Use IIFE for async
1819 | useEffect(() => {
1820 | (async () => await fetch())();
1821 | }, []);
1822 | ```
1823 |
1824 | **5. Profile before optimizing:**
1825 | - Most effects are cheap
1826 | - Don't prematurely optimize with `useLayoutEffect`
1827 | - Measure actual performance impact
1828 |
1829 | ---
1830 |
1831 | ## `useRef` - Persistent Mutable References
1832 |
1833 | **Purpose:** Store mutable values that persist across renders without triggering re-renders.
1834 |
1835 | **Syntax:** `const ref = useRef(initialValue)`
1836 |
1837 | ### DOM References
1838 |
1839 | ```tsx
1840 | function TextInput() {
1841 | const inputRef = useRef<HTMLInputElement>(null);
1842 |
1843 | const focusInput = () => {
1844 | inputRef.current?.focus();
1845 | };
1846 |
1847 | return (
1848 | <div>
1849 | <input ref={inputRef} />
1850 | <button onClick={focusInput}>Focus</button>
1851 | </div>
1852 | );
1853 | }
1854 | ```
1855 |
1856 | ### Storing Mutable Values
1857 |
1858 | ```tsx
1859 | function Timer() {
1860 | const [count, setCount] = useState(0);
1861 | const intervalRef = useRef<NodeJS.Timeout>();
1862 |
1863 | useEffect(() => {
1864 | intervalRef.current = setInterval(() => {
1865 | setCount(c => c + 1);
1866 | }, 1000);
1867 |
1868 | return () => clearInterval(intervalRef.current);
1869 | }, []);
1870 |
1871 | const stop = () => {
1872 | clearInterval(intervalRef.current);
1873 | };
1874 |
1875 | return (
1876 | <div>
1877 | {count} seconds
1878 | <button onClick={stop}>Stop</button>
1879 | </div>
1880 | );
1881 | }
1882 | ```
1883 |
1884 | ### Avoiding Stale Closures
1885 |
1886 | ```tsx
1887 | function Component({ callback }: Props) {
1888 | const callbackRef = useRef(callback);
1889 |
1890 | // Keep ref updated with latest callback
1891 | useEffect(() => {
1892 | callbackRef.current = callback;
1893 | }, [callback]);
1894 |
1895 | useEffect(() => {
1896 | const interval = setInterval(() => {
1897 | // Always uses latest callback
1898 | callbackRef.current();
1899 | }, 1000);
1900 |
1901 | return () => clearInterval(interval);
1902 | }, []); // No callback dependency needed
1903 |
1904 | return <div>Running...</div>;
1905 | }
1906 | ```
1907 |
1908 | ### Common Patterns in XMLUI
1909 |
1910 | **Previous Value Tracking:**
1911 | ```tsx
1912 | function usePrevious<T>(value: T): T | undefined {
1913 | const ref = useRef<T>();
1914 |
1915 | useEffect(() => {
1916 | ref.current = value;
1917 | }, [value]);
1918 |
1919 | return ref.current;
1920 | }
1921 |
1922 | function Component({ count }: Props) {
1923 | const prevCount = usePrevious(count);
1924 |
1925 | return <div>Now: {count}, Before: {prevCount}</div>;
1926 | }
1927 | ```
1928 |
1929 | ### Key Differences: useState vs useRef
1930 |
1931 | | Feature | `useState` | `useRef` |
1932 | |---------|-----------|---------|
1933 | | Triggers re-render | ✅ Yes | ❌ No |
1934 | | Persists across renders | ✅ Yes | ✅ Yes |
1935 | | Use for UI state | ✅ Yes | ❌ No |
1936 | | Use for DOM access | ❌ No | ✅ Yes |
1937 | | Use for mutable timers/intervals | ❌ No | ✅ Yes |
1938 |
1939 | ---
1940 |
1941 | ## `useId` - Unique ID Generation
1942 |
1943 | **Purpose:** Generate stable unique IDs for accessibility attributes.
1944 |
1945 | **Syntax:** `const id = useId()`
1946 |
1947 | ### Basic Usage
1948 |
1949 | ```tsx
1950 | function FormField({ label }: Props) {
1951 | const id = useId();
1952 |
1953 | return (
1954 | <div>
1955 | <label htmlFor={id}>{label}</label>
1956 | <input id={id} />
1957 | </div>
1958 | );
1959 | }
1960 | ```
1961 |
1962 | ### Multiple IDs
1963 |
1964 | ```tsx
1965 | function ComplexForm() {
1966 | const id = useId();
1967 |
1968 | return (
1969 | <div>
1970 | <label htmlFor={`${id}-name`}>Name</label>
1971 | <input id={`${id}-name`} aria-describedby={`${id}-name-hint`} />
1972 | <span id={`${id}-name-hint`}>Enter your full name</span>
1973 |
1974 | <label htmlFor={`${id}-email`}>Email</label>
1975 | <input id={`${id}-email`} />
1976 | </div>
1977 | );
1978 | }
1979 | ```
1980 |
1981 | **Why not just use a counter?** `useId` generates IDs that are stable across server and client rendering, preventing hydration mismatches.
1982 |
1983 | ---
1984 |
1985 | ## `forwardRef` - Ref Forwarding to Child Components
1986 |
1987 | **Purpose:** Allow parent components to access DOM nodes or component instances of child components by forwarding refs through component boundaries.
1988 |
1989 | **Syntax:** `const Component = forwardRef((props, ref) => { ... })`
1990 |
1991 | ### Basic Usage
1992 |
1993 | ```tsx
1994 | const TextInput = forwardRef<HTMLInputElement, Props>(
1995 | function TextInput({ label, ...props }, forwardedRef) {
1996 | return (
1997 | <div>
1998 | <label>{label}</label>
1999 | <input ref={forwardedRef} {...props} />
2000 | </div>
2001 | );
2002 | }
2003 | );
2004 |
2005 | // Parent can now access the input element
2006 | function Form() {
2007 | const inputRef = useRef<HTMLInputElement>(null);
2008 |
2009 | const focusInput = () => {
2010 | inputRef.current?.focus();
2011 | };
2012 |
2013 | return (
2014 | <div>
2015 | <TextInput ref={inputRef} label="Name" />
2016 | <button onClick={focusInput}>Focus Input</button>
2017 | </div>
2018 | );
2019 | }
2020 | ```
2021 |
2022 | ### TypeScript Generic Syntax
2023 |
2024 | ```tsx
2025 | // Explicitly type both the ref and props
2026 | const Component = forwardRef<RefType, PropsType>(
2027 | function Component(props, ref) {
2028 | return <div ref={ref}>...</div>;
2029 | }
2030 | );
2031 |
2032 | // Example with HTMLDivElement
2033 | const Card = forwardRef<HTMLDivElement, CardProps>(
2034 | function Card({ children, className }, ref) {
2035 | return (
2036 | <div ref={ref} className={className}>
2037 | {children}
2038 | </div>
2039 | );
2040 | }
2041 | );
2042 | ```
2043 |
2044 | **Why explicit typing matters:**
2045 |
2046 | Without generic syntax, TypeScript infers types from the function signature, which can lead to several issues:
2047 |
2048 | ```tsx
2049 | // ❌ WRONG - Without explicit generics
2050 | const Input = forwardRef(function Input(props: Props, ref) {
2051 | // TypeScript infers ref as: ForwardedRef<unknown>
2052 | // This means:
2053 | // 1. No autocomplete for ref.current properties
2054 | // 2. No type checking when assigning ref to JSX elements
2055 | // 3. Parent components can pass wrong ref type without errors
2056 | return <input ref={ref} />; // Type error: ref might not be compatible!
2057 | });
2058 |
2059 | // Usage - TypeScript won't catch this error:
2060 | const divRef = useRef<HTMLDivElement>(null);
2061 | <Input ref={divRef} /> // Should error but doesn't - expecting HTMLInputElement!
2062 |
2063 | // ✅ CORRECT - With explicit generics
2064 | const Input = forwardRef<HTMLInputElement, Props>(
2065 | function Input(props, ref) {
2066 | // TypeScript knows ref is: ForwardedRef<HTMLInputElement>
2067 | // Benefits:
2068 | // 1. Autocomplete works: ref.current?.focus()
2069 | // 2. Type checking ensures ref matches JSX element
2070 | // 3. Parent must pass correct ref type
2071 | return <input ref={ref} />; // Type safe!
2072 | }
2073 | );
2074 |
2075 | // Usage - TypeScript catches the error:
2076 | const divRef = useRef<HTMLDivElement>(null);
2077 | <Input ref={divRef} /> // ❌ Type error: expected RefObject<HTMLInputElement>
2078 | ```
2079 |
2080 | **Key problems without explicit generics:**
2081 | 1. **Loss of type safety** - Parent can pass incompatible ref types
2082 | 2. **No IntelliSense** - No autocomplete for `ref.current` properties
2083 | 3. **Runtime errors** - Type mismatches only discovered at runtime
2084 | 4. **Harder refactoring** - Changes to ref type don't propagate to consumers
2085 |
2086 | **Best practice:** Always specify both generic parameters explicitly in XMLUI components.
2087 |
2088 | ### Composing Multiple Refs
2089 |
2090 | **The Problem:** Components often need to manage multiple refs pointing to the same DOM element:
2091 | 1. **Internal ref** - Component's own logic (measurements, animations, focus)
2092 | 2. **Forwarded ref** - Parent needs access to the DOM element
2093 | 3. **Third-party refs** - Integration with libraries (Popper, Radix UI, etc.)
2094 |
2095 | **The Solution:** Use `composeRefs` from `@radix-ui/react-compose-refs` to merge multiple refs into one.
2096 |
2097 | **Why you need to compose refs:**
2098 |
2099 | | Use Case | Example | Reason |
2100 | |----------|---------|--------|
2101 | | **Internal logic + parent access** | Auto-resize textarea | Component measures scrollHeight, parent needs focus() |
2102 | | **Library integration** | Popover/Tooltip | Popper needs ref for positioning, parent needs ref for control |
2103 | | **Wrapper components** | Container with single child | Parent ref applies to child, child has own ref |
2104 | | **Multiple behaviors** | Draggable element | Drag library needs ref, resize observer needs ref, parent needs ref |
2105 |
2106 | **Key differences: Inner vs Forwarded refs:**
2107 |
2108 | | Aspect | Inner Ref | Forwarded Ref |
2109 | |--------|-----------|---------------|
2110 | | **Created by** | Component itself with `useRef()` | Parent component |
2111 | | **Purpose** | Internal component logic | Parent needs DOM access |
2112 | | **Type** | Always `RefObject<T>` | Can be `RefObject<T>`, `RefCallback<T>`, or `null` |
2113 | | **Guaranteed to exist** | Yes - always has `.current` property | No - parent might not pass a ref |
2114 | | **When to use** | Component needs DOM access for its own behavior | Expose DOM element to parent |
2115 |
2116 | **Example 1: Internal + Forwarded (most common in XMLUI):**
2117 |
2118 | ```tsx
2119 | import { composeRefs } from "@radix-ui/react-compose-refs";
2120 |
2121 | function TextArea({ value, onChange }: Props, forwardedRef: Ref<HTMLTextAreaElement>) {
2122 | // Inner ref: Component creates and owns this for auto-resize logic
2123 | const innerRef = useRef<HTMLTextAreaElement>(null);
2124 |
2125 | // Compose both refs - textarea element needs both
2126 | const composedRef = forwardedRef
2127 | ? composeRefs(innerRef, forwardedRef)
2128 | : innerRef;
2129 |
2130 | useEffect(() => {
2131 | // ✅ CORRECT: Use innerRef for internal logic
2132 | // It's guaranteed to exist and have .current property
2133 | if (innerRef.current) {
2134 | innerRef.current.style.height = 'auto';
2135 | innerRef.current.style.height = `${innerRef.current.scrollHeight}px`;
2136 | }
2137 |
2138 | // ❌ WRONG: Don't use forwardedRef directly
2139 | // if (forwardedRef?.current) { ... } // Type error: Ref<T> might be a callback!
2140 | }, [value]);
2141 |
2142 | return <textarea ref={composedRef} value={value} onChange={onChange} />;
2143 | }
2144 |
2145 | export const AutoResizeTextArea = forwardRef(TextArea);
2146 |
2147 | // Parent usage:
2148 | function Form() {
2149 | const textareaRef = useRef<HTMLTextAreaElement>(null);
2150 |
2151 | const focusTextarea = () => {
2152 | textareaRef.current?.focus(); // Parent can access via forwarded ref
2153 | };
2154 |
2155 | return <AutoResizeTextArea ref={textareaRef} />; // Component auto-resizes via inner ref
2156 | }
2157 | ```
2158 |
2159 | **Example 2: Library Integration (Popper + Forwarded):**
2160 |
2161 | ```tsx
2162 | function Select({ options }: Props, forwardedRef: Ref<HTMLButtonElement>) {
2163 | // Popper library needs a ref for positioning
2164 | const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
2165 |
2166 | // Compose library ref setter with forwarded ref
2167 | const composedRef = forwardedRef
2168 | ? composeRefs(setReferenceElement, forwardedRef)
2169 | : setReferenceElement;
2170 |
2171 | return (
2172 | <>
2173 | <button ref={composedRef}>Select</button>
2174 | <Popper referenceElement={referenceElement}>
2175 | {/* Dropdown content */}
2176 | </Popper>
2177 | </>
2178 | );
2179 | }
2180 |
2181 | export const SelectComponent = forwardRef(Select);
2182 | ```
2183 |
2184 | **Example 3: Wrapper Component (Parent + Child refs):**
2185 |
2186 | ```tsx
2187 | function Container({ children }: Props, ref: Ref<HTMLElement>) {
2188 | const renderedChild = renderChild(children);
2189 |
2190 | // If single child, compose parent's ref with child's existing ref
2191 | if (isValidElement(renderedChild)) {
2192 | return cloneElement(renderedChild, {
2193 | ref: composeRefs(ref, (renderedChild as any).ref),
2194 | });
2195 | }
2196 |
2197 | return renderedChild;
2198 | }
2199 |
2200 | export const ContainerComponent = forwardRef(Container);
2201 | ```
2202 |
2203 | **Example 4: Multiple Behaviors (Drag + Resize + Forward):**
2204 |
2205 | ```tsx
2206 | function DraggablePanel(props: Props, forwardedRef: Ref<HTMLDivElement>) {
2207 | const dragRef = useRef<HTMLDivElement>(null);
2208 | const resizeObserverRef = useRef<HTMLDivElement>(null);
2209 |
2210 | // Compose all three refs
2211 | const composedRef = composeRefs(
2212 | dragRef,
2213 | resizeObserverRef,
2214 | forwardedRef || null
2215 | );
2216 |
2217 | useDragLogic(dragRef);
2218 | useResizeObserver(resizeObserverRef);
2219 |
2220 | return <div ref={composedRef}>Draggable and resizable</div>;
2221 | }
2222 | ```
2223 |
2224 | **When to compose refs:**
2225 | - Component needs internal ref AND parent needs access
2226 | - Integrating with libraries that require refs (Popper, React DnD, etc.)
2227 | - Wrapping components that need to forward refs to children
2228 | - Multiple hooks/effects need refs to the same element
2229 |
2230 | **How `composeRefs` works:**
2231 | - Accepts multiple refs (RefObjects, callbacks, or null)
2232 | - Returns a single callback ref that updates all provided refs
2233 | - Handles both RefObject (sets `.current`) and callback refs (calls function)
2234 | - Safely ignores `null`/`undefined` refs
2235 |
2236 | ### When to Use forwardRef
2237 |
2238 | **Use `forwardRef` when:**
2239 | - Building reusable components that wrap DOM elements
2240 | - Parent needs direct DOM access (focus, scroll, measurements)
2241 | - Integrating with third-party libraries requiring refs
2242 | - Creating form components that need imperative control
2243 |
2244 | **Don't use `forwardRef` when:**
2245 | - Component doesn't wrap a single DOM element
2246 | - Refs aren't needed by parent components
2247 | - You can solve the problem with callbacks/props instead
2248 |
2249 | ### Common Mistakes
2250 |
2251 | ```tsx
2252 | // ❌ WRONG - Forgetting to attach ref to DOM element
2253 | const Bad = forwardRef((props, ref) => {
2254 | return <div>{props.children}</div>; // ref is ignored!
2255 | });
2256 |
2257 | // ✅ CORRECT - Always attach ref to actual DOM element
2258 | const Good = forwardRef((props, ref) => {
2259 | return <div ref={ref}>{props.children}</div>;
2260 | });
2261 |
2262 | // ❌ WRONG - Attaching ref to component (won't work)
2263 | const AlsoBad = forwardRef((props, ref) => {
2264 | return <CustomComponent ref={ref} />; // CustomComponent must also use forwardRef
2265 | });
2266 |
2267 | // ✅ CORRECT - Forward through nested components
2268 | const CustomComponent = forwardRef((props, ref) => {
2269 | return <div ref={ref}>...</div>;
2270 | });
2271 |
2272 | const AlsoGood = forwardRef((props, ref) => {
2273 | return <CustomComponent ref={ref} />; // Works because CustomComponent forwards
2274 | });
2275 | ```
2276 |
2277 | ---
2278 |
2279 | ## `createPortal` - Render Outside Hierarchy
2280 |
2281 | **Purpose:** Render children into a DOM node outside the parent component's hierarchy.
2282 |
2283 | **Syntax:** `createPortal(children, domNode, key?)`
2284 |
2285 | ### Basic Usage
2286 |
2287 | ```tsx
2288 | import { createPortal } from 'react-dom';
2289 |
2290 | function Modal({ isOpen, children }: Props) {
2291 | if (!isOpen) return null;
2292 |
2293 | // Render into document.body instead of parent component
2294 | return createPortal(
2295 | <div className="modal-overlay">
2296 | <div className="modal-content">
2297 | {children}
2298 | </div>
2299 | </div>,
2300 | document.body
2301 | );
2302 | }
2303 |
2304 | // Usage
2305 | function App() {
2306 | return (
2307 | <div className="app">
2308 | <Modal isOpen={true}>
2309 | <h1>This renders in document.body, not .app!</h1>
2310 | </Modal>
2311 | </div>
2312 | );
2313 | }
2314 | ```
2315 |
2316 | ### Common Use Cases
2317 |
2318 | **1. Tooltips/Popovers (avoid z-index issues):**
2319 | ```tsx
2320 | function Tooltip({ targetRef, content }: Props) {
2321 | return createPortal(
2322 | <div className="tooltip" style={calculatePosition(targetRef)}>
2323 | {content}
2324 | </div>,
2325 | document.body
2326 | );
2327 | }
2328 | ```
2329 |
2330 | **2. Notifications/Toasts:**
2331 | ```tsx
2332 | function NotificationToast() {
2333 | const [shouldRender, setShouldRender] = useState(false);
2334 |
2335 | useEffect(() => {
2336 | setShouldRender(true);
2337 | }, []);
2338 |
2339 | if (!shouldRender) return null;
2340 |
2341 | return createPortal(
2342 | <Toaster position="top-right">
2343 | {(t) => <ToastBar toast={t} />}
2344 | </Toaster>,
2345 | document.body
2346 | );
2347 | }
2348 | ```
2349 |
2350 | **3. Modal Dialogs:**
2351 | ```tsx
2352 | function ModalDialog({ isOpen, children }: Props) {
2353 | if (!isOpen) return null;
2354 |
2355 | return createPortal(
2356 | <div className="modal-backdrop">
2357 | <div className="modal-dialog">
2358 | {children}
2359 | </div>
2360 | </div>,
2361 | document.getElementById('modal-root') || document.body
2362 | );
2363 | }
2364 | ```
2365 |
2366 | **4. Full-Screen Overlays:**
2367 | ```tsx
2368 | function FullScreenOverlay({ show, children }: Props) {
2369 | if (!show) return null;
2370 |
2371 | return createPortal(
2372 | <div className="fullscreen-overlay">
2373 | {children}
2374 | </div>,
2375 | document.body
2376 | );
2377 | }
2378 | ```
2379 |
2380 | ### Event Bubbling Still Works
2381 |
2382 | ```tsx
2383 | // Event bubbling works despite DOM hierarchy
2384 | function Parent() {
2385 | const handleClick = () => {
2386 | console.log('Clicked!'); // This fires even though button is portaled
2387 | };
2388 |
2389 | return (
2390 | <div onClick={handleClick}>
2391 | <PortaledButton />
2392 | </div>
2393 | );
2394 | }
2395 |
2396 | function PortaledButton() {
2397 | return createPortal(
2398 | <button>Click me</button>,
2399 | document.body
2400 | );
2401 | }
2402 | ```
2403 |
2404 | ### Common Pattern in XMLUI
2405 |
2406 | ```tsx
2407 | // App component portals theme styles
2408 | function App({ children }: Props) {
2409 | return (
2410 | <>
2411 | {children}
2412 | {createPortal(
2413 | <style>{themeCSS}</style>,
2414 | document.head
2415 | )}
2416 | </>
2417 | );
2418 | }
2419 |
2420 | // Inspector portals debugging UI
2421 | function Inspector() {
2422 | return createPortal(
2423 | <div className="inspector-panel">
2424 | {/* Debug tools */}
2425 | </div>,
2426 | document.body
2427 | );
2428 | }
2429 | ```
2430 |
2431 | ### When to Use createPortal
2432 |
2433 | **Use `createPortal` when:**
2434 | - Modals, dialogs, and overlays
2435 | - Tooltips and popovers
2436 | - Notifications and toasts
2437 | - Avoiding parent overflow/z-index issues
2438 | - Rendering into different parts of DOM (head, body)
2439 |
2440 | **Don't use when:**
2441 | - Normal component rendering is sufficient
2442 | - No CSS stacking or overflow issues
2443 | - Adds unnecessary complexity
2444 |
2445 | ---
2446 |
2447 | ## `Fragment` - Grouping Without DOM Nodes
2448 |
2449 | **Purpose:** Group multiple elements without adding extra nodes to the DOM.
2450 |
2451 | **Syntax:** `<Fragment>...</Fragment>` or `<>...</>`
2452 |
2453 | ### Basic Usage
2454 |
2455 | ```tsx
2456 | // ❌ WRONG - Adds unnecessary div wrapper
2457 | function List() {
2458 | return (
2459 | <div>
2460 | <li>Item 1</li>
2461 | <li>Item 2</li>
2462 | </div>
2463 | );
2464 | }
2465 |
2466 | // ✅ CORRECT - No extra DOM node
2467 | function List() {
2468 | return (
2469 | <>
2470 | <li>Item 1</li>
2471 | <li>Item 2</li>
2472 | </>
2473 | );
2474 | }
2475 | ```
2476 |
2477 | ### Short vs Long Syntax
2478 |
2479 | ```tsx
2480 | // Short syntax <> - Use for most cases
2481 | function Component() {
2482 | return (
2483 | <>
2484 | <Header />
2485 | <Content />
2486 | </>
2487 | );
2488 | }
2489 |
2490 | // Long syntax <Fragment> - Required when you need a key
2491 | function List({ items }: Props) {
2492 | return (
2493 | <ul>
2494 | {items.map(item => (
2495 | <Fragment key={item.id}>
2496 | <li>{item.name}</li>
2497 | <li>{item.description}</li>
2498 | </Fragment>
2499 | ))}
2500 | </ul>
2501 | );
2502 | }
2503 | ```
2504 |
2505 | **Limitations of short syntax:**
2506 | - ❌ Cannot add `key` prop (use `<Fragment key={...}>` instead)
2507 | - ❌ Cannot add any other props (only `key` is allowed on Fragment)
2508 | - ✅ Use short syntax everywhere else (cleaner, less verbose)
2509 |
2510 | ### Common Use Cases
2511 |
2512 | **1. Returning Multiple Elements:**
2513 | ```tsx
2514 | function Header() {
2515 | return (
2516 | <>
2517 | <h1>Title</h1>
2518 | <nav>Navigation</nav>
2519 | </>
2520 | );
2521 | }
2522 | ```
2523 |
2524 | **2. Conditional Rendering:**
2525 | ```tsx
2526 | function Component({ showExtra }: Props) {
2527 | return (
2528 | <div>
2529 | <h1>Always shown</h1>
2530 | {showExtra && (
2531 | <>
2532 | <p>Extra content</p>
2533 | <button>Extra button</button>
2534 | </>
2535 | )}
2536 | </div>
2537 | );
2538 | }
2539 | ```
2540 |
2541 | **3. Table Rows:**
2542 | ```tsx
2543 | function TableRows({ data }: Props) {
2544 | return (
2545 | <>
2546 | {data.map(row => (
2547 | <Fragment key={row.id}>
2548 | <tr>
2549 | <td>{row.name}</td>
2550 | <td>{row.value}</td>
2551 | </tr>
2552 | {row.hasDetails && (
2553 | <tr>
2554 | <td colSpan={2}>{row.details}</td>
2555 | </tr>
2556 | )}
2557 | </Fragment>
2558 | ))}
2559 | </>
2560 | );
2561 | }
2562 | ```
2563 |
2564 | **4. Avoiding Invalid HTML:**
2565 | ```tsx
2566 | // ❌ WRONG - div inside p is invalid HTML
2567 | function Text() {
2568 | return (
2569 | <p>
2570 | <div>This is invalid!</div>
2571 | </p>
2572 | );
2573 | }
2574 |
2575 | // ✅ CORRECT - Fragment doesn't create DOM node
2576 | function Text() {
2577 | return (
2578 | <p>
2579 | <>
2580 | <span>This is valid!</span>
2581 | </>
2582 | </p>
2583 | );
2584 | }
2585 | ```
2586 |
2587 | ### When to Use Fragment
2588 |
2589 | **Use `Fragment` when:**
2590 | - Component must return multiple elements
2591 | - Avoiding wrapper divs that break CSS (flexbox, grid)
2592 | - Keeping HTML semantically valid
2593 | - Conditional rendering of multiple elements
2594 |
2595 | **Don't use when:**
2596 | - Single element (no need to wrap)
2597 | - Wrapper div doesn't cause issues
2598 | - Need to attach events or refs (Fragment can't have them)
2599 |
2600 | ---
2601 |
2602 | ## `cloneElement` - Clone and Modify React Elements
2603 |
2604 | **Purpose:** Clone a React element and override its props, refs, or children.
2605 |
2606 | **Syntax:** `cloneElement(element, props?, ...children?)`
2607 |
2608 | ### Basic Usage
2609 |
2610 | ```tsx
2611 | import { cloneElement, isValidElement } from 'react';
2612 |
2613 | function Container({ children }: Props) {
2614 | if (!isValidElement(children)) {
2615 | return children;
2616 | }
2617 |
2618 | // Clone child and add extra props
2619 | return cloneElement(children, {
2620 | className: 'container-child',
2621 | style: { padding: '10px' },
2622 | });
2623 | }
2624 |
2625 | // Usage
2626 | <Container>
2627 | <div>Original</div> {/* Becomes <div className="container-child" style={{padding: '10px'}}>Original</div> */}
2628 | </Container>
2629 | ```
2630 |
2631 | ### Adding Props to Children
2632 |
2633 | ```tsx
2634 | function Animation({ children, duration = 300 }: Props) {
2635 | if (!isValidElement(children)) {
2636 | return children;
2637 | }
2638 |
2639 | // Add animation props to child
2640 | return cloneElement(children, {
2641 | style: {
2642 | ...children.props.style,
2643 | transition: `all ${duration}ms`,
2644 | },
2645 | });
2646 | }
2647 | ```
2648 |
2649 | ### Forwarding Refs Through Clone
2650 |
2651 | ```tsx
2652 | function Wrapper({ children, ...rest }: Props, forwardedRef: Ref<any>) {
2653 | if (!isValidElement(children)) {
2654 | return children;
2655 | }
2656 |
2657 | // Clone and forward ref + other props
2658 | return cloneElement(children, {
2659 | ...rest,
2660 | ref: forwardedRef,
2661 | });
2662 | }
2663 |
2664 | export const WrapperComponent = forwardRef(Wrapper);
2665 | ```
2666 |
2667 | ### Common Pattern in XMLUI
2668 |
2669 | **Container with single child ref forwarding:**
2670 | ```tsx
2671 | function Container({ children }: Props, ref: Ref<HTMLElement>) {
2672 | const renderedChild = renderChild(children);
2673 |
2674 | // If single valid child, compose refs and merge props
2675 | if (ref && renderedChild && isValidElement(renderedChild)) {
2676 | return cloneElement(renderedChild, {
2677 | ref: composeRefs(ref, (renderedChild as any).ref),
2678 | ...mergeProps(renderedChild.props, rest),
2679 | });
2680 | }
2681 |
2682 | return renderedChild;
2683 | }
2684 | ```
2685 |
2686 | **Form field with label integration:**
2687 | ```tsx
2688 | function ItemWithLabel({ children, label }: Props) {
2689 | const id = useId();
2690 |
2691 | return (
2692 | <div>
2693 | <label htmlFor={id}>{label}</label>
2694 | {cloneElement(children as ReactElement, {
2695 | id,
2696 | 'aria-labelledby': id,
2697 | })}
2698 | </div>
2699 | );
2700 | }
2701 | ```
2702 |
2703 | ### When to Use cloneElement
2704 |
2705 | **Use `cloneElement` when:**
2706 | - Wrapping components need to add props to children
2707 | - Forwarding refs through wrapper components
2708 | - Adding common behavior to arbitrary children
2709 | - Integrating with child elements you don't control
2710 |
2711 | **Don't use when:**
2712 | - You can pass props directly (prefer explicit props)
2713 | - You need to modify deeply nested children (use context instead)
2714 | - Children are not React elements (check with `isValidElement` first)
2715 |
2716 | ### Common Mistakes
2717 |
2718 | ```tsx
2719 | // ❌ WRONG - Not checking if child is valid element
2720 | function Bad({ children }: Props) {
2721 | return cloneElement(children, { className: 'bad' }); // Crashes if children is string/number
2722 | }
2723 |
2724 | // ✅ CORRECT - Always validate first (see isValidElement section)
2725 | function Good({ children }: Props) {
2726 | if (!isValidElement(children)) {
2727 | return children;
2728 | }
2729 | return cloneElement(children, { className: 'good' });
2730 | }
2731 |
2732 | // ❌ WRONG - Overriding all existing props
2733 | return cloneElement(child, { style: { color: 'red' } }); // Loses child's existing style
2734 |
2735 | // ✅ CORRECT - Merge with existing props
2736 | return cloneElement(child, {
2737 | style: { ...child.props.style, color: 'red' },
2738 | });
2739 | ```
2740 |
2741 | ---
2742 |
2743 | ## `isValidElement` - Type Check for React Elements
2744 |
2745 | **Purpose:** Check if a value is a valid React element (created with JSX or `createElement`). Always use before `cloneElement`.
2746 |
2747 | **Syntax:** `isValidElement(value)`
2748 |
2749 | ### Basic Usage
2750 |
2751 | ```tsx
2752 | import { isValidElement } from 'react';
2753 |
2754 | function processChild(child: React.ReactNode) {
2755 | // child could be anything: string, number, element, array, etc.
2756 |
2757 | if (isValidElement(child)) {
2758 | // TypeScript now knows child is ReactElement
2759 | console.log(child.props); // ✅ OK - access props safely
2760 | console.log(child.type); // ✅ OK - access type safely
2761 | return child;
2762 | }
2763 |
2764 | // Not an element - return as is
2765 | return child;
2766 | }
2767 | ```
2768 |
2769 | ### Common Pattern in XMLUI
2770 |
2771 | **Conditional element wrapping:**
2772 | ```tsx
2773 | function ConditionalWrapper({ condition, children }: Props) {
2774 | if (!condition) {
2775 | return children;
2776 | }
2777 |
2778 | // Only wrap if child is valid element
2779 | return isValidElement(children)
2780 | ? <div className="wrapper">{children}</div>
2781 | : children;
2782 | }
2783 | ```
2784 |
2785 | ### What isValidElement Checks
2786 |
2787 | ```tsx
2788 | isValidElement(<div />); // ✅ true - JSX element
2789 | isValidElement(React.createElement('div')); // ✅ true - created element
2790 | isValidElement(<Component />); // ✅ true - component element
2791 | isValidElement('hello'); // ❌ false - string
2792 | isValidElement(123); // ❌ false - number
2793 | isValidElement(null); // ❌ false - null
2794 | isValidElement(undefined); // ❌ false - undefined
2795 | isValidElement([<div key="1" />]); // ❌ false - array of elements
2796 | ```
2797 |
2798 | ### When to Use isValidElement
2799 |
2800 | **Use `isValidElement` when:**
2801 | - Before calling `cloneElement` (required to avoid crashes)
2802 | - Type narrowing for TypeScript (ReactNode → ReactElement)
2803 | - Validating `children` prop type
2804 | - Conditional element manipulation
2805 |
2806 | **Note:** See `cloneElement` section for examples of using these two functions together.
2807 |
2808 | ---
2809 |
2810 | ## `flushSync` - Synchronous State Updates
2811 |
2812 | **Purpose:** Force React to flush state updates synchronously, bypassing automatic batching.
2813 |
2814 | **Syntax:** `flushSync(() => { /* state updates */ })`
2815 |
2816 | **Warning:** Use sparingly - breaks React's batching optimization and can hurt performance.
2817 |
2818 | ### Basic Usage
2819 |
2820 | ```tsx
2821 | import { flushSync } from 'react-dom';
2822 |
2823 | function Form() {
2824 | const [value, setValue] = useState('');
2825 |
2826 | const handleSubmit = () => {
2827 | // Normal: state updates are batched
2828 | setValue('');
2829 | setError(null);
2830 | // Both updates happen together
2831 |
2832 | // With flushSync: update happens immediately
2833 | flushSync(() => {
2834 | setValue('');
2835 | });
2836 | // DOM is updated here, before next line
2837 | inputRef.current?.focus();
2838 | };
2839 | }
2840 | ```
2841 |
2842 | ### When DOM Must Update Immediately
2843 |
2844 | ```tsx
2845 | function Table({ data }: Props) {
2846 | const [selectedRow, setSelectedRow] = useState(0);
2847 | const rowRef = useRef<HTMLTableRowElement>(null);
2848 |
2849 | const selectRow = (index: number) => {
2850 | // Must update DOM before scrolling
2851 | flushSync(() => {
2852 | setSelectedRow(index);
2853 | });
2854 |
2855 | // DOM is updated, can now scroll
2856 | rowRef.current?.scrollIntoView();
2857 | };
2858 | }
2859 | ```
2860 |
2861 | ### Common Pattern in XMLUI
2862 |
2863 | **Form reset with focus:**
2864 | ```tsx
2865 | function Form({ onSubmit }: Props) {
2866 | const doReset = () => {
2867 | // Reset all fields
2868 | };
2869 |
2870 | const handleSuccess = () => {
2871 | const prevFocused = document.activeElement;
2872 |
2873 | // Force synchronous reset before restoring focus
2874 | flushSync(() => {
2875 | doReset();
2876 | });
2877 |
2878 | // DOM is reset, restore focus
2879 | if (prevFocused && typeof (prevFocused as HTMLElement).focus === 'function') {
2880 | (prevFocused as HTMLElement).focus();
2881 | }
2882 | };
2883 | }
2884 | ```
2885 |
2886 | **Table with immediate scroll:**
2887 | ```tsx
2888 | function DataTable({ data }: Props) {
2889 | const handleSort = (column: string) => {
2890 | // Update sort synchronously before scrolling
2891 | flushSync(() => {
2892 | setSortColumn(column);
2893 | setSortedData(sortData(data, column));
2894 | });
2895 |
2896 | // Table is re-rendered, can scroll to top
2897 | tableRef.current?.scrollTo(0, 0);
2898 | };
2899 | }
2900 | ```
2901 |
2902 | ### Why flushSync Exists
2903 |
2904 | ```tsx
2905 | // ❌ Problem: Without flushSync
2906 | function Component() {
2907 | const [text, setText] = useState('');
2908 |
2909 | const update = () => {
2910 | setText('new value');
2911 | // DOM not updated yet!
2912 | inputRef.current?.focus(); // Focuses old state
2913 | };
2914 | }
2915 |
2916 | // ✅ Solution: With flushSync
2917 | function Component() {
2918 | const [text, setText] = useState('');
2919 |
2920 | const update = () => {
2921 | flushSync(() => {
2922 | setText('new value');
2923 | });
2924 | // DOM is updated
2925 | inputRef.current?.focus(); // Focuses new state
2926 | };
2927 | }
2928 | ```
2929 |
2930 | ### When to Use flushSync
2931 |
2932 | **Use `flushSync` when:**
2933 | - Need DOM measurements after state change
2934 | - Synchronizing with third-party libraries
2935 | - Scrolling after state update
2936 | - Focus management after state change
2937 |
2938 | **Don't use when:**
2939 | - Normal state updates (let React batch)
2940 | - Performance-critical code paths
2941 | - You can solve it with `useLayoutEffect`
2942 | - Inside render (not allowed)
2943 |
2944 | **Performance impact:**
2945 | ```tsx
2946 | // ❌ BAD - Multiple flushSync calls
2947 | data.forEach(item => {
2948 | flushSync(() => {
2949 | processItem(item); // Forces re-render each time
2950 | });
2951 | });
2952 |
2953 | // ✅ GOOD - Single batch update
2954 | const processedItems = data.map(processItem);
2955 | flushSync(() => {
2956 | setItems(processedItems); // Single re-render
2957 | });
2958 | ```
2959 |
2960 | ---
2961 |
2962 | ## `createRoot` - React 18 Root API
2963 |
2964 | **Purpose:** Create a root to render React components into a DOM container (React 18+).
2965 |
2966 | **Syntax:** `const root = createRoot(container); root.render(<App />)`
2967 |
2968 | ### Basic Usage
2969 |
2970 | ```tsx
2971 | import { createRoot } from 'react-dom/client';
2972 |
2973 | // Old way (React 17)
2974 | ReactDOM.render(<App />, document.getElementById('root'));
2975 |
2976 | // New way (React 18+)
2977 | const root = createRoot(document.getElementById('root')!);
2978 | root.render(<App />);
2979 | ```
2980 |
2981 | ### With TypeScript
2982 |
2983 | ```tsx
2984 | import { createRoot } from 'react-dom/client';
2985 |
2986 | const container = document.getElementById('root');
2987 | if (!container) {
2988 | throw new Error('Root element not found');
2989 | }
2990 |
2991 | const root = createRoot(container);
2992 | root.render(<App />);
2993 | ```
2994 |
2995 | ### Unmounting
2996 |
2997 | ```tsx
2998 | const root = createRoot(container);
2999 | root.render(<App />);
3000 |
3001 | // Later: unmount
3002 | root.unmount();
3003 | ```
3004 |
3005 | ### Common Pattern in XMLUI
3006 |
3007 | **Standalone app rendering:**
3008 | ```tsx
3009 | function renderStandaloneApp(rootElement: HTMLElement) {
3010 | let contentRoot: Root;
3011 |
3012 | if (!contentRoot) {
3013 | contentRoot = createRoot(rootElement);
3014 | }
3015 |
3016 | contentRoot.render(
3017 | <StrictMode>
3018 | <App />
3019 | </StrictMode>
3020 | );
3021 |
3022 | return contentRoot;
3023 | }
3024 | ```
3025 |
3026 | **Shadow DOM rendering:**
3027 | ```tsx
3028 | function NestedApp({ children }: Props) {
3029 | const shadowRef = useRef<ShadowRoot>(null);
3030 | const contentRootRef = useRef<Root | null>(null);
3031 |
3032 | useEffect(() => {
3033 | if (shadowRef.current && !contentRootRef.current) {
3034 | // Create root in shadow DOM
3035 | contentRootRef.current = createRoot(shadowRef.current);
3036 | contentRootRef.current.render(<NestedContent />);
3037 | }
3038 |
3039 | return () => {
3040 | contentRootRef.current?.unmount();
3041 | };
3042 | }, []);
3043 | }
3044 | ```
3045 |
3046 | ### Benefits of createRoot (React 18)
3047 |
3048 | 1. **Automatic batching** - All updates batched, even in promises/setTimeout
3049 | 2. **Concurrent features** - Enables `useTransition`, `useDeferredValue`, etc.
3050 | 3. **Improved hydration** - Better SSR support
3051 | 4. **Suspense improvements** - Better streaming SSR
3052 |
3053 | ### When to Use createRoot
3054 |
3055 | **Use `createRoot` when:**
3056 | - Starting a new React 18+ application
3057 | - Rendering React into a DOM container
3058 | - Creating multiple roots in one app
3059 | - Rendering into shadow DOM
3060 |
3061 | **Migration from React 17:**
3062 | ```tsx
3063 | // React 17
3064 | import ReactDOM from 'react-dom';
3065 | ReactDOM.render(<App />, container);
3066 | ReactDOM.unmountComponentAtNode(container);
3067 |
3068 | // React 18
3069 | import { createRoot } from 'react-dom/client';
3070 | const root = createRoot(container);
3071 | root.render(<App />);
3072 | root.unmount();
3073 | ```
3074 |
3075 | ---
3076 |
3077 | ## React Accessibility Patterns
3078 |
3079 | ### ARIA Attributes Pattern
3080 |
3081 | **Key ARIA attributes:** `role`, `aria-label`, `aria-labelledby`, `aria-describedby`, `aria-hidden`, `aria-live`, `aria-expanded`, `aria-selected`, `aria-disabled`, `aria-current`
3082 |
3083 | **Common patterns:**
3084 |
3085 | ```tsx
3086 | // Icon button with accessible label
3087 | <button onClick={onClick} aria-label="Close dialog">
3088 | <Icon name="close" aria-hidden="true" />
3089 | </button>
3090 |
3091 | // Form field with error/help text
3092 | function FormField({ label, error, helpText }: Props) {
3093 | const id = useId();
3094 | return (
3095 | <>
3096 | <label htmlFor={id}>{label}</label>
3097 | <input id={id} aria-describedby={`${id}-desc`} aria-invalid={!!error} />
3098 | <span id={`${id}-desc`} role={error ? "alert" : undefined}>
3099 | {error || helpText}
3100 | </span>
3101 | </>
3102 | );
3103 | }
3104 |
3105 | // Accordion/expandable section
3106 | <button aria-expanded={isOpen} aria-controls={contentId}>
3107 | {title}
3108 | </button>
3109 | <div id={contentId} hidden={!isOpen} role="region">
3110 | {children}
3111 | </div>
3112 |
3113 | // Live region for announcements
3114 | <div role="status" aria-live="polite" aria-atomic="true">
3115 | {message}
3116 | </div>
3117 |
3118 | // Modal dialog
3119 | <div role="dialog" aria-modal="true" aria-labelledby={titleId}>
3120 | <h2 id={titleId}>{title}</h2>
3121 | {children}
3122 | </div>
3123 |
3124 | // Tab navigation
3125 | <div role="tablist">
3126 | <button role="tab" aria-selected={isActive} aria-controls={panelId}>
3127 | {label}
3128 | </button>
3129 | </div>
3130 | <div role="tabpanel" id={panelId} aria-labelledby={tabId}>
3131 | {content}
3132 | </div>
3133 | ```
3134 |
3135 | **Rules:** Use semantic HTML first, add ARIA only when needed, keep attributes in sync with state, test with screen readers.
3136 |
3137 | ---
3138 |
3139 | ### Focus Management Pattern
3140 |
3141 | **Common scenarios:** Auto-focus on mount, focus traps in modals, focus restoration, roving tab index.
3142 |
3143 | ```tsx
3144 | // Auto-focus first element in dialog
3145 | function Dialog({ isOpen }: Props) {
3146 | const buttonRef = useRef<HTMLButtonElement>(null);
3147 |
3148 | useEffect(() => {
3149 | if (isOpen) buttonRef.current?.focus();
3150 | }, [isOpen]);
3151 |
3152 | return <button ref={buttonRef}>Close</button>;
3153 | }
3154 |
3155 | // Focus trap + restoration in modal
3156 | function Modal({ isOpen, onClose, children }: Props) {
3157 | const modalRef = useRef<HTMLDivElement>(null);
3158 | const restoreFocusRef = useRef<HTMLElement | null>(null);
3159 |
3160 | useEffect(() => {
3161 | if (!isOpen) return;
3162 |
3163 | restoreFocusRef.current = document.activeElement as HTMLElement;
3164 | modalRef.current?.querySelector<HTMLElement>('button')?.focus();
3165 |
3166 | return () => restoreFocusRef.current?.focus();
3167 | }, [isOpen]);
3168 |
3169 | const handleKeyDown = (e: React.KeyboardEvent) => {
3170 | if (e.key === 'Escape') onClose();
3171 |
3172 | // Trap Tab key
3173 | if (e.key === 'Tab') {
3174 | const focusable = modalRef.current?.querySelectorAll<HTMLElement>(
3175 | 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
3176 | );
3177 | if (!focusable?.length) return;
3178 |
3179 | const first = focusable[0];
3180 | const last = focusable[focusable.length - 1];
3181 |
3182 | if (e.shiftKey && document.activeElement === first) {
3183 | e.preventDefault();
3184 | last.focus();
3185 | } else if (!e.shiftKey && document.activeElement === last) {
3186 | e.preventDefault();
3187 | first.focus();
3188 | }
3189 | }
3190 | };
3191 |
3192 | return (
3193 | <div ref={modalRef} role="dialog" aria-modal="true" onKeyDown={handleKeyDown}>
3194 | {children}
3195 | </div>
3196 | );
3197 | }
3198 |
3199 | // Focus after delete action
3200 | function DeleteButton({ itemId, onDelete }: Props) {
3201 | const handleDelete = () => {
3202 | const current = document.getElementById(`item-${itemId}`);
3203 | const next = (current?.nextElementSibling || current?.previousElementSibling)
3204 | ?.querySelector('button') as HTMLElement;
3205 |
3206 | onDelete(itemId);
3207 | setTimeout(() => next?.focus(), 0);
3208 | };
3209 |
3210 | return <button onClick={handleDelete}>Delete</button>;
3211 | }
3212 |
3213 | // Roving tab index for lists
3214 | function RadioGroup({ options, value, onChange }: Props) {
3215 | const [focusedIndex, setFocusedIndex] = useState(0);
3216 |
3217 | const handleKeyDown = (e: React.KeyboardEvent, index: number) => {
3218 | let newIndex = index;
3219 | if (e.key === 'ArrowDown') newIndex = (index + 1) % options.length;
3220 | if (e.key === 'ArrowUp') newIndex = index === 0 ? options.length - 1 : index - 1;
3221 | if (e.key === 'Home') newIndex = 0;
3222 | if (e.key === 'End') newIndex = options.length - 1;
3223 |
3224 | if (newIndex !== index) {
3225 | e.preventDefault();
3226 | setFocusedIndex(newIndex);
3227 | }
3228 | };
3229 |
3230 | return (
3231 | <div role="radiogroup">
3232 | {options.map((opt, i) => (
3233 | <div
3234 | key={opt.id}
3235 | role="radio"
3236 | aria-checked={value === opt.id}
3237 | tabIndex={focusedIndex === i ? 0 : -1}
3238 | onClick={() => onChange(opt.id)}
3239 | onKeyDown={(e) => handleKeyDown(e, i)}
3240 | onFocus={() => setFocusedIndex(i)}
3241 | >
3242 | {opt.label}
3243 | </div>
3244 | ))}
3245 | </div>
3246 | );
3247 | }
3248 | ```
3249 |
3250 | **Rules:** Always restore focus when closing modals, trap focus within modal contexts, use `focus-visible` for keyboard-only indicators, test thoroughly.
3251 |
3252 | ---
3253 |
3254 | ### Keyboard Navigation Pattern
3255 |
3256 | **Standard keyboard shortcuts:** Escape (close), Tab/Shift+Tab (navigate), Arrow keys (move focus), Enter/Space (activate), Home/End (first/last).
3257 |
3258 | ```tsx
3259 | // Dropdown with full keyboard support
3260 | function Dropdown({ trigger, items, onSelect }: Props) {
3261 | const [isOpen, setIsOpen] = useState(false);
3262 | const [focusedIndex, setFocusedIndex] = useState(0);
3263 | const itemsRef = useRef<(HTMLButtonElement | null)[]>([]);
3264 |
3265 | const handleKeyDown = (e: React.KeyboardEvent) => {
3266 | switch (e.key) {
3267 | case 'ArrowDown':
3268 | e.preventDefault();
3269 | if (!isOpen) {
3270 | setIsOpen(true);
3271 | } else {
3272 | const next = (focusedIndex + 1) % items.length;
3273 | setFocusedIndex(next);
3274 | itemsRef.current[next]?.focus();
3275 | }
3276 | break;
3277 | case 'ArrowUp':
3278 | e.preventDefault();
3279 | if (isOpen) {
3280 | const prev = focusedIndex === 0 ? items.length - 1 : focusedIndex - 1;
3281 | setFocusedIndex(prev);
3282 | itemsRef.current[prev]?.focus();
3283 | }
3284 | break;
3285 | case 'Escape':
3286 | e.preventDefault();
3287 | setIsOpen(false);
3288 | break;
3289 | case 'Enter':
3290 | case ' ':
3291 | e.preventDefault();
3292 | if (isOpen) {
3293 | onSelect(items[focusedIndex]);
3294 | setIsOpen(false);
3295 | } else {
3296 | setIsOpen(true);
3297 | }
3298 | break;
3299 | }
3300 | };
3301 |
3302 | return (
3303 | <div onKeyDown={handleKeyDown}>
3304 | <button onClick={() => setIsOpen(!isOpen)} aria-expanded={isOpen}>
3305 | {trigger}
3306 | </button>
3307 | {isOpen && (
3308 | <ul role="menu">
3309 | {items.map((item, i) => (
3310 | <li key={item.id} role="none">
3311 | <button
3312 | ref={el => itemsRef.current[i] = el}
3313 | role="menuitem"
3314 | onClick={() => { onSelect(item); setIsOpen(false); }}
3315 | onFocus={() => setFocusedIndex(i)}
3316 | >
3317 | {item.label}
3318 | </button>
3319 | </li>
3320 | ))}
3321 | </ul>
3322 | )}
3323 | </div>
3324 | );
3325 | }
3326 |
3327 | // Global keyboard shortcuts hook
3328 | function useKeyboardShortcuts(shortcuts: Record<string, () => void>) {
3329 | useEffect(() => {
3330 | const handleKeyDown = (e: KeyboardEvent) => {
3331 | const keys: string[] = [];
3332 | if (e.ctrlKey || e.metaKey) keys.push('Ctrl');
3333 | if (e.shiftKey) keys.push('Shift');
3334 | if (e.altKey) keys.push('Alt');
3335 | keys.push(e.key.toUpperCase());
3336 |
3337 | const handler = shortcuts[keys.join('+')];
3338 | if (handler) {
3339 | e.preventDefault();
3340 | handler();
3341 | }
3342 | };
3343 |
3344 | document.addEventListener('keydown', handleKeyDown);
3345 | return () => document.removeEventListener('keydown', handleKeyDown);
3346 | }, [shortcuts]);
3347 | }
3348 |
3349 | // Usage: Editor with shortcuts
3350 | function Editor() {
3351 | useKeyboardShortcuts({
3352 | 'Ctrl+S': handleSave,
3353 | 'Ctrl+Z': handleUndo,
3354 | 'Ctrl+Shift+Z': handleRedo,
3355 | });
3356 | return <div>Editor</div>;
3357 | }
3358 |
3359 | // Data table with arrow key navigation
3360 | function DataTable({ columns, rows }: Props) {
3361 | const [focusedCell, setFocusedCell] = useState({ row: 0, col: 0 });
3362 | const cellRefs = useRef<(HTMLTableCellElement | null)[][]>([]);
3363 |
3364 | const handleKeyDown = (e: React.KeyboardEvent, rowIndex: number, colIndex: number) => {
3365 | let newRow = rowIndex, newCol = colIndex;
3366 |
3367 | if (e.key === 'ArrowUp') newRow = Math.max(0, rowIndex - 1);
3368 | if (e.key === 'ArrowDown') newRow = Math.min(rows.length - 1, rowIndex + 1);
3369 | if (e.key === 'ArrowLeft') newCol = Math.max(0, colIndex - 1);
3370 | if (e.key === 'ArrowRight') newCol = Math.min(columns.length - 1, colIndex + 1);
3371 | if (e.key === 'Home') newCol = 0;
3372 | if (e.key === 'End') newCol = columns.length - 1;
3373 |
3374 | if (newRow !== rowIndex || newCol !== colIndex) {
3375 | e.preventDefault();
3376 | setFocusedCell({ row: newRow, col: newCol });
3377 | cellRefs.current[newRow]?.[newCol]?.focus();
3378 | }
3379 | };
3380 |
3381 | return (
3382 | <table>
3383 | <tbody>
3384 | {rows.map((row, ri) => (
3385 | <tr key={row.id}>
3386 | {columns.map((col, ci) => (
3387 | <td
3388 | key={col.id}
3389 | ref={el => {
3390 | if (!cellRefs.current[ri]) cellRefs.current[ri] = [];
3391 | cellRefs.current[ri][ci] = el;
3392 | }}
3393 | tabIndex={focusedCell.row === ri && focusedCell.col === ci ? 0 : -1}
3394 | onKeyDown={e => handleKeyDown(e, ri, ci)}
3395 | onFocus={() => setFocusedCell({ row: ri, col: ci })}
3396 | >
3397 | {row[col.id]}
3398 | </td>
3399 | ))}
3400 | </tr>
3401 | ))}
3402 | </tbody>
3403 | </table>
3404 | );
3405 | }
3406 | ```
3407 |
3408 | **Rules:** Support standard shortcuts (Escape, Tab, Arrows), don't override browser/OS shortcuts, provide visible focus feedback, test keyboard-only navigation.
3409 |
3410 | ---
3411 |
3412 | ### Accessibility Best Practices Summary
3413 |
3414 | **Key principles:**
3415 | - Use semantic HTML first (`<button>`, `<nav>`, `<main>`)
3416 | - Add ARIA only when semantic HTML isn't enough
3417 | - All interactive elements must be keyboard accessible
3418 | - Provide visible focus indicators (use `:focus-visible`)
3419 | - Keep ARIA attributes in sync with visual state
3420 | - Restore focus after closing modals/dialogs
3421 | - Test with screen readers and keyboard only
3422 |
3423 | **Testing checklist:**
3424 | - [ ] Navigate entire app with keyboard only
3425 | - [ ] Focus indicators visible and high contrast
3426 | - [ ] Screen reader announces all content correctly
3427 | - [ ] Color not the only state indicator
3428 | - [ ] Text contrast ≥ 4.5:1 for normal text
3429 | - [ ] Interactive elements have accessible names
3430 | - [ ] Form fields have associated labels
3431 | - [ ] Error messages are announced
3432 |
3433 | **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)
3434 |
```