This is page 168 of 180. Use http://codebase.md/xmlui-org/xmlui/mockApiDef.js?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-optimized.yml │ ├── deploy-docs-optimized.yml │ ├── deploy-docs.yml │ ├── prepare-versions.yml │ ├── release-packages.yml │ ├── run-all-tests.yml │ └── run-smoke-tests.yml ├── .gitignore ├── .prettierrc.js ├── .vscode │ ├── launch.json │ └── settings.json ├── blog │ ├── .gitignore │ ├── .gitkeep │ ├── CHANGELOG.md │ ├── extensions.ts │ ├── index.html │ ├── index.ts │ ├── package.json │ ├── public │ │ ├── blog │ │ │ ├── images │ │ │ │ ├── blog-page-component.png │ │ │ │ ├── blog-scrabble.png │ │ │ │ ├── integrated-blog-search.png │ │ │ │ └── lorem-ipsum.png │ │ │ ├── lorem-ipsum.md │ │ │ ├── newest-post.md │ │ │ ├── older-post.md │ │ │ └── welcome-to-the-xmlui-blog.md │ │ ├── mockServiceWorker.js │ │ ├── resources │ │ │ ├── favicon.ico │ │ │ ├── files │ │ │ │ └── for-download │ │ │ │ └── xmlui │ │ │ │ └── xmlui-standalone.umd.js │ │ │ ├── github.svg │ │ │ ├── llms.txt │ │ │ ├── logo-dark.svg │ │ │ ├── logo.svg │ │ │ ├── pg-popout.svg │ │ │ └── xmlui-logo.svg │ │ ├── serve.json │ │ └── web.config │ ├── scripts │ │ ├── download-latest-xmlui.js │ │ ├── generate-rss.js │ │ ├── get-releases.js │ │ └── utils.js │ ├── src │ │ ├── components │ │ │ ├── BlogOverview.xmlui │ │ │ ├── BlogPage.xmlui │ │ │ └── PageNotFound.xmlui │ │ ├── config.ts │ │ ├── Main.xmlui │ │ └── themes │ │ ├── docs-theme.ts │ │ ├── earthtone.ts │ │ ├── xmlui-gray-on-default.ts │ │ ├── xmlui-green-on-default.ts │ │ └── xmlui-orange-on-default.ts │ └── tsconfig.json ├── CONTRIBUTING.md ├── docs │ ├── .gitignore │ ├── CHANGELOG.md │ ├── ComponentRefLinks.txt │ ├── content │ │ ├── _meta.json │ │ ├── components │ │ │ ├── _meta.json │ │ │ ├── _overview.md │ │ │ ├── APICall.md │ │ │ ├── App.md │ │ │ ├── AppHeader.md │ │ │ ├── AppState.md │ │ │ ├── AutoComplete.md │ │ │ ├── Avatar.md │ │ │ ├── Backdrop.md │ │ │ ├── Badge.md │ │ │ ├── BarChart.md │ │ │ ├── Bookmark.md │ │ │ ├── Breakout.md │ │ │ ├── Button.md │ │ │ ├── Card.md │ │ │ ├── Carousel.md │ │ │ ├── ChangeListener.md │ │ │ ├── Checkbox.md │ │ │ ├── CHStack.md │ │ │ ├── ColorPicker.md │ │ │ ├── Column.md │ │ │ ├── ContentSeparator.md │ │ │ ├── CVStack.md │ │ │ ├── DataSource.md │ │ │ ├── DateInput.md │ │ │ ├── DatePicker.md │ │ │ ├── DonutChart.md │ │ │ ├── DropdownMenu.md │ │ │ ├── EmojiSelector.md │ │ │ ├── ExpandableItem.md │ │ │ ├── FileInput.md │ │ │ ├── FileUploadDropZone.md │ │ │ ├── FlowLayout.md │ │ │ ├── Footer.md │ │ │ ├── Form.md │ │ │ ├── FormItem.md │ │ │ ├── FormSection.md │ │ │ ├── Fragment.md │ │ │ ├── H1.md │ │ │ ├── H2.md │ │ │ ├── H3.md │ │ │ ├── H4.md │ │ │ ├── H5.md │ │ │ ├── H6.md │ │ │ ├── Heading.md │ │ │ ├── HSplitter.md │ │ │ ├── HStack.md │ │ │ ├── Icon.md │ │ │ ├── IFrame.md │ │ │ ├── Image.md │ │ │ ├── Items.md │ │ │ ├── LabelList.md │ │ │ ├── Legend.md │ │ │ ├── LineChart.md │ │ │ ├── Link.md │ │ │ ├── List.md │ │ │ ├── Logo.md │ │ │ ├── Markdown.md │ │ │ ├── MenuItem.md │ │ │ ├── MenuSeparator.md │ │ │ ├── ModalDialog.md │ │ │ ├── NavGroup.md │ │ │ ├── NavLink.md │ │ │ ├── NavPanel.md │ │ │ ├── NoResult.md │ │ │ ├── NumberBox.md │ │ │ ├── Option.md │ │ │ ├── Page.md │ │ │ ├── PageMetaTitle.md │ │ │ ├── Pages.md │ │ │ ├── Pagination.md │ │ │ ├── PasswordInput.md │ │ │ ├── PieChart.md │ │ │ ├── ProgressBar.md │ │ │ ├── Queue.md │ │ │ ├── RadioGroup.md │ │ │ ├── RealTimeAdapter.md │ │ │ ├── Redirect.md │ │ │ ├── Select.md │ │ │ ├── Slider.md │ │ │ ├── Slot.md │ │ │ ├── SpaceFiller.md │ │ │ ├── Spinner.md │ │ │ ├── Splitter.md │ │ │ ├── Stack.md │ │ │ ├── StickyBox.md │ │ │ ├── SubMenuItem.md │ │ │ ├── Switch.md │ │ │ ├── TabItem.md │ │ │ ├── Table.md │ │ │ ├── TableOfContents.md │ │ │ ├── Tabs.md │ │ │ ├── Text.md │ │ │ ├── TextArea.md │ │ │ ├── TextBox.md │ │ │ ├── Theme.md │ │ │ ├── TimeInput.md │ │ │ ├── Timer.md │ │ │ ├── ToneChangerButton.md │ │ │ ├── ToneSwitch.md │ │ │ ├── Tooltip.md │ │ │ ├── Tree.md │ │ │ ├── VSplitter.md │ │ │ ├── VStack.md │ │ │ ├── xmlui-animations │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ ├── Animation.md │ │ │ │ ├── FadeAnimation.md │ │ │ │ ├── FadeInAnimation.md │ │ │ │ ├── FadeOutAnimation.md │ │ │ │ ├── ScaleAnimation.md │ │ │ │ └── SlideInAnimation.md │ │ │ ├── xmlui-pdf │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ └── Pdf.md │ │ │ ├── xmlui-spreadsheet │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ └── Spreadsheet.md │ │ │ └── xmlui-website-blocks │ │ │ ├── _meta.json │ │ │ ├── _overview.md │ │ │ ├── Carousel.md │ │ │ ├── HelloMd.md │ │ │ ├── HeroSection.md │ │ │ └── ScrollToTop.md │ │ └── extensions │ │ ├── _meta.json │ │ ├── xmlui-animations │ │ │ ├── _meta.json │ │ │ ├── _overview.md │ │ │ ├── Animation.md │ │ │ ├── FadeAnimation.md │ │ │ ├── FadeInAnimation.md │ │ │ ├── FadeOutAnimation.md │ │ │ ├── ScaleAnimation.md │ │ │ └── SlideInAnimation.md │ │ └── xmlui-website-blocks │ │ ├── _meta.json │ │ ├── _overview.md │ │ ├── Carousel.md │ │ ├── HelloMd.md │ │ ├── HeroSection.md │ │ └── ScrollToTop.md │ ├── extensions.ts │ ├── index.html │ ├── index.ts │ ├── package.json │ ├── public │ │ ├── feed.rss │ │ ├── mockServiceWorker.js │ │ ├── pages │ │ │ ├── _meta.json │ │ │ ├── app-structure.md │ │ │ ├── build-editor-component.md │ │ │ ├── build-hello-world-component.md │ │ │ ├── components-intro.md │ │ │ ├── context-variables.md │ │ │ ├── forms.md │ │ │ ├── globals.md │ │ │ ├── glossary.md │ │ │ ├── helper-tags.md │ │ │ ├── hosted-deployment.md │ │ │ ├── howto │ │ │ │ ├── assign-a-complex-json-literal-to-a-component-variable.md │ │ │ │ ├── chain-a-refetch.md │ │ │ │ ├── debug-a-component.md │ │ │ │ ├── delay-a-datasource-until-another-datasource-is-ready.md │ │ │ │ ├── delegate-a-method.md │ │ │ │ ├── do-custom-form-validation.md │ │ │ │ ├── expose-a-method-from-a-component.md │ │ │ │ ├── filter-and-transform-data-from-an-api.md │ │ │ │ ├── group-items-in-list-by-a-property.md │ │ │ │ ├── handle-background-operations.md │ │ │ │ ├── hide-an-element-until-its-datasource-is-ready.md │ │ │ │ ├── make-a-set-of-equal-width-cards.md │ │ │ │ ├── make-a-table-responsive.md │ │ │ │ ├── modify-a-value-reported-in-a-column.md │ │ │ │ ├── paginate-a-list.md │ │ │ │ ├── pass-data-to-a-modal-dialog.md │ │ │ │ ├── react-to-button-click-not-keystrokes.md │ │ │ │ ├── set-the-initial-value-of-a-select-from-fetched-data.md │ │ │ │ ├── share-a-modaldialog-across-components.md │ │ │ │ ├── sync-selections-between-table-and-list-views.md │ │ │ │ ├── update-ui-optimistically.md │ │ │ │ ├── use-built-in-form-validation.md │ │ │ │ └── use-the-same-modaldialog-to-add-or-edit.md │ │ │ ├── howto.md │ │ │ ├── intro.md │ │ │ ├── layout.md │ │ │ ├── markup.md │ │ │ ├── mcp.md │ │ │ ├── modal-dialogs.md │ │ │ ├── news-and-reviews.md │ │ │ ├── reactive-intro.md │ │ │ ├── refactoring.md │ │ │ ├── routing-and-links.md │ │ │ ├── samples │ │ │ │ ├── color-palette.xmlui │ │ │ │ ├── color-values.xmlui │ │ │ │ ├── shadow-sizes.xmlui │ │ │ │ ├── spacing-sizes.xmlui │ │ │ │ ├── swatch.xmlui │ │ │ │ ├── theme-gallery-brief.xmlui │ │ │ │ └── theme-gallery.xmlui │ │ │ ├── scoping.md │ │ │ ├── scripting.md │ │ │ ├── styles-and-themes │ │ │ │ ├── common-units.md │ │ │ │ ├── layout-props.md │ │ │ │ ├── theme-variable-defaults.md │ │ │ │ ├── theme-variables.md │ │ │ │ └── themes.md │ │ │ ├── template-properties.md │ │ │ ├── test.md │ │ │ ├── tutorial-01.md │ │ │ ├── tutorial-02.md │ │ │ ├── tutorial-03.md │ │ │ ├── tutorial-04.md │ │ │ ├── tutorial-05.md │ │ │ ├── tutorial-06.md │ │ │ ├── tutorial-07.md │ │ │ ├── tutorial-08.md │ │ │ ├── tutorial-09.md │ │ │ ├── tutorial-10.md │ │ │ ├── tutorial-11.md │ │ │ ├── tutorial-12.md │ │ │ ├── universal-properties.md │ │ │ ├── user-defined-components.md │ │ │ ├── vscode.md │ │ │ ├── working-with-markdown.md │ │ │ ├── working-with-text.md │ │ │ ├── xmlui-animations │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ ├── Animation.md │ │ │ │ ├── FadeAnimation.md │ │ │ │ ├── FadeInAnimation.md │ │ │ │ ├── FadeOutAnimation.md │ │ │ │ ├── ScaleAnimation.md │ │ │ │ └── SlideInAnimation.md │ │ │ ├── xmlui-charts │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ ├── BarChart.md │ │ │ │ ├── DonutChart.md │ │ │ │ ├── LabelList.md │ │ │ │ ├── Legend.md │ │ │ │ ├── LineChart.md │ │ │ │ └── PieChart.md │ │ │ ├── xmlui-pdf │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ └── Pdf.md │ │ │ └── xmlui-spreadsheet │ │ │ ├── _meta.json │ │ │ ├── _overview.md │ │ │ └── Spreadsheet.md │ │ ├── resources │ │ │ ├── devdocs │ │ │ │ ├── debug-proxy-object-2.png │ │ │ │ ├── debug-proxy-object.png │ │ │ │ ├── table_editor_01.png │ │ │ │ ├── table_editor_02.png │ │ │ │ ├── table_editor_03.png │ │ │ │ ├── table_editor_04.png │ │ │ │ ├── table_editor_05.png │ │ │ │ ├── table_editor_06.png │ │ │ │ ├── table_editor_07.png │ │ │ │ ├── table_editor_08.png │ │ │ │ ├── table_editor_09.png │ │ │ │ ├── table_editor_10.png │ │ │ │ ├── table_editor_11.png │ │ │ │ ├── table-editor-01.png │ │ │ │ ├── table-editor-02.png │ │ │ │ ├── table-editor-03.png │ │ │ │ ├── table-editor-04.png │ │ │ │ ├── table-editor-06.png │ │ │ │ ├── table-editor-07.png │ │ │ │ ├── table-editor-08.png │ │ │ │ ├── table-editor-09.png │ │ │ │ └── xmlui-rendering-of-tiptap-markdown.png │ │ │ ├── favicon.ico │ │ │ ├── files │ │ │ │ ├── clients.json │ │ │ │ ├── daily-revenue.json │ │ │ │ ├── dashboard-stats.json │ │ │ │ ├── demo.xmlui │ │ │ │ ├── demo.xmlui.xs │ │ │ │ ├── downloads │ │ │ │ │ └── downloads.json │ │ │ │ ├── for-download │ │ │ │ │ ├── index-with-api.html │ │ │ │ │ ├── index.html │ │ │ │ │ ├── mockApi.js │ │ │ │ │ ├── start-darwin.sh │ │ │ │ │ ├── start-linux.sh │ │ │ │ │ ├── start.bat │ │ │ │ │ └── xmlui │ │ │ │ │ └── xmlui-standalone.umd.js │ │ │ │ ├── getting-started │ │ │ │ │ ├── cl-tutorial-final.zip │ │ │ │ │ ├── cl-tutorial.zip │ │ │ │ │ ├── cl-tutorial2.zip │ │ │ │ │ ├── cl-tutorial3.zip │ │ │ │ │ ├── cl-tutorial4.zip │ │ │ │ │ ├── cl-tutorial5.zip │ │ │ │ │ ├── cl-tutorial6.zip │ │ │ │ │ ├── getting-started.zip │ │ │ │ │ ├── hello-xmlui.zip │ │ │ │ │ ├── xmlui-empty.zip │ │ │ │ │ └── xmlui-starter.zip │ │ │ │ ├── howto │ │ │ │ │ └── component-icons │ │ │ │ │ └── up-arrow.svg │ │ │ │ ├── invoices.json │ │ │ │ ├── monthly-status.json │ │ │ │ ├── news-and-reviews.json │ │ │ │ ├── products.json │ │ │ │ ├── releases.json │ │ │ │ ├── tutorials │ │ │ │ │ ├── datasource │ │ │ │ │ │ └── api.ts │ │ │ │ │ └── p2do │ │ │ │ │ ├── api.ts │ │ │ │ │ └── todo-logo.svg │ │ │ │ └── xmlui.json │ │ │ ├── github.svg │ │ │ ├── images │ │ │ │ ├── apiaction-tutorial │ │ │ │ │ ├── add-success.png │ │ │ │ │ ├── apiaction-param.png │ │ │ │ │ ├── change-completed.png │ │ │ │ │ ├── change-in-progress.png │ │ │ │ │ ├── confirm-delete.png │ │ │ │ │ ├── data-error.png │ │ │ │ │ ├── data-progress.png │ │ │ │ │ ├── data-success.png │ │ │ │ │ ├── display-1.png │ │ │ │ │ ├── item-deleted.png │ │ │ │ │ ├── item-updated.png │ │ │ │ │ ├── missing-api-key.png │ │ │ │ │ ├── new-item-added.png │ │ │ │ │ └── test-message.png │ │ │ │ ├── chat-api │ │ │ │ │ └── domain-model.svg │ │ │ │ ├── components │ │ │ │ │ ├── image │ │ │ │ │ │ └── breakfast.jpg │ │ │ │ │ ├── markdown │ │ │ │ │ │ └── colors.png │ │ │ │ │ └── modal │ │ │ │ │ ├── deep_link_dialog_1.jpg │ │ │ │ │ └── deep_link_dialog_2.jpg │ │ │ │ ├── create-apps │ │ │ │ │ ├── collapsed-vertical.png │ │ │ │ │ ├── using-forms-warning-dialog.png │ │ │ │ │ └── using-forms.png │ │ │ │ ├── datasource-tutorial │ │ │ │ │ ├── data-with-header.png │ │ │ │ │ ├── filtered-data.png │ │ │ │ │ ├── filtered-items.png │ │ │ │ │ ├── initial-page-items.png │ │ │ │ │ ├── list-items.png │ │ │ │ │ ├── next-page-items.png │ │ │ │ │ ├── no-data.png │ │ │ │ │ ├── pagination-1.jpg │ │ │ │ │ ├── pagination-1.png │ │ │ │ │ ├── polling-1.png │ │ │ │ │ ├── refetch-data.png │ │ │ │ │ ├── slow-loading.png │ │ │ │ │ ├── test-message.png │ │ │ │ │ ├── Thumbs.db │ │ │ │ │ ├── unconventional-data.png │ │ │ │ │ └── unfiltered-items.png │ │ │ │ ├── flower.jpg │ │ │ │ ├── get-started │ │ │ │ │ ├── add-new-contact.png │ │ │ │ │ ├── app-modified.png │ │ │ │ │ ├── app-start.png │ │ │ │ │ ├── app-with-boxes.png │ │ │ │ │ ├── app-with-toast.png │ │ │ │ │ ├── boilerplate-structure.png │ │ │ │ │ ├── cl-initial.png │ │ │ │ │ ├── cl-start.png │ │ │ │ │ ├── contact-counts.png │ │ │ │ │ ├── contact-dialog-title.png │ │ │ │ │ ├── contact-dialog.png │ │ │ │ │ ├── contact-menus.png │ │ │ │ │ ├── contact-predicates.png │ │ │ │ │ ├── context-menu.png │ │ │ │ │ ├── dashboard-numbers.png │ │ │ │ │ ├── default-contact-list.png │ │ │ │ │ ├── delete-contact.png │ │ │ │ │ ├── delete-task.png │ │ │ │ │ ├── detailed-template.png │ │ │ │ │ ├── edit-contact-details.png │ │ │ │ │ ├── edited-contact-saved.png │ │ │ │ │ ├── empty-sections.png │ │ │ │ │ ├── filter-completed.png │ │ │ │ │ ├── fullwidth-desktop.png │ │ │ │ │ ├── fullwidth-mobile.png │ │ │ │ │ ├── initial-table.png │ │ │ │ │ ├── items-and-badges.png │ │ │ │ │ ├── loading-message.png │ │ │ │ │ ├── new-contact-button.png │ │ │ │ │ ├── new-contact-saved.png │ │ │ │ │ ├── no-empty-sections.png │ │ │ │ │ ├── personal-todo-initial.png │ │ │ │ │ ├── piechart.png │ │ │ │ │ ├── review-today.png │ │ │ │ │ ├── rudimentary-dashboard.png │ │ │ │ │ ├── section-collapsed.png │ │ │ │ │ ├── sectioned-items.png │ │ │ │ │ ├── sections-ordered.png │ │ │ │ │ ├── spacex-list-with-links.png │ │ │ │ │ ├── spacex-list.png │ │ │ │ │ ├── start-personal-todo-1.png │ │ │ │ │ ├── submit-new-contact.png │ │ │ │ │ ├── submit-new-task.png │ │ │ │ │ ├── syntax-highlighting.png │ │ │ │ │ ├── table-with-badge.png │ │ │ │ │ ├── template-with-card.png │ │ │ │ │ ├── test-emulated-api.png │ │ │ │ │ ├── Thumbs.db │ │ │ │ │ ├── todo-logo.png │ │ │ │ │ └── xmlui-tools.png │ │ │ │ ├── HelloApp.png │ │ │ │ ├── HelloApp2.png │ │ │ │ ├── logos │ │ │ │ │ ├── xmlui1.svg │ │ │ │ │ ├── xmlui2.svg │ │ │ │ │ ├── xmlui3.svg │ │ │ │ │ ├── xmlui4.svg │ │ │ │ │ ├── xmlui5.svg │ │ │ │ │ ├── xmlui6.svg │ │ │ │ │ └── xmlui7.svg │ │ │ │ ├── pdf │ │ │ │ │ └── dummy-pdf.jpg │ │ │ │ ├── rendering-engine │ │ │ │ │ ├── AppEngine-flow.svg │ │ │ │ │ ├── Component.svg │ │ │ │ │ ├── CompoundComponent.svg │ │ │ │ │ ├── RootComponent.svg │ │ │ │ │ └── tree-with-containers.svg │ │ │ │ ├── reviewers-guide │ │ │ │ │ ├── AppEngine-flow.svg │ │ │ │ │ └── incbutton-in-action.png │ │ │ │ ├── tools │ │ │ │ │ └── boilerplate-structure.png │ │ │ │ ├── try.svg │ │ │ │ ├── tutorial │ │ │ │ │ ├── app-chat-history.png │ │ │ │ │ ├── app-content-placeholder.png │ │ │ │ │ ├── app-header-and-content.png │ │ │ │ │ ├── app-links-channel-selected.png │ │ │ │ │ ├── app-links-click.png │ │ │ │ │ ├── app-navigation.png │ │ │ │ │ ├── finished-ex01.png │ │ │ │ │ ├── finished-ex02.png │ │ │ │ │ ├── hello.png │ │ │ │ │ ├── splash-screen-advanced.png │ │ │ │ │ ├── splash-screen-after-click.png │ │ │ │ │ ├── splash-screen-centered.png │ │ │ │ │ ├── splash-screen-events.png │ │ │ │ │ ├── splash-screen-expression.png │ │ │ │ │ ├── splash-screen-reuse-after.png │ │ │ │ │ ├── splash-screen-reuse-before.png │ │ │ │ │ └── splash-screen.png │ │ │ │ └── tutorial-01.png │ │ │ ├── llms.txt │ │ │ ├── logo-dark.svg │ │ │ ├── logo.svg │ │ │ ├── pg-popout.svg │ │ │ └── xmlui-logo.svg │ │ ├── serve.json │ │ └── web.config │ ├── scripts │ │ ├── download-latest-xmlui.js │ │ ├── generate-rss.js │ │ ├── get-releases.js │ │ └── utils.js │ ├── src │ │ ├── components │ │ │ ├── BlogOverview.xmlui │ │ │ ├── BlogPage.xmlui │ │ │ ├── Boxes.xmlui │ │ │ ├── Breadcrumb.xmlui │ │ │ ├── ChangeLog.xmlui │ │ │ ├── ColorPalette.xmlui │ │ │ ├── DocumentLinks.xmlui │ │ │ ├── DocumentPage.xmlui │ │ │ ├── DocumentPageNoTOC.xmlui │ │ │ ├── Icons.xmlui │ │ │ ├── IncButton.xmlui │ │ │ ├── IncButton2.xmlui │ │ │ ├── NameValue.xmlui │ │ │ ├── PageNotFound.xmlui │ │ │ ├── PaletteItem.xmlui │ │ │ ├── Palettes.xmlui │ │ │ ├── SectionHeader.xmlui │ │ │ ├── TBD.xmlui │ │ │ ├── Test.xmlui │ │ │ ├── ThemesIntro.xmlui │ │ │ ├── ThousandThemes.xmlui │ │ │ ├── TubeStops.xmlui │ │ │ ├── TubeStops.xmlui.xs │ │ │ └── TwoColumnCode.xmlui │ │ ├── config.ts │ │ ├── Main.xmlui │ │ └── themes │ │ ├── docs-theme.ts │ │ ├── earthtone.ts │ │ ├── xmlui-gray-on-default.ts │ │ ├── xmlui-green-on-default.ts │ │ └── xmlui-orange-on-default.ts │ └── tsconfig.json ├── LICENSE ├── package-lock.json ├── package.json ├── packages │ ├── xmlui-animations │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── Animation.tsx │ │ │ ├── AnimationNative.tsx │ │ │ ├── FadeAnimation.tsx │ │ │ ├── FadeInAnimation.tsx │ │ │ ├── FadeOutAnimation.tsx │ │ │ ├── index.tsx │ │ │ ├── ScaleAnimation.tsx │ │ │ └── SlideInAnimation.tsx │ │ └── tsconfig.json │ ├── xmlui-devtools │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── devtools │ │ │ │ ├── DevTools.tsx │ │ │ │ ├── DevToolsNative.module.scss │ │ │ │ ├── DevToolsNative.tsx │ │ │ │ ├── ModalDialog.module.scss │ │ │ │ ├── ModalDialog.tsx │ │ │ │ ├── ModalVisibilityContext.tsx │ │ │ │ ├── Tooltip.module.scss │ │ │ │ ├── Tooltip.tsx │ │ │ │ └── utils.ts │ │ │ ├── editor │ │ │ │ └── Editor.tsx │ │ │ └── index.tsx │ │ ├── tsconfig.json │ │ └── vite.config-overrides.ts │ ├── xmlui-hello-world │ │ ├── .gitignore │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── HelloWorld.module.scss │ │ │ ├── HelloWorld.tsx │ │ │ ├── HelloWorldNative.tsx │ │ │ └── index.tsx │ │ └── tsconfig.json │ ├── xmlui-os-frames │ │ ├── .gitignore │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── IPhoneFrame.module.scss │ │ │ ├── IPhoneFrame.tsx │ │ │ ├── MacOSAppFrame.module.scss │ │ │ ├── MacOSAppFrame.tsx │ │ │ ├── WindowsAppFrame.module.scss │ │ │ └── WindowsAppFrame.tsx │ │ └── tsconfig.json │ ├── xmlui-pdf │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ ├── components │ │ │ │ └── Pdf.xmlui │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── LazyPdfNative.tsx │ │ │ ├── Pdf.module.scss │ │ │ └── Pdf.tsx │ │ └── tsconfig.json │ ├── xmlui-playground │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── hooks │ │ │ │ ├── usePlayground.ts │ │ │ │ └── useToast.ts │ │ │ ├── index.tsx │ │ │ ├── playground │ │ │ │ ├── Box.module.scss │ │ │ │ ├── Box.tsx │ │ │ │ ├── CodeSelector.tsx │ │ │ │ ├── ConfirmationDialog.module.scss │ │ │ │ ├── ConfirmationDialog.tsx │ │ │ │ ├── Editor.tsx │ │ │ │ ├── Header.module.scss │ │ │ │ ├── Header.tsx │ │ │ │ ├── Playground.tsx │ │ │ │ ├── PlaygroundContent.module.scss │ │ │ │ ├── PlaygroundContent.tsx │ │ │ │ ├── PlaygroundNative.module.scss │ │ │ │ ├── PlaygroundNative.tsx │ │ │ │ ├── Preview.module.scss │ │ │ │ ├── Preview.tsx │ │ │ │ ├── Select.module.scss │ │ │ │ ├── StandalonePlayground.tsx │ │ │ │ ├── StandalonePlaygroundNative.module.scss │ │ │ │ ├── StandalonePlaygroundNative.tsx │ │ │ │ ├── ThemeSwitcher.module.scss │ │ │ │ ├── ThemeSwitcher.tsx │ │ │ │ ├── ToneSwitcher.tsx │ │ │ │ ├── Tooltip.module.scss │ │ │ │ ├── Tooltip.tsx │ │ │ │ └── utils.ts │ │ │ ├── providers │ │ │ │ ├── Toast.module.scss │ │ │ │ └── ToastProvider.tsx │ │ │ ├── state │ │ │ │ └── store.ts │ │ │ ├── themes │ │ │ │ └── theme.ts │ │ │ └── utils │ │ │ └── helpers.ts │ │ └── tsconfig.json │ ├── xmlui-search │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── Search.module.scss │ │ │ └── Search.tsx │ │ └── tsconfig.json │ ├── xmlui-spreadsheet │ │ ├── .gitignore │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── Spreadsheet.tsx │ │ │ └── SpreadsheetNative.tsx │ │ └── tsconfig.json │ └── xmlui-website-blocks │ ├── .gitignore │ ├── CHANGELOG.md │ ├── demo │ │ ├── components │ │ │ ├── HeroBackgroundBreakoutPage.xmlui │ │ │ ├── HeroBackgroundsPage.xmlui │ │ │ ├── HeroContentsPage.xmlui │ │ │ ├── HeroTextAlignPage.xmlui │ │ │ ├── HeroTextPage.xmlui │ │ │ └── HeroTonesPage.xmlui │ │ ├── Main.xmlui │ │ └── themes │ │ └── default.ts │ ├── index.html │ ├── index.ts │ ├── meta │ │ └── componentsMetadata.ts │ ├── package.json │ ├── public │ │ └── resources │ │ ├── building.jpg │ │ └── xmlui-logo.svg │ ├── src │ │ ├── Carousel │ │ │ ├── Carousel.module.scss │ │ │ ├── Carousel.tsx │ │ │ ├── CarouselContext.tsx │ │ │ └── CarouselNative.tsx │ │ ├── FancyButton │ │ │ ├── FancyButton.module.scss │ │ │ ├── FancyButton.tsx │ │ │ └── FancyButton.xmlui │ │ ├── Hello │ │ │ ├── Hello.tsx │ │ │ ├── Hello.xmlui │ │ │ └── Hello.xmlui.xs │ │ ├── HeroSection │ │ │ ├── HeroSection.module.scss │ │ │ ├── HeroSection.tsx │ │ │ └── HeroSectionNative.tsx │ │ ├── index.tsx │ │ ├── ScrollToTop │ │ │ ├── ScrollToTop.module.scss │ │ │ ├── ScrollToTop.tsx │ │ │ └── ScrollToTopNative.tsx │ │ └── vite-env.d.ts │ └── tsconfig.json ├── README.md ├── tools │ ├── codefence │ │ └── xmlui-code-fence-docs.md │ ├── create-app │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── create-app.ts │ │ ├── helpers │ │ │ ├── copy.ts │ │ │ ├── get-pkg-manager.ts │ │ │ ├── git.ts │ │ │ ├── install.ts │ │ │ ├── is-folder-empty.ts │ │ │ ├── is-writeable.ts │ │ │ ├── make-dir.ts │ │ │ └── validate-pkg.ts │ │ ├── index.ts │ │ ├── package.json │ │ ├── templates │ │ │ ├── default │ │ │ │ └── ts │ │ │ │ ├── gitignore │ │ │ │ ├── index.html │ │ │ │ ├── index.ts │ │ │ │ ├── public │ │ │ │ │ ├── mockServiceWorker.js │ │ │ │ │ ├── resources │ │ │ │ │ │ ├── favicon.ico │ │ │ │ │ │ └── xmlui-logo.svg │ │ │ │ │ └── serve.json │ │ │ │ └── src │ │ │ │ ├── components │ │ │ │ │ ├── ApiAware.xmlui │ │ │ │ │ ├── Home.xmlui │ │ │ │ │ ├── IncButton.xmlui │ │ │ │ │ └── PagePanel.xmlui │ │ │ │ ├── config.ts │ │ │ │ └── Main.xmlui │ │ │ ├── index.ts │ │ │ └── types.ts │ │ └── tsconfig.json │ ├── create-xmlui-hello-world │ │ ├── index.js │ │ └── package.json │ └── vscode │ ├── .gitignore │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ ├── .vscodeignore │ ├── build.sh │ ├── CHANGELOG.md │ ├── esbuild.js │ ├── eslint.config.mjs │ ├── formatter-docs.md │ ├── generate-test-sample.sh │ ├── LICENSE.md │ ├── package-lock.json │ ├── package.json │ ├── README.md │ ├── resources │ │ ├── xmlui-logo.png │ │ └── xmlui-markup-syntax-highlighting.png │ ├── src │ │ ├── extension.ts │ │ └── server.ts │ ├── syntaxes │ │ └── xmlui.tmLanguage.json │ ├── test-samples │ │ └── sample.xmlui │ ├── tsconfig.json │ └── tsconfig.tsbuildinfo ├── turbo.json └── xmlui ├── .gitignore ├── bin │ ├── bootstrap.js │ ├── build-lib.ts │ ├── build.ts │ ├── index.ts │ ├── preview.ts │ ├── start.ts │ ├── vite-xmlui-plugin.ts │ └── viteConfig.ts ├── CHANGELOG.md ├── conventions │ ├── component-qa-checklist.md │ ├── copilot-conventions.md │ ├── create-xmlui-components.md │ ├── mermaid.md │ ├── testing-conventions.md │ └── xmlui-in-a-nutshell.md ├── dev-docs │ ├── accessibility.md │ ├── build-system.md │ ├── build-xmlui.md │ ├── containers.md │ ├── glossary.md │ ├── index.md │ ├── next │ │ ├── component-dev-guide.md │ │ ├── configuration-management-enhancement-summary.md │ │ ├── documentation-scripts-refactoring-complete-summary.md │ │ ├── documentation-scripts-refactoring-plan.md │ │ ├── duplicate-pattern-extraction-summary.md │ │ ├── error-handling-standardization-summary.md │ │ ├── generating-component-reference.md │ │ ├── index.md │ │ ├── logging-consistency-implementation-summary.md │ │ ├── project-build.md │ │ ├── project-structure.md │ │ ├── theme-context.md │ │ ├── tiptap-design-considerations.md │ │ ├── working-with-code.md │ │ ├── xmlui-runtime-architecture │ │ └── xmlui-wcag-accessibility-report.md │ ├── react-fundamentals.md │ ├── release-method.md │ ├── standalone-app.md │ ├── state-management.md │ └── xmlui-repo.md ├── package.json ├── playwright.config.ts ├── scripts │ ├── coverage-only.js │ ├── e2e-test-summary.js │ ├── generate-docs │ │ ├── build-downloads-map.mjs │ │ ├── build-pages-map.mjs │ │ ├── components-config.json │ │ ├── configuration-management.mjs │ │ ├── constants.mjs │ │ ├── create-theme-files.mjs │ │ ├── DocsGenerator.mjs │ │ ├── error-handling.mjs │ │ ├── extensions-config.json │ │ ├── folders.mjs │ │ ├── generate-summary-files.mjs │ │ ├── get-docs.mjs │ │ ├── input-handler.mjs │ │ ├── logger.mjs │ │ ├── logging-standards.mjs │ │ ├── MetadataProcessor.mjs │ │ ├── pattern-utilities.mjs │ │ └── utils.mjs │ ├── get-langserver-metadata.mjs │ ├── inline-links.mjs │ └── README-e2e-summary.md ├── src │ ├── abstractions │ │ ├── _conventions.md │ │ ├── ActionDefs.ts │ │ ├── AppContextDefs.ts │ │ ├── ComponentDefs.ts │ │ ├── ContainerDefs.ts │ │ ├── ExtensionDefs.ts │ │ ├── FunctionDefs.ts │ │ ├── RendererDefs.ts │ │ ├── scripting │ │ │ ├── BlockScope.ts │ │ │ ├── Compilation.ts │ │ │ ├── LogicalThread.ts │ │ │ ├── LoopScope.ts │ │ │ ├── modules.ts │ │ │ ├── ScriptParserError.ts │ │ │ ├── Token.ts │ │ │ ├── TryScope.ts │ │ │ └── TryScopeExp.ts │ │ └── ThemingDefs.ts │ ├── components │ │ ├── _conventions.md │ │ ├── abstractions.ts │ │ ├── Accordion │ │ │ ├── Accordion.md │ │ │ ├── Accordion.module.scss │ │ │ ├── Accordion.spec.ts │ │ │ ├── Accordion.tsx │ │ │ ├── AccordionContext.tsx │ │ │ ├── AccordionItem.tsx │ │ │ ├── AccordionItemNative.tsx │ │ │ └── AccordionNative.tsx │ │ ├── Animation │ │ │ └── AnimationNative.tsx │ │ ├── APICall │ │ │ ├── APICall.md │ │ │ ├── APICall.spec.ts │ │ │ ├── APICall.tsx │ │ │ └── APICallNative.tsx │ │ ├── App │ │ │ ├── App.md │ │ │ ├── App.module.scss │ │ │ ├── App.spec.ts │ │ │ ├── App.tsx │ │ │ ├── AppLayoutContext.ts │ │ │ ├── AppNative.tsx │ │ │ ├── AppStateContext.ts │ │ │ ├── doc-resources │ │ │ │ ├── condensed-sticky.xmlui │ │ │ │ ├── condensed.xmlui │ │ │ │ ├── horizontal-sticky.xmlui │ │ │ │ ├── horizontal.xmlui │ │ │ │ ├── vertical-full-header.xmlui │ │ │ │ ├── vertical-sticky.xmlui │ │ │ │ └── vertical.xmlui │ │ │ ├── IndexerContext.ts │ │ │ ├── LinkInfoContext.ts │ │ │ ├── SearchContext.tsx │ │ │ ├── Sheet.module.scss │ │ │ └── Sheet.tsx │ │ ├── AppHeader │ │ │ ├── AppHeader.md │ │ │ ├── AppHeader.module.scss │ │ │ ├── AppHeader.spec.ts │ │ │ ├── AppHeader.tsx │ │ │ └── AppHeaderNative.tsx │ │ ├── AppState │ │ │ ├── AppState.md │ │ │ ├── AppState.spec.ts │ │ │ ├── AppState.tsx │ │ │ └── AppStateNative.tsx │ │ ├── AutoComplete │ │ │ ├── AutoComplete.md │ │ │ ├── AutoComplete.module.scss │ │ │ ├── AutoComplete.spec.ts │ │ │ ├── AutoComplete.tsx │ │ │ ├── AutoCompleteContext.tsx │ │ │ └── AutoCompleteNative.tsx │ │ ├── Avatar │ │ │ ├── Avatar.md │ │ │ ├── Avatar.module.scss │ │ │ ├── Avatar.spec.ts │ │ │ ├── Avatar.tsx │ │ │ └── AvatarNative.tsx │ │ ├── Backdrop │ │ │ ├── Backdrop.md │ │ │ ├── Backdrop.module.scss │ │ │ ├── Backdrop.spec.ts │ │ │ ├── Backdrop.tsx │ │ │ └── BackdropNative.tsx │ │ ├── Badge │ │ │ ├── Badge.md │ │ │ ├── Badge.module.scss │ │ │ ├── Badge.spec.ts │ │ │ ├── Badge.tsx │ │ │ └── BadgeNative.tsx │ │ ├── Bookmark │ │ │ ├── Bookmark.md │ │ │ ├── Bookmark.module.scss │ │ │ ├── Bookmark.spec.ts │ │ │ ├── Bookmark.tsx │ │ │ └── BookmarkNative.tsx │ │ ├── Breakout │ │ │ ├── Breakout.module.scss │ │ │ ├── Breakout.spec.ts │ │ │ ├── Breakout.tsx │ │ │ └── BreakoutNative.tsx │ │ ├── Button │ │ │ ├── Button-style.spec.ts │ │ │ ├── Button.md │ │ │ ├── Button.module.scss │ │ │ ├── Button.spec.ts │ │ │ ├── Button.tsx │ │ │ └── ButtonNative.tsx │ │ ├── Card │ │ │ ├── Card.md │ │ │ ├── Card.module.scss │ │ │ ├── Card.spec.ts │ │ │ ├── Card.tsx │ │ │ └── CardNative.tsx │ │ ├── Carousel │ │ │ ├── Carousel.md │ │ │ ├── Carousel.module.scss │ │ │ ├── Carousel.spec.ts │ │ │ ├── Carousel.tsx │ │ │ ├── CarouselContext.tsx │ │ │ ├── CarouselItem.tsx │ │ │ ├── CarouselItemNative.tsx │ │ │ └── CarouselNative.tsx │ │ ├── ChangeListener │ │ │ ├── ChangeListener.md │ │ │ ├── ChangeListener.spec.ts │ │ │ ├── ChangeListener.tsx │ │ │ └── ChangeListenerNative.tsx │ │ ├── chart-color-schemes.ts │ │ ├── Charts │ │ │ ├── AreaChart │ │ │ │ ├── AreaChart.md │ │ │ │ ├── AreaChart.spec.ts │ │ │ │ ├── AreaChart.tsx │ │ │ │ └── AreaChartNative.tsx │ │ │ ├── BarChart │ │ │ │ ├── BarChart.md │ │ │ │ ├── BarChart.module.scss │ │ │ │ ├── BarChart.spec.ts │ │ │ │ ├── BarChart.tsx │ │ │ │ └── BarChartNative.tsx │ │ │ ├── DonutChart │ │ │ │ ├── DonutChart.spec.ts │ │ │ │ └── DonutChart.tsx │ │ │ ├── LabelList │ │ │ │ ├── LabelList.spec.ts │ │ │ │ ├── LabelList.tsx │ │ │ │ ├── LabelListNative.module.scss │ │ │ │ └── LabelListNative.tsx │ │ │ ├── Legend │ │ │ │ ├── Legend.spec.ts │ │ │ │ ├── Legend.tsx │ │ │ │ └── LegendNative.tsx │ │ │ ├── LineChart │ │ │ │ ├── LineChart.md │ │ │ │ ├── LineChart.module.scss │ │ │ │ ├── LineChart.spec.ts │ │ │ │ ├── LineChart.tsx │ │ │ │ └── LineChartNative.tsx │ │ │ ├── PieChart │ │ │ │ ├── PieChart.md │ │ │ │ ├── PieChart.spec.ts │ │ │ │ ├── PieChart.tsx │ │ │ │ ├── PieChartNative.module.scss │ │ │ │ └── PieChartNative.tsx │ │ │ ├── RadarChart │ │ │ │ ├── RadarChart.md │ │ │ │ ├── RadarChart.spec.ts │ │ │ │ ├── RadarChart.tsx │ │ │ │ └── RadarChartNative.tsx │ │ │ ├── Tooltip │ │ │ │ ├── TooltipContent.module.scss │ │ │ │ ├── TooltipContent.spec.ts │ │ │ │ └── TooltipContent.tsx │ │ │ └── utils │ │ │ ├── abstractions.ts │ │ │ └── ChartProvider.tsx │ │ ├── Checkbox │ │ │ ├── Checkbox.md │ │ │ ├── Checkbox.spec.ts │ │ │ └── Checkbox.tsx │ │ ├── CodeBlock │ │ │ ├── CodeBlock.module.scss │ │ │ ├── CodeBlock.spec.ts │ │ │ ├── CodeBlock.tsx │ │ │ ├── CodeBlockNative.tsx │ │ │ └── highlight-code.ts │ │ ├── collectedComponentMetadata.ts │ │ ├── ColorPicker │ │ │ ├── ColorPicker.md │ │ │ ├── ColorPicker.module.scss │ │ │ ├── ColorPicker.spec.ts │ │ │ ├── ColorPicker.tsx │ │ │ └── ColorPickerNative.tsx │ │ ├── Column │ │ │ ├── Column.md │ │ │ ├── Column.tsx │ │ │ ├── ColumnNative.tsx │ │ │ ├── doc-resources │ │ │ │ └── list-component-data.js │ │ │ └── TableContext.tsx │ │ ├── component-utils.ts │ │ ├── ComponentProvider.tsx │ │ ├── ComponentRegistryContext.tsx │ │ ├── container-helpers.tsx │ │ ├── ContentSeparator │ │ │ ├── ContentSeparator.md │ │ │ ├── ContentSeparator.module.scss │ │ │ ├── ContentSeparator.spec.ts │ │ │ ├── ContentSeparator.tsx │ │ │ └── ContentSeparatorNative.tsx │ │ ├── DataSource │ │ │ ├── DataSource.md │ │ │ └── DataSource.tsx │ │ ├── DateInput │ │ │ ├── DateInput.md │ │ │ ├── DateInput.module.scss │ │ │ ├── DateInput.spec.ts │ │ │ ├── DateInput.tsx │ │ │ └── DateInputNative.tsx │ │ ├── DatePicker │ │ │ ├── DatePicker.md │ │ │ ├── DatePicker.module.scss │ │ │ ├── DatePicker.spec.ts │ │ │ ├── DatePicker.tsx │ │ │ └── DatePickerNative.tsx │ │ ├── DropdownMenu │ │ │ ├── DropdownMenu.md │ │ │ ├── DropdownMenu.module.scss │ │ │ ├── DropdownMenu.spec.ts │ │ │ ├── DropdownMenu.tsx │ │ │ ├── DropdownMenuNative.tsx │ │ │ ├── MenuItem.md │ │ │ └── SubMenuItem.md │ │ ├── EmojiSelector │ │ │ ├── EmojiSelector.md │ │ │ ├── EmojiSelector.spec.ts │ │ │ ├── EmojiSelector.tsx │ │ │ └── EmojiSelectorNative.tsx │ │ ├── ExpandableItem │ │ │ ├── ExpandableItem.module.scss │ │ │ ├── ExpandableItem.spec.ts │ │ │ ├── ExpandableItem.tsx │ │ │ └── ExpandableItemNative.tsx │ │ ├── FileInput │ │ │ ├── FileInput.md │ │ │ ├── FileInput.module.scss │ │ │ ├── FileInput.spec.ts │ │ │ ├── FileInput.tsx │ │ │ └── FileInputNative.tsx │ │ ├── FileUploadDropZone │ │ │ ├── FileUploadDropZone.md │ │ │ ├── FileUploadDropZone.module.scss │ │ │ ├── FileUploadDropZone.spec.ts │ │ │ ├── FileUploadDropZone.tsx │ │ │ └── FileUploadDropZoneNative.tsx │ │ ├── FlowLayout │ │ │ ├── FlowLayout.md │ │ │ ├── FlowLayout.module.scss │ │ │ ├── FlowLayout.spec.ts │ │ │ ├── FlowLayout.spec.ts-snapshots │ │ │ │ └── Edge-cases-boxShadow-is-not-clipped-1-non-smoke-darwin.png │ │ │ ├── FlowLayout.tsx │ │ │ └── FlowLayoutNative.tsx │ │ ├── Footer │ │ │ ├── Footer.md │ │ │ ├── Footer.module.scss │ │ │ ├── Footer.spec.ts │ │ │ ├── Footer.tsx │ │ │ └── FooterNative.tsx │ │ ├── Form │ │ │ ├── Form.md │ │ │ ├── Form.module.scss │ │ │ ├── Form.spec.ts │ │ │ ├── Form.tsx │ │ │ ├── formActions.ts │ │ │ ├── FormContext.ts │ │ │ └── FormNative.tsx │ │ ├── FormItem │ │ │ ├── FormItem.md │ │ │ ├── FormItem.module.scss │ │ │ ├── FormItem.spec.ts │ │ │ ├── FormItem.tsx │ │ │ ├── FormItemNative.tsx │ │ │ ├── HelperText.module.scss │ │ │ ├── HelperText.tsx │ │ │ ├── ItemWithLabel.tsx │ │ │ └── Validations.ts │ │ ├── FormSection │ │ │ ├── FormSection.md │ │ │ ├── FormSection.ts │ │ │ └── FormSection.xmlui │ │ ├── Fragment │ │ │ ├── Fragment.spec.ts │ │ │ └── Fragment.tsx │ │ ├── Heading │ │ │ ├── abstractions.ts │ │ │ ├── H1.md │ │ │ ├── H1.spec.ts │ │ │ ├── H2.md │ │ │ ├── H2.spec.ts │ │ │ ├── H3.md │ │ │ ├── H3.spec.ts │ │ │ ├── H4.md │ │ │ ├── H4.spec.ts │ │ │ ├── H5.md │ │ │ ├── H5.spec.ts │ │ │ ├── H6.md │ │ │ ├── H6.spec.ts │ │ │ ├── Heading.md │ │ │ ├── Heading.module.scss │ │ │ ├── Heading.spec.ts │ │ │ ├── Heading.tsx │ │ │ └── HeadingNative.tsx │ │ ├── HoverCard │ │ │ ├── HoverCard.tsx │ │ │ └── HovercardNative.tsx │ │ ├── HtmlTags │ │ │ ├── HtmlTags.module.scss │ │ │ ├── HtmlTags.spec.ts │ │ │ └── HtmlTags.tsx │ │ ├── Icon │ │ │ ├── AdmonitionDanger.tsx │ │ │ ├── AdmonitionInfo.tsx │ │ │ ├── AdmonitionNote.tsx │ │ │ ├── AdmonitionTip.tsx │ │ │ ├── AdmonitionWarning.tsx │ │ │ ├── ApiIcon.tsx │ │ │ ├── ArrowDropDown.module.scss │ │ │ ├── ArrowDropDown.tsx │ │ │ ├── ArrowDropUp.module.scss │ │ │ ├── ArrowDropUp.tsx │ │ │ ├── ArrowLeft.module.scss │ │ │ ├── ArrowLeft.tsx │ │ │ ├── ArrowRight.module.scss │ │ │ ├── ArrowRight.tsx │ │ │ ├── Attach.tsx │ │ │ ├── Binding.module.scss │ │ │ ├── Binding.tsx │ │ │ ├── BoardIcon.tsx │ │ │ ├── BoxIcon.tsx │ │ │ ├── CheckIcon.tsx │ │ │ ├── ChevronDownIcon.tsx │ │ │ ├── ChevronLeft.tsx │ │ │ ├── ChevronRight.tsx │ │ │ ├── ChevronUpIcon.tsx │ │ │ ├── CodeFileIcon.tsx │ │ │ ├── CodeSandbox.tsx │ │ │ ├── CompactListIcon.tsx │ │ │ ├── ContentCopyIcon.tsx │ │ │ ├── DarkToLightIcon.tsx │ │ │ ├── DatabaseIcon.module.scss │ │ │ ├── DatabaseIcon.tsx │ │ │ ├── DocFileIcon.tsx │ │ │ ├── DocIcon.tsx │ │ │ ├── DotMenuHorizontalIcon.tsx │ │ │ ├── DotMenuIcon.tsx │ │ │ ├── EmailIcon.tsx │ │ │ ├── EmptyFolderIcon.tsx │ │ │ ├── ErrorIcon.tsx │ │ │ ├── ExpressionIcon.tsx │ │ │ ├── FillPlusCricleIcon.tsx │ │ │ ├── FilterIcon.tsx │ │ │ ├── FolderIcon.tsx │ │ │ ├── GlobeIcon.tsx │ │ │ ├── HomeIcon.tsx │ │ │ ├── HyperLinkIcon.tsx │ │ │ ├── Icon.md │ │ │ ├── Icon.module.scss │ │ │ ├── Icon.spec.ts │ │ │ ├── Icon.tsx │ │ │ ├── IconNative.tsx │ │ │ ├── ImageFileIcon.tsx │ │ │ ├── Inspect.tsx │ │ │ ├── LightToDark.tsx │ │ │ ├── LinkIcon.tsx │ │ │ ├── ListIcon.tsx │ │ │ ├── LooseListIcon.tsx │ │ │ ├── MoonIcon.tsx │ │ │ ├── MoreOptionsIcon.tsx │ │ │ ├── NoSortIcon.tsx │ │ │ ├── PDFIcon.tsx │ │ │ ├── PenIcon.tsx │ │ │ ├── PhoneIcon.tsx │ │ │ ├── PhotoIcon.tsx │ │ │ ├── PlusIcon.tsx │ │ │ ├── SearchIcon.tsx │ │ │ ├── ShareIcon.tsx │ │ │ ├── SortAscendingIcon.tsx │ │ │ ├── SortDescendingIcon.tsx │ │ │ ├── StarsIcon.tsx │ │ │ ├── SunIcon.tsx │ │ │ ├── svg │ │ │ │ ├── admonition_danger.svg │ │ │ │ ├── admonition_info.svg │ │ │ │ ├── admonition_note.svg │ │ │ │ ├── admonition_tip.svg │ │ │ │ ├── admonition_warning.svg │ │ │ │ ├── api.svg │ │ │ │ ├── arrow-dropdown.svg │ │ │ │ ├── arrow-left.svg │ │ │ │ ├── arrow-right.svg │ │ │ │ ├── arrow-up.svg │ │ │ │ ├── attach.svg │ │ │ │ ├── binding.svg │ │ │ │ ├── box.svg │ │ │ │ ├── bulb.svg │ │ │ │ ├── code-file.svg │ │ │ │ ├── code-sandbox.svg │ │ │ │ ├── dark_to_light.svg │ │ │ │ ├── database.svg │ │ │ │ ├── doc.svg │ │ │ │ ├── empty-folder.svg │ │ │ │ ├── expression.svg │ │ │ │ ├── eye-closed.svg │ │ │ │ ├── eye-dark.svg │ │ │ │ ├── eye.svg │ │ │ │ ├── file-text.svg │ │ │ │ ├── filter.svg │ │ │ │ ├── folder.svg │ │ │ │ ├── img.svg │ │ │ │ ├── inspect.svg │ │ │ │ ├── light_to_dark.svg │ │ │ │ ├── moon.svg │ │ │ │ ├── pdf.svg │ │ │ │ ├── photo.svg │ │ │ │ ├── share.svg │ │ │ │ ├── stars.svg │ │ │ │ ├── sun.svg │ │ │ │ ├── trending-down.svg │ │ │ │ ├── trending-level.svg │ │ │ │ ├── trending-up.svg │ │ │ │ ├── txt.svg │ │ │ │ ├── unknown-file.svg │ │ │ │ ├── unlink.svg │ │ │ │ └── xls.svg │ │ │ ├── TableDeleteColumnIcon.tsx │ │ │ ├── TableDeleteRowIcon.tsx │ │ │ ├── TableInsertColumnIcon.tsx │ │ │ ├── TableInsertRowIcon.tsx │ │ │ ├── TrashIcon.tsx │ │ │ ├── TrendingDownIcon.tsx │ │ │ ├── TrendingLevelIcon.tsx │ │ │ ├── TrendingUpIcon.tsx │ │ │ ├── TxtIcon.tsx │ │ │ ├── UnknownFileIcon.tsx │ │ │ ├── UnlinkIcon.tsx │ │ │ ├── UserIcon.tsx │ │ │ ├── WarningIcon.tsx │ │ │ └── XlsIcon.tsx │ │ ├── IconProvider.tsx │ │ ├── IconRegistryContext.tsx │ │ ├── IFrame │ │ │ ├── IFrame.md │ │ │ ├── IFrame.module.scss │ │ │ ├── IFrame.spec.ts │ │ │ ├── IFrame.tsx │ │ │ └── IFrameNative.tsx │ │ ├── Image │ │ │ ├── Image.md │ │ │ ├── Image.module.scss │ │ │ ├── Image.spec.ts │ │ │ ├── Image.tsx │ │ │ └── ImageNative.tsx │ │ ├── Input │ │ │ ├── index.ts │ │ │ ├── InputAdornment.module.scss │ │ │ ├── InputAdornment.tsx │ │ │ ├── InputDivider.module.scss │ │ │ ├── InputDivider.tsx │ │ │ ├── InputLabel.module.scss │ │ │ ├── InputLabel.tsx │ │ │ ├── PartialInput.module.scss │ │ │ └── PartialInput.tsx │ │ ├── InspectButton │ │ │ ├── InspectButton.module.scss │ │ │ └── InspectButton.tsx │ │ ├── Items │ │ │ ├── Items.md │ │ │ ├── Items.spec.ts │ │ │ ├── Items.tsx │ │ │ └── ItemsNative.tsx │ │ ├── Link │ │ │ ├── Link.md │ │ │ ├── Link.module.scss │ │ │ ├── Link.spec.ts │ │ │ ├── Link.tsx │ │ │ └── LinkNative.tsx │ │ ├── List │ │ │ ├── doc-resources │ │ │ │ └── list-component-data.js │ │ │ ├── List.md │ │ │ ├── List.module.scss │ │ │ ├── List.spec.ts │ │ │ ├── List.tsx │ │ │ └── ListNative.tsx │ │ ├── Logo │ │ │ ├── doc-resources │ │ │ │ └── xmlui-logo.svg │ │ │ ├── Logo.md │ │ │ ├── Logo.tsx │ │ │ └── LogoNative.tsx │ │ ├── Markdown │ │ │ ├── CodeText.module.scss │ │ │ ├── CodeText.tsx │ │ │ ├── Markdown.md │ │ │ ├── Markdown.module.scss │ │ │ ├── Markdown.spec.ts │ │ │ ├── Markdown.tsx │ │ │ ├── MarkdownNative.tsx │ │ │ ├── parse-binding-expr.ts │ │ │ └── utils.ts │ │ ├── metadata-helpers.ts │ │ ├── ModalDialog │ │ │ ├── ConfirmationModalContextProvider.tsx │ │ │ ├── Dialog.module.scss │ │ │ ├── Dialog.tsx │ │ │ ├── ModalDialog.md │ │ │ ├── ModalDialog.module.scss │ │ │ ├── ModalDialog.spec.ts │ │ │ ├── ModalDialog.tsx │ │ │ ├── ModalDialogNative.tsx │ │ │ └── ModalVisibilityContext.tsx │ │ ├── NavGroup │ │ │ ├── NavGroup.md │ │ │ ├── NavGroup.module.scss │ │ │ ├── NavGroup.spec.ts │ │ │ ├── NavGroup.tsx │ │ │ ├── NavGroupContext.ts │ │ │ └── NavGroupNative.tsx │ │ ├── NavLink │ │ │ ├── NavLink.md │ │ │ ├── NavLink.module.scss │ │ │ ├── NavLink.spec.ts │ │ │ ├── NavLink.tsx │ │ │ └── NavLinkNative.tsx │ │ ├── NavPanel │ │ │ ├── NavPanel.md │ │ │ ├── NavPanel.module.scss │ │ │ ├── NavPanel.spec.ts │ │ │ ├── NavPanel.tsx │ │ │ └── NavPanelNative.tsx │ │ ├── NestedApp │ │ │ ├── AppWithCodeView.module.scss │ │ │ ├── AppWithCodeView.tsx │ │ │ ├── AppWithCodeViewNative.tsx │ │ │ ├── defaultProps.tsx │ │ │ ├── logo.svg │ │ │ ├── NestedApp.module.scss │ │ │ ├── NestedApp.tsx │ │ │ ├── NestedAppNative.tsx │ │ │ ├── Tooltip.module.scss │ │ │ ├── Tooltip.tsx │ │ │ └── utils.ts │ │ ├── NoResult │ │ │ ├── NoResult.md │ │ │ ├── NoResult.module.scss │ │ │ ├── NoResult.spec.ts │ │ │ ├── NoResult.tsx │ │ │ └── NoResultNative.tsx │ │ ├── NumberBox │ │ │ ├── numberbox-abstractions.ts │ │ │ ├── NumberBox.md │ │ │ ├── NumberBox.module.scss │ │ │ ├── NumberBox.spec.ts │ │ │ ├── NumberBox.tsx │ │ │ └── NumberBoxNative.tsx │ │ ├── Option │ │ │ ├── Option.md │ │ │ ├── Option.spec.ts │ │ │ ├── Option.tsx │ │ │ ├── OptionNative.tsx │ │ │ └── OptionTypeProvider.tsx │ │ ├── PageMetaTitle │ │ │ ├── PageMetaTilteNative.tsx │ │ │ ├── PageMetaTitle.md │ │ │ ├── PageMetaTitle.spec.ts │ │ │ └── PageMetaTitle.tsx │ │ ├── Pages │ │ │ ├── Page.md │ │ │ ├── Pages.md │ │ │ ├── Pages.module.scss │ │ │ ├── Pages.tsx │ │ │ └── PagesNative.tsx │ │ ├── Pagination │ │ │ ├── Pagination.md │ │ │ ├── Pagination.module.scss │ │ │ ├── Pagination.spec.ts │ │ │ ├── Pagination.tsx │ │ │ └── PaginationNative.tsx │ │ ├── PositionedContainer │ │ │ ├── PositionedContainer.module.scss │ │ │ ├── PositionedContainer.tsx │ │ │ └── PositionedContainerNative.tsx │ │ ├── ProfileMenu │ │ │ ├── ProfileMenu.module.scss │ │ │ └── ProfileMenu.tsx │ │ ├── ProgressBar │ │ │ ├── ProgressBar.md │ │ │ ├── ProgressBar.module.scss │ │ │ ├── ProgressBar.spec.ts │ │ │ ├── ProgressBar.tsx │ │ │ └── ProgressBarNative.tsx │ │ ├── Queue │ │ │ ├── Queue.md │ │ │ ├── Queue.spec.ts │ │ │ ├── Queue.tsx │ │ │ ├── queueActions.ts │ │ │ └── QueueNative.tsx │ │ ├── RadioGroup │ │ │ ├── RadioGroup.md │ │ │ ├── RadioGroup.module.scss │ │ │ ├── RadioGroup.spec.ts │ │ │ ├── RadioGroup.tsx │ │ │ ├── RadioGroupNative.tsx │ │ │ ├── RadioItem.tsx │ │ │ └── RadioItemNative.tsx │ │ ├── RealTimeAdapter │ │ │ ├── RealTimeAdapter.tsx │ │ │ └── RealTimeAdapterNative.tsx │ │ ├── Redirect │ │ │ ├── Redirect.md │ │ │ ├── Redirect.spec.ts │ │ │ └── Redirect.tsx │ │ ├── ResponsiveBar │ │ │ ├── README.md │ │ │ ├── ResponsiveBar.md │ │ │ ├── ResponsiveBar.module.scss │ │ │ ├── ResponsiveBar.spec.ts │ │ │ ├── ResponsiveBar.tsx │ │ │ └── ResponsiveBarNative.tsx │ │ ├── Select │ │ │ ├── HiddenOption.tsx │ │ │ ├── MultiSelectOption.tsx │ │ │ ├── OptionContext.ts │ │ │ ├── Select.md │ │ │ ├── Select.module.scss │ │ │ ├── Select.spec.ts │ │ │ ├── Select.tsx │ │ │ ├── SelectContext.tsx │ │ │ ├── SelectNative.tsx │ │ │ ├── SelectOption.tsx │ │ │ └── SimpleSelect.tsx │ │ ├── SelectionStore │ │ │ ├── SelectionStore.md │ │ │ ├── SelectionStore.tsx │ │ │ └── SelectionStoreNative.tsx │ │ ├── Slider │ │ │ ├── Slider.md │ │ │ ├── Slider.module.scss │ │ │ ├── Slider.spec.ts │ │ │ ├── Slider.tsx │ │ │ └── SliderNative.tsx │ │ ├── Slot │ │ │ ├── Slot.md │ │ │ ├── Slot.spec.ts │ │ │ └── Slot.ts │ │ ├── SlotItem.tsx │ │ ├── SpaceFiller │ │ │ ├── SpaceFiller.md │ │ │ ├── SpaceFiller.module.scss │ │ │ ├── SpaceFiller.spec.ts │ │ │ ├── SpaceFiller.tsx │ │ │ └── SpaceFillerNative.tsx │ │ ├── Spinner │ │ │ ├── Spinner.md │ │ │ ├── Spinner.module.scss │ │ │ ├── Spinner.spec.ts │ │ │ ├── Spinner.tsx │ │ │ └── SpinnerNative.tsx │ │ ├── Splitter │ │ │ ├── HSplitter.md │ │ │ ├── HSplitter.spec.ts │ │ │ ├── Splitter.md │ │ │ ├── Splitter.module.scss │ │ │ ├── Splitter.spec.ts │ │ │ ├── Splitter.tsx │ │ │ ├── SplitterNative.tsx │ │ │ ├── utils.ts │ │ │ ├── VSplitter.md │ │ │ └── VSplitter.spec.ts │ │ ├── Stack │ │ │ ├── CHStack.md │ │ │ ├── CHStack.spec.ts │ │ │ ├── CVStack.md │ │ │ ├── CVStack.spec.ts │ │ │ ├── HStack.md │ │ │ ├── HStack.spec.ts │ │ │ ├── Stack.md │ │ │ ├── Stack.module.scss │ │ │ ├── Stack.spec.ts │ │ │ ├── Stack.tsx │ │ │ ├── StackNative.tsx │ │ │ ├── VStack.md │ │ │ └── VStack.spec.ts │ │ ├── StickyBox │ │ │ ├── StickyBox.md │ │ │ ├── StickyBox.module.scss │ │ │ ├── StickyBox.tsx │ │ │ └── StickyBoxNative.tsx │ │ ├── Switch │ │ │ ├── Switch.md │ │ │ ├── Switch.spec.ts │ │ │ └── Switch.tsx │ │ ├── Table │ │ │ ├── doc-resources │ │ │ │ └── list-component-data.js │ │ │ ├── react-table-config.d.ts │ │ │ ├── Table.md │ │ │ ├── Table.module.scss │ │ │ ├── Table.spec.ts │ │ │ ├── Table.tsx │ │ │ ├── TableNative.tsx │ │ │ └── useRowSelection.tsx │ │ ├── TableOfContents │ │ │ ├── TableOfContents.module.scss │ │ │ ├── TableOfContents.spec.ts │ │ │ ├── TableOfContents.tsx │ │ │ └── TableOfContentsNative.tsx │ │ ├── Tabs │ │ │ ├── TabContext.tsx │ │ │ ├── TabItem.md │ │ │ ├── TabItem.tsx │ │ │ ├── TabItemNative.tsx │ │ │ ├── Tabs.md │ │ │ ├── Tabs.module.scss │ │ │ ├── Tabs.spec.ts │ │ │ ├── Tabs.tsx │ │ │ └── TabsNative.tsx │ │ ├── Text │ │ │ ├── Text.md │ │ │ ├── Text.module.scss │ │ │ ├── Text.spec.ts │ │ │ ├── Text.tsx │ │ │ └── TextNative.tsx │ │ ├── TextArea │ │ │ ├── TextArea.md │ │ │ ├── TextArea.module.scss │ │ │ ├── TextArea.spec.ts │ │ │ ├── TextArea.tsx │ │ │ ├── TextAreaNative.tsx │ │ │ ├── TextAreaResizable.tsx │ │ │ └── useComposedRef.ts │ │ ├── TextBox │ │ │ ├── TextBox.md │ │ │ ├── TextBox.module.scss │ │ │ ├── TextBox.spec.ts │ │ │ ├── TextBox.tsx │ │ │ └── TextBoxNative.tsx │ │ ├── Theme │ │ │ ├── NotificationToast.tsx │ │ │ ├── Theme.md │ │ │ ├── Theme.module.scss │ │ │ ├── Theme.spec.ts │ │ │ ├── Theme.tsx │ │ │ └── ThemeNative.tsx │ │ ├── TimeInput │ │ │ ├── TimeInput.md │ │ │ ├── TimeInput.module.scss │ │ │ ├── TimeInput.spec.ts │ │ │ ├── TimeInput.tsx │ │ │ ├── TimeInputNative.tsx │ │ │ └── utils.ts │ │ ├── Timer │ │ │ ├── Timer.md │ │ │ ├── Timer.spec.ts │ │ │ ├── Timer.tsx │ │ │ └── TimerNative.tsx │ │ ├── Toggle │ │ │ ├── Toggle.module.scss │ │ │ └── Toggle.tsx │ │ ├── ToneChangerButton │ │ │ ├── ToneChangerButton.md │ │ │ ├── ToneChangerButton.spec.ts │ │ │ └── ToneChangerButton.tsx │ │ ├── ToneSwitch │ │ │ ├── ToneSwitch.md │ │ │ ├── ToneSwitch.module.scss │ │ │ ├── ToneSwitch.spec.ts │ │ │ ├── ToneSwitch.tsx │ │ │ └── ToneSwitchNative.tsx │ │ ├── Tooltip │ │ │ ├── Tooltip.md │ │ │ ├── Tooltip.module.scss │ │ │ ├── Tooltip.spec.ts │ │ │ ├── Tooltip.tsx │ │ │ └── TooltipNative.tsx │ │ ├── Tree │ │ │ ├── testData.ts │ │ │ ├── Tree-dynamic.spec.ts │ │ │ ├── Tree-icons.spec.ts │ │ │ ├── Tree.md │ │ │ ├── Tree.spec.ts │ │ │ ├── TreeComponent.module.scss │ │ │ ├── TreeComponent.tsx │ │ │ └── TreeNative.tsx │ │ ├── TreeDisplay │ │ │ ├── TreeDisplay.md │ │ │ ├── TreeDisplay.module.scss │ │ │ ├── TreeDisplay.tsx │ │ │ └── TreeDisplayNative.tsx │ │ ├── ValidationSummary │ │ │ ├── ValidationSummary.module.scss │ │ │ └── ValidationSummary.tsx │ │ └── VisuallyHidden.tsx │ ├── components-core │ │ ├── abstractions │ │ │ ├── ComponentRenderer.ts │ │ │ ├── LoaderRenderer.ts │ │ │ ├── standalone.ts │ │ │ └── treeAbstractions.ts │ │ ├── action │ │ │ ├── actions.ts │ │ │ ├── APICall.tsx │ │ │ ├── FileDownloadAction.tsx │ │ │ ├── FileUploadAction.tsx │ │ │ ├── NavigateAction.tsx │ │ │ └── TimedAction.tsx │ │ ├── ApiBoundComponent.tsx │ │ ├── appContext │ │ │ ├── date-functions.ts │ │ │ ├── math-function.ts │ │ │ └── misc-utils.ts │ │ ├── AppContext.tsx │ │ ├── behaviors │ │ │ ├── Behavior.tsx │ │ │ ├── BehaviorContext.tsx │ │ │ └── CoreBehaviors.tsx │ │ ├── component-hooks.ts │ │ ├── ComponentDecorator.tsx │ │ ├── ComponentViewer.tsx │ │ ├── CompoundComponent.tsx │ │ ├── constants.ts │ │ ├── DebugViewProvider.tsx │ │ ├── descriptorHelper.ts │ │ ├── devtools │ │ │ ├── InspectorDialog.module.scss │ │ │ ├── InspectorDialog.tsx │ │ │ └── InspectorDialogVisibilityContext.tsx │ │ ├── EngineError.ts │ │ ├── event-handlers.ts │ │ ├── InspectorButton.module.scss │ │ ├── InspectorContext.tsx │ │ ├── interception │ │ │ ├── abstractions.ts │ │ │ ├── ApiInterceptor.ts │ │ │ ├── ApiInterceptorProvider.tsx │ │ │ ├── apiInterceptorWorker.ts │ │ │ ├── Backend.ts │ │ │ ├── Errors.ts │ │ │ ├── IndexedDb.ts │ │ │ ├── initMock.ts │ │ │ ├── InMemoryDb.ts │ │ │ ├── ReadonlyCollection.ts │ │ │ └── useApiInterceptorContext.tsx │ │ ├── loader │ │ │ ├── ApiLoader.tsx │ │ │ ├── DataLoader.tsx │ │ │ ├── ExternalDataLoader.tsx │ │ │ ├── Loader.tsx │ │ │ ├── MockLoaderRenderer.tsx │ │ │ └── PageableLoader.tsx │ │ ├── LoaderComponent.tsx │ │ ├── markup-check.ts │ │ ├── parts.ts │ │ ├── renderers.ts │ │ ├── rendering │ │ │ ├── AppContent.tsx │ │ │ ├── AppRoot.tsx │ │ │ ├── AppWrapper.tsx │ │ │ ├── buildProxy.ts │ │ │ ├── collectFnVarDeps.ts │ │ │ ├── ComponentAdapter.tsx │ │ │ ├── ComponentWrapper.tsx │ │ │ ├── Container.tsx │ │ │ ├── containers.ts │ │ │ ├── ContainerWrapper.tsx │ │ │ ├── ErrorBoundary.module.scss │ │ │ ├── ErrorBoundary.tsx │ │ │ ├── InvalidComponent.module.scss │ │ │ ├── InvalidComponent.tsx │ │ │ ├── nodeUtils.ts │ │ │ ├── reducer.ts │ │ │ ├── renderChild.tsx │ │ │ ├── StandaloneComponent.tsx │ │ │ ├── StateContainer.tsx │ │ │ ├── UnknownComponent.module.scss │ │ │ ├── UnknownComponent.tsx │ │ │ └── valueExtractor.ts │ │ ├── reportEngineError.ts │ │ ├── RestApiProxy.ts │ │ ├── script-runner │ │ │ ├── asyncProxy.ts │ │ │ ├── AttributeValueParser.ts │ │ │ ├── bannedFunctions.ts │ │ │ ├── BindingTreeEvaluationContext.ts │ │ │ ├── eval-tree-async.ts │ │ │ ├── eval-tree-common.ts │ │ │ ├── eval-tree-sync.ts │ │ │ ├── ParameterParser.ts │ │ │ ├── process-statement-async.ts │ │ │ ├── process-statement-common.ts │ │ │ ├── process-statement-sync.ts │ │ │ ├── ScriptingSourceTree.ts │ │ │ ├── simplify-expression.ts │ │ │ ├── statement-queue.ts │ │ │ └── visitors.ts │ │ ├── StandaloneApp.tsx │ │ ├── StandaloneExtensionManager.ts │ │ ├── TableOfContentsContext.tsx │ │ ├── theming │ │ │ ├── _themes.scss │ │ │ ├── component-layout-resolver.ts │ │ │ ├── extendThemeUtils.ts │ │ │ ├── hvar.ts │ │ │ ├── layout-resolver.ts │ │ │ ├── parse-layout-props.ts │ │ │ ├── StyleContext.tsx │ │ │ ├── StyleRegistry.ts │ │ │ ├── ThemeContext.tsx │ │ │ ├── ThemeProvider.tsx │ │ │ ├── themes │ │ │ │ ├── base-utils.ts │ │ │ │ ├── palette.ts │ │ │ │ ├── root.ts │ │ │ │ ├── solid.ts │ │ │ │ ├── theme-colors.ts │ │ │ │ └── xmlui.ts │ │ │ ├── themeVars.module.scss │ │ │ ├── themeVars.ts │ │ │ ├── transformThemeVars.ts │ │ │ └── utils.ts │ │ ├── utils │ │ │ ├── actionUtils.ts │ │ │ ├── audio-utils.ts │ │ │ ├── compound-utils.ts │ │ │ ├── css-utils.ts │ │ │ ├── DataLoaderQueryKeyGenerator.ts │ │ │ ├── date-utils.ts │ │ │ ├── extractParam.ts │ │ │ ├── hooks.tsx │ │ │ ├── LruCache.ts │ │ │ ├── mergeProps.ts │ │ │ ├── misc.ts │ │ │ ├── request-params.ts │ │ │ ├── statementUtils.ts │ │ │ └── treeUtils.ts │ │ └── xmlui-parser.ts │ ├── index-standalone.ts │ ├── index.scss │ ├── index.ts │ ├── language-server │ │ ├── server-common.ts │ │ ├── server-web-worker.ts │ │ ├── server.ts │ │ ├── services │ │ │ ├── common │ │ │ │ ├── docs-generation.ts │ │ │ │ ├── lsp-utils.ts │ │ │ │ ├── metadata-utils.ts │ │ │ │ └── syntax-node-utilities.ts │ │ │ ├── completion.ts │ │ │ ├── diagnostic.ts │ │ │ ├── format.ts │ │ │ └── hover.ts │ │ └── xmlui-metadata-generated.mjs │ ├── logging │ │ ├── LoggerContext.tsx │ │ ├── LoggerInitializer.tsx │ │ ├── LoggerService.ts │ │ └── xmlui.ts │ ├── logo.svg │ ├── parsers │ │ ├── common │ │ │ ├── GenericToken.ts │ │ │ ├── InputStream.ts │ │ │ └── utils.ts │ │ ├── scripting │ │ │ ├── code-behind-collect.ts │ │ │ ├── Lexer.ts │ │ │ ├── modules.ts │ │ │ ├── Parser.ts │ │ │ ├── ParserError.ts │ │ │ ├── ScriptingNodeTypes.ts │ │ │ ├── TokenTrait.ts │ │ │ ├── TokenType.ts │ │ │ └── tree-visitor.ts │ │ ├── style-parser │ │ │ ├── errors.ts │ │ │ ├── source-tree.ts │ │ │ ├── StyleInputStream.ts │ │ │ ├── StyleLexer.ts │ │ │ ├── StyleParser.ts │ │ │ └── tokens.ts │ │ └── xmlui-parser │ │ ├── CharacterCodes.ts │ │ ├── diagnostics.ts │ │ ├── fileExtensions.ts │ │ ├── index.ts │ │ ├── lint.ts │ │ ├── parser.ts │ │ ├── ParserError.ts │ │ ├── scanner.ts │ │ ├── syntax-kind.ts │ │ ├── syntax-node.ts │ │ ├── transform.ts │ │ ├── utils.ts │ │ ├── xmlui-serializer.ts │ │ └── xmlui-tree.ts │ ├── react-app-env.d.ts │ ├── syntax │ │ ├── monaco │ │ │ ├── grammar.monacoLanguage.ts │ │ │ ├── index.ts │ │ │ ├── xmlui-dark.ts │ │ │ ├── xmlui-light.ts │ │ │ └── xmluiscript.monacoLanguage.ts │ │ └── textMate │ │ ├── index.ts │ │ ├── xmlui-dark.json │ │ ├── xmlui-light.json │ │ ├── xmlui.json │ │ └── xmlui.tmLanguage.json │ ├── testing │ │ ├── assertions.ts │ │ ├── component-test-helpers.ts │ │ ├── ComponentDrivers.ts │ │ ├── drivers │ │ │ ├── DateInputDriver.ts │ │ │ ├── ModalDialogDriver.ts │ │ │ ├── NumberBoxDriver.ts │ │ │ ├── TextBoxDriver.ts │ │ │ ├── TimeInputDriver.ts │ │ │ ├── TimerDriver.ts │ │ │ └── TreeDriver.ts │ │ ├── fixtures.ts │ │ ├── infrastructure │ │ │ ├── index.html │ │ │ ├── main.tsx │ │ │ ├── public │ │ │ │ ├── mockServiceWorker.js │ │ │ │ ├── resources │ │ │ │ │ ├── bell.svg │ │ │ │ │ ├── box.svg │ │ │ │ │ ├── doc.svg │ │ │ │ │ ├── eye.svg │ │ │ │ │ ├── flower-640x480.jpg │ │ │ │ │ ├── sun.svg │ │ │ │ │ ├── test-image-100x100.jpg │ │ │ │ │ └── txt.svg │ │ │ │ └── serve.json │ │ │ └── TestBed.tsx │ │ └── themed-app-test-helpers.ts │ └── vite-env.d.ts ├── tests │ ├── components │ │ ├── CodeBlock │ │ │ └── hightlight-code.test.ts │ │ ├── playground-pattern.test.ts │ │ └── Tree │ │ └── Tree-states.test.ts │ ├── components-core │ │ ├── abstractions │ │ │ └── treeAbstractions.test.ts │ │ ├── container │ │ │ └── buildProxy.test.ts │ │ ├── interception │ │ │ ├── orderBy.test.ts │ │ │ ├── ReadOnlyCollection.test.ts │ │ │ └── request-param-converter.test.ts │ │ ├── scripts-runner │ │ │ ├── AttributeValueParser.test.ts │ │ │ ├── eval-tree-arrow-async.test.ts │ │ │ ├── eval-tree-arrow.test.ts │ │ │ ├── eval-tree-func-decl-async.test.ts │ │ │ ├── eval-tree-func-decl.test.ts │ │ │ ├── eval-tree-pre-post.test.ts │ │ │ ├── eval-tree-regression.test.ts │ │ │ ├── eval-tree.test.ts │ │ │ ├── function-proxy.test.ts │ │ │ ├── parser-regression.test.ts │ │ │ ├── process-event.test.ts │ │ │ ├── process-function.test.ts │ │ │ ├── process-implicit-context.test.ts │ │ │ ├── process-statement-asgn.test.ts │ │ │ ├── process-statement-destruct.test.ts │ │ │ ├── process-statement-regs.test.ts │ │ │ ├── process-statement-sync.test.ts │ │ │ ├── process-statement.test.ts │ │ │ ├── process-switch-sync.test.ts │ │ │ ├── process-switch.test.ts │ │ │ ├── process-try-sync.test.ts │ │ │ ├── process-try.test.ts │ │ │ └── test-helpers.ts │ │ ├── test-metadata-handler.ts │ │ ├── theming │ │ │ ├── border-segments.test.ts │ │ │ ├── component-layout.resolver.test.ts │ │ │ ├── layout-property-parser.test.ts │ │ │ ├── layout-resolver.test.ts │ │ │ ├── layout-resolver2.test.ts │ │ │ ├── layout-vp-override.test.ts │ │ │ └── padding-segments.test.ts │ │ └── utils │ │ ├── date-utils.test.ts │ │ ├── format-human-elapsed-time.test.ts │ │ └── LruCache.test.ts │ ├── language-server │ │ ├── completion.test.ts │ │ ├── format.test.ts │ │ ├── hover.test.ts │ │ └── mockData.ts │ └── parsers │ ├── common │ │ └── input-stream.test.ts │ ├── markdown │ │ └── parse-binding-expression.test.ts │ ├── parameter-parser.test.ts │ ├── paremeter-parser.test.ts │ ├── scripting │ │ ├── eval-tree-arrow.test.ts │ │ ├── eval-tree-pre-post.test.ts │ │ ├── eval-tree.test.ts │ │ ├── function-proxy.test.ts │ │ ├── lexer-literals.test.ts │ │ ├── lexer-misc.test.ts │ │ ├── module-parse.test.ts │ │ ├── parser-arrow.test.ts │ │ ├── parser-assignments.test.ts │ │ ├── parser-binary.test.ts │ │ ├── parser-destructuring.test.ts │ │ ├── parser-errors.test.ts │ │ ├── parser-expressions.test.ts │ │ ├── parser-function.test.ts │ │ ├── parser-literals.test.ts │ │ ├── parser-primary.test.ts │ │ ├── parser-regex.test.ts │ │ ├── parser-statements.test.ts │ │ ├── parser-unary.test.ts │ │ ├── process-event.test.ts │ │ ├── process-implicit-context.test.ts │ │ ├── process-statement-asgn.test.ts │ │ ├── process-statement-destruct.test.ts │ │ ├── process-statement-regs.test.ts │ │ ├── process-statement-sync.test.ts │ │ ├── process-statement.test.ts │ │ ├── process-switch-sync.test.ts │ │ ├── process-switch.test.ts │ │ ├── process-try-sync.test.ts │ │ ├── process-try.test.ts │ │ ├── simplify-expression.test.ts │ │ ├── statement-hooks.test.ts │ │ └── test-helpers.ts │ ├── style-parser │ │ ├── generateHvarChain.test.ts │ │ ├── parseHVar.test.ts │ │ ├── parser.test.ts │ │ └── tokens.test.ts │ └── xmlui │ ├── lint.test.ts │ ├── parser.test.ts │ ├── scanner.test.ts │ ├── transform.attr.test.ts │ ├── transform.circular.test.ts │ ├── transform.element.test.ts │ ├── transform.errors.test.ts │ ├── transform.escape.test.ts │ ├── transform.regression.test.ts │ ├── transform.script.test.ts │ ├── transform.test.ts │ └── xmlui.ts ├── tests-e2e │ ├── api-bound-component-regression.spec.ts │ ├── api-call-as-extracted-component.spec.ts │ ├── assign-to-object-or-array-regression.spec.ts │ ├── binding-regression.spec.ts │ ├── children-as-template-context-vars.spec.ts │ ├── compound-component.spec.ts │ ├── context-vars-regression.spec.ts │ ├── data-bindings.spec.ts │ ├── datasource-and-api-usage-in-var.spec.ts │ ├── datasource-direct-binding.spec.ts │ ├── datasource-onLoaded-regression.spec.ts │ ├── modify-array-item-regression.spec.ts │ ├── namespaces.spec.ts │ ├── push-to-array-regression.spec.ts │ ├── screen-breakpoints.spec.ts │ ├── scripting.spec.ts │ ├── state-scope-in-pages.spec.ts │ └── state-var-scopes.spec.ts ├── tsconfig.bin.json ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── vitest.config.ts ``` # Files -------------------------------------------------------------------------------- /xmlui/dev-docs/react-fundamentals.md: -------------------------------------------------------------------------------- ```markdown 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 | ## `useState` - Local State Management 123 | 124 | **Purpose:** Manage component-local state that triggers re-renders when updated. 125 | 126 | **Syntax:** `const [state, setState] = useState(initialValue)` 127 | 128 | ### Basic Usage 129 | 130 | ```tsx 131 | function Counter() { 132 | const [count, setCount] = useState(0); 133 | 134 | return ( 135 | <div> 136 | <p>Count: {count}</p> 137 | <button onClick={() => setCount(count + 1)}>Increment</button> 138 | </div> 139 | ); 140 | } 141 | ``` 142 | 143 | ### Functional Updates 144 | 145 | Use functional updates when the new state depends on the previous state. This avoids stale closure issues. 146 | 147 | ```tsx 148 | function Counter() { 149 | const [count, setCount] = useState(0); 150 | 151 | const incrementTwice = () => { 152 | // ❌ WRONG - Both updates use the same `count` value 153 | setCount(count + 1); 154 | setCount(count + 1); // Still increments by 1 total 155 | 156 | // ✅ CORRECT - Each update uses the latest state 157 | setCount(prev => prev + 1); 158 | setCount(prev => prev + 1); // Increments by 2 total 159 | }; 160 | 161 | return <button onClick={incrementTwice}>+2</button>; 162 | } 163 | ``` 164 | 165 | ### Lazy Initialization 166 | 167 | For expensive initial state calculations, pass a function to `useState`. It only runs once on mount. 168 | 169 | ```tsx 170 | function ExpensiveComponent() { 171 | // ❌ WRONG - Runs on every render 172 | const [data, setData] = useState(expensiveComputation()); 173 | 174 | // ✅ CORRECT - Runs only once 175 | const [data, setData] = useState(() => expensiveComputation()); 176 | 177 | return <div>{data}</div>; 178 | } 179 | ``` 180 | 181 | ### Object and Array State 182 | 183 | State updates must be immutable. Create new objects/arrays instead of mutating existing ones. 184 | 185 | ```tsx 186 | function UserForm() { 187 | const [user, setUser] = useState({ name: "", email: "" }); 188 | const [items, setItems] = useState<string[]>([]); 189 | 190 | // Update object - spread to create new object 191 | const updateName = (name: string) => { 192 | setUser(prev => ({ ...prev, name })); 193 | }; 194 | 195 | // Update array - use immutable methods 196 | const addItem = (item: string) => { 197 | setItems(prev => [...prev, item]); 198 | }; 199 | 200 | const removeItem = (index: number) => { 201 | setItems(prev => prev.filter((_, i) => i !== index)); 202 | }; 203 | 204 | return <div>...</div>; 205 | } 206 | ``` 207 | 208 | ### State Batching 209 | 210 | React batches multiple state updates in event handlers into a single re-render for performance. 211 | 212 | ```tsx 213 | function Form() { 214 | const [name, setName] = useState(""); 215 | const [email, setEmail] = useState(""); 216 | 217 | const handleSubmit = () => { 218 | // Both updates batched - component re-renders once 219 | setName(""); 220 | setEmail(""); 221 | }; 222 | 223 | return <form onSubmit={handleSubmit}>...</form>; 224 | } 225 | ``` 226 | 227 | ### Common Patterns in XMLUI 228 | 229 | **Form Input State:** 230 | ```tsx 231 | function TextInput({ initialValue, onDidChange }: Props) { 232 | const [value, setValue] = useState(initialValue || ""); 233 | 234 | const handleChange = (e: ChangeEvent<HTMLInputElement>) => { 235 | const newValue = e.target.value; 236 | setValue(newValue); 237 | onDidChange?.(newValue); 238 | }; 239 | 240 | return <input value={value} onChange={handleChange} />; 241 | } 242 | ``` 243 | 244 | **Toggle State:** 245 | ```tsx 246 | function Collapsible({ children }: Props) { 247 | const [isOpen, setIsOpen] = useState(false); 248 | 249 | return ( 250 | <div> 251 | <button onClick={() => setIsOpen(prev => !prev)}> 252 | {isOpen ? "Collapse" : "Expand"} 253 | </button> 254 | {isOpen && <div>{children}</div>} 255 | </div> 256 | ); 257 | } 258 | ``` 259 | 260 | **Derived State (Avoid):** 261 | ```tsx 262 | // ❌ WRONG - Redundant state that can be computed 263 | function SearchList({ items }: Props) { 264 | const [query, setQuery] = useState(""); 265 | const [filtered, setFiltered] = useState(items); 266 | 267 | useEffect(() => { 268 | setFiltered(items.filter(item => item.includes(query))); 269 | }, [items, query]); 270 | } 271 | 272 | // ✅ CORRECT - Compute during render 273 | function SearchList({ items }: Props) { 274 | const [query, setQuery] = useState(""); 275 | const filtered = items.filter(item => item.includes(query)); 276 | } 277 | ``` 278 | 279 | ### When to Use useState 280 | 281 | **Use `useState` when:** 282 | - State is local to a single component 283 | - State updates are simple (primitives, simple objects) 284 | - No complex state transitions or validation logic 285 | 286 | **Consider alternatives when:** 287 | - State is shared across components → Use `useContext` or prop drilling 288 | - State logic is complex → Use `useReducer` 289 | - State synchronizes with external systems → Use `useEffect` + `useState` or `useSyncExternalStore` 290 | 291 | --- 292 | 293 | ## `useEffect` - Side Effects and Lifecycle 294 | 295 | **Purpose:** Run side effects after render (data fetching, subscriptions, DOM manipulation). 296 | 297 | **Syntax:** `useEffect(() => { /* effect */ return () => { /* cleanup */ } }, [dependencies])` 298 | 299 | ### Basic Usage 300 | 301 | ```tsx 302 | function UserProfile({ userId }: Props) { 303 | const [user, setUser] = useState(null); 304 | 305 | useEffect(() => { 306 | // Effect runs after render 307 | fetch(`/api/users/${userId}`) 308 | .then(res => res.json()) 309 | .then(data => setUser(data)); 310 | }, [userId]); // Re-run when userId changes 311 | 312 | return <div>{user?.name}</div>; 313 | } 314 | ``` 315 | 316 | **With async/await (using IIFE):** 317 | ```tsx 318 | function UserProfile({ userId }: Props) { 319 | const [user, setUser] = useState(null); 320 | 321 | useEffect(() => { 322 | // Can't make useEffect callback async directly, so use IIFE 323 | (async () => { 324 | const res = await fetch(`/api/users/${userId}`); 325 | const data = await res.json(); 326 | setUser(data); 327 | })(); 328 | }, [userId]); 329 | 330 | return <div>{user?.name}</div>; 331 | } 332 | ``` 333 | 334 | ### Cleanup Functions 335 | 336 | Return a cleanup function to prevent memory leaks and cancel ongoing operations. 337 | 338 | ```tsx 339 | function Chat({ roomId }: Props) { 340 | useEffect(() => { 341 | const connection = createConnection(roomId); 342 | connection.connect(); 343 | 344 | // Cleanup runs before next effect and on unmount 345 | return () => { 346 | connection.disconnect(); 347 | }; 348 | }, [roomId]); 349 | 350 | return <div>Connected to {roomId}</div>; 351 | } 352 | ``` 353 | 354 | ### Dependency Array Patterns 355 | 356 | ```tsx 357 | // ❌ WRONG - Missing dependencies (stale closure bug) 358 | useEffect(() => { 359 | console.log(count); // Always logs initial count value 360 | }, []); // Should include [count] 361 | 362 | // ❌ WRONG - Object in dependencies (runs every render) 363 | useEffect(() => { 364 | fetchData(config); 365 | }, [config]); // config is recreated every render 366 | 367 | // ✅ CORRECT - Destructure or memoize objects 368 | useEffect(() => { 369 | fetchData(config); 370 | }, [config.id, config.filter]); // Only primitive values 371 | 372 | // ✅ CORRECT - Empty array = run once on mount 373 | useEffect(() => { 374 | initializeApp(); 375 | return () => cleanupApp(); 376 | }, []); // No dependencies 377 | 378 | // ✅ CORRECT - No array = run after every render (rarely needed) 379 | useEffect(() => { 380 | updateDocumentTitle(); 381 | }); // Runs after every render 382 | ``` 383 | 384 | ### Common Patterns in XMLUI 385 | 386 | **Data Fetching:** 387 | ```tsx 388 | function DataComponent({ url }: Props) { 389 | const [data, setData] = useState(null); 390 | const [loading, setLoading] = useState(true); 391 | 392 | useEffect(() => { 393 | let cancelled = false; 394 | 395 | setLoading(true); 396 | fetch(url) 397 | .then(res => res.json()) 398 | .then(data => { 399 | if (!cancelled) { 400 | setData(data); 401 | setLoading(false); 402 | } 403 | }); 404 | 405 | return () => { cancelled = true; }; 406 | }, [url]); 407 | 408 | if (loading) return <div>Loading...</div>; 409 | return <div>{JSON.stringify(data)}</div>; 410 | } 411 | ``` 412 | 413 | **Event Listeners:** 414 | ```tsx 415 | function WindowSize() { 416 | const [size, setSize] = useState({ width: 0, height: 0 }); 417 | 418 | useEffect(() => { 419 | const handleResize = () => { 420 | setSize({ width: window.innerWidth, height: window.innerHeight }); 421 | }; 422 | 423 | window.addEventListener('resize', handleResize); 424 | handleResize(); // Initial size 425 | 426 | return () => window.removeEventListener('resize', handleResize); 427 | }, []); 428 | 429 | return <div>{size.width} x {size.height}</div>; 430 | } 431 | ``` 432 | 433 | **Syncing with External Systems:** 434 | ```tsx 435 | function ExternalStore({ store }: Props) { 436 | const [value, setValue] = useState(store.getValue()); 437 | 438 | useEffect(() => { 439 | // Subscribe to external store 440 | const unsubscribe = store.subscribe(newValue => { 441 | setValue(newValue); 442 | }); 443 | 444 | return unsubscribe; 445 | }, [store]); 446 | 447 | return <div>{value}</div>; 448 | } 449 | ``` 450 | 451 | ### When to Use useEffect 452 | 453 | **Use `useEffect` when:** 454 | - Fetching data from APIs 455 | - Setting up subscriptions or event listeners 456 | - Manually manipulating DOM elements 457 | - Integrating with non-React libraries 458 | - Logging or analytics 459 | 460 | **Avoid `useEffect` when:** 461 | - Computing derived values → Use direct calculation or `useMemo` 462 | - Handling user events → Use event handlers 463 | - Initializing state → Use `useState` initializer or `useMemo` 464 | 465 | --- 466 | 467 | ## `useRef` - Persistent Mutable References 468 | 469 | **Purpose:** Store mutable values that persist across renders without triggering re-renders. 470 | 471 | **Syntax:** `const ref = useRef(initialValue)` 472 | 473 | ### DOM References 474 | 475 | ```tsx 476 | function TextInput() { 477 | const inputRef = useRef<HTMLInputElement>(null); 478 | 479 | const focusInput = () => { 480 | inputRef.current?.focus(); 481 | }; 482 | 483 | return ( 484 | <div> 485 | <input ref={inputRef} /> 486 | <button onClick={focusInput}>Focus</button> 487 | </div> 488 | ); 489 | } 490 | ``` 491 | 492 | ### Storing Mutable Values 493 | 494 | ```tsx 495 | function Timer() { 496 | const [count, setCount] = useState(0); 497 | const intervalRef = useRef<NodeJS.Timeout>(); 498 | 499 | useEffect(() => { 500 | intervalRef.current = setInterval(() => { 501 | setCount(c => c + 1); 502 | }, 1000); 503 | 504 | return () => clearInterval(intervalRef.current); 505 | }, []); 506 | 507 | const stop = () => { 508 | clearInterval(intervalRef.current); 509 | }; 510 | 511 | return ( 512 | <div> 513 | {count} seconds 514 | <button onClick={stop}>Stop</button> 515 | </div> 516 | ); 517 | } 518 | ``` 519 | 520 | ### Avoiding Stale Closures 521 | 522 | ```tsx 523 | function Component({ callback }: Props) { 524 | const callbackRef = useRef(callback); 525 | 526 | // Keep ref updated with latest callback 527 | useEffect(() => { 528 | callbackRef.current = callback; 529 | }, [callback]); 530 | 531 | useEffect(() => { 532 | const interval = setInterval(() => { 533 | // Always uses latest callback 534 | callbackRef.current(); 535 | }, 1000); 536 | 537 | return () => clearInterval(interval); 538 | }, []); // No callback dependency needed 539 | 540 | return <div>Running...</div>; 541 | } 542 | ``` 543 | 544 | ### Common Patterns in XMLUI 545 | 546 | **Previous Value Tracking:** 547 | ```tsx 548 | function usePrevious<T>(value: T): T | undefined { 549 | const ref = useRef<T>(); 550 | 551 | useEffect(() => { 552 | ref.current = value; 553 | }, [value]); 554 | 555 | return ref.current; 556 | } 557 | 558 | function Component({ count }: Props) { 559 | const prevCount = usePrevious(count); 560 | 561 | return <div>Now: {count}, Before: {prevCount}</div>; 562 | } 563 | ``` 564 | 565 | ### Key Differences: useState vs useRef 566 | 567 | | Feature | `useState` | `useRef` | 568 | |---------|-----------|---------| 569 | | Triggers re-render | ✅ Yes | ❌ No | 570 | | Persists across renders | ✅ Yes | ✅ Yes | 571 | | Use for UI state | ✅ Yes | ❌ No | 572 | | Use for DOM access | ❌ No | ✅ Yes | 573 | | Use for mutable timers/intervals | ❌ No | ✅ Yes | 574 | 575 | --- 576 | 577 | ## `useMemo` - Expensive Computation Caching 578 | 579 | **Purpose:** Cache expensive calculation results between renders. 580 | 581 | **Syntax:** `const memoized = useMemo(() => computeValue(), [dependencies])` 582 | 583 | ### Basic Usage 584 | 585 | ```tsx 586 | function FilteredList({ items, filter }: Props) { 587 | // Only recomputes when items or filter changes 588 | const filtered = useMemo(() => { 589 | console.log('Filtering...'); 590 | return items.filter(item => item.includes(filter)); 591 | }, [items, filter]); 592 | 593 | return <ul>{filtered.map(item => <li key={item}>{item}</li>)}</ul>; 594 | } 595 | ``` 596 | 597 | ### Referential Stability 598 | 599 | ```tsx 600 | function Parent() { 601 | const [count, setCount] = useState(0); 602 | 603 | // ❌ WRONG - New object every render 604 | const config = { theme: 'dark', size: 'large' }; 605 | 606 | // ✅ CORRECT - Same object reference until dependencies change 607 | const config = useMemo(() => ({ theme: 'dark', size: 'large' }), []); 608 | 609 | return <ExpensiveChild config={config} />; 610 | } 611 | 612 | const ExpensiveChild = React.memo(({ config }: Props) => { 613 | // Only re-renders when config reference changes 614 | return <div>{config.theme}</div>; 615 | }); 616 | ``` 617 | 618 | ### Complex Computations 619 | 620 | ```tsx 621 | function DataAnalysis({ data }: Props) { 622 | const statistics = useMemo(() => { 623 | return { 624 | mean: data.reduce((a, b) => a + b, 0) / data.length, 625 | median: [...data].sort()[Math.floor(data.length / 2)], 626 | mode: findMode(data), 627 | stdDev: calculateStdDev(data), 628 | }; 629 | }, [data]); 630 | 631 | return <div>{JSON.stringify(statistics)}</div>; 632 | } 633 | ``` 634 | 635 | ### When to Use useMemo 636 | 637 | **Use `useMemo` when:** 638 | - Computation is expensive (>10ms) 639 | - Creating objects/arrays passed to memoized children 640 | - Calculations used in dependency arrays 641 | 642 | **Don't use `useMemo` when:** 643 | - Computation is cheap (<1ms) - premature optimization 644 | - Result only used once in render 645 | - Component rarely re-renders 646 | 647 | --- 648 | 649 | ## `useCallback` - Function Reference Caching 650 | 651 | **Purpose:** Cache function definitions between renders to prevent child re-renders. 652 | 653 | **Syntax:** `const memoized = useCallback(() => { /* function */ }, [dependencies])` 654 | 655 | ### Basic Usage 656 | 657 | ```tsx 658 | function Parent() { 659 | const [count, setCount] = useState(0); 660 | 661 | // ❌ WRONG - New function every render 662 | const handleClick = () => { 663 | console.log('Clicked'); 664 | }; 665 | 666 | // ✅ CORRECT - Same function reference 667 | const handleClick = useCallback(() => { 668 | console.log('Clicked'); 669 | }, []); 670 | 671 | return <MemoizedChild onClick={handleClick} />; 672 | } 673 | 674 | const MemoizedChild = React.memo(({ onClick }: Props) => { 675 | console.log('Child rendered'); 676 | return <button onClick={onClick}>Click</button>; 677 | }); 678 | ``` 679 | 680 | ### With Dependencies 681 | 682 | ```tsx 683 | function TodoList() { 684 | const [todos, setTodos] = useState([]); 685 | const [filter, setFilter] = useState('all'); 686 | 687 | const addTodo = useCallback((text: string) => { 688 | setTodos(prev => [...prev, { id: Date.now(), text }]); 689 | }, []); // No dependencies needed with functional updates 690 | 691 | const filterTodos = useCallback((todos) => { 692 | return filter === 'all' 693 | ? todos 694 | : todos.filter(t => t.status === filter); 695 | }, [filter]); // Recreated when filter changes 696 | 697 | return <TodoForm onAdd={addTodo} />; 698 | } 699 | ``` 700 | 701 | ### Common Pattern in XMLUI 702 | 703 | ```tsx 704 | function Form({ onSubmit }: Props) { 705 | const [name, setName] = useState(''); 706 | 707 | const handleSubmit = useCallback(() => { 708 | onSubmit({ name }); 709 | }, [name, onSubmit]); // Include all used values 710 | 711 | return ( 712 | <form onSubmit={handleSubmit}> 713 | <input value={name} onChange={e => setName(e.target.value)} /> 714 | <SubmitButton onSubmit={handleSubmit} /> 715 | </form> 716 | ); 717 | } 718 | ``` 719 | 720 | ### useCallback vs useMemo 721 | 722 | ```tsx 723 | // These are equivalent: 724 | const memoized = useCallback(() => doSomething(), [dep]); 725 | const memoized = useMemo(() => () => doSomething(), [dep]); 726 | 727 | // useCallback is just sugar for useMemo returning a function 728 | ``` 729 | 730 | --- 731 | 732 | ## `useReducer` - Complex State Logic 733 | 734 | **Purpose:** Manage complex state with predictable state transitions using reducer pattern. 735 | 736 | **Syntax:** `const [state, dispatch] = useReducer(reducer, initialState)` 737 | 738 | ### Basic Usage 739 | 740 | ```tsx 741 | type State = { count: number }; 742 | type Action = { type: 'increment' } | { type: 'decrement' } | { type: 'reset' }; 743 | 744 | function reducer(state: State, action: Action): State { 745 | switch (action.type) { 746 | case 'increment': 747 | return { count: state.count + 1 }; 748 | case 'decrement': 749 | return { count: state.count - 1 }; 750 | case 'reset': 751 | return { count: 0 }; 752 | default: 753 | return state; 754 | } 755 | } 756 | 757 | function Counter() { 758 | const [state, dispatch] = useReducer(reducer, { count: 0 }); 759 | 760 | return ( 761 | <div> 762 | Count: {state.count} 763 | <button onClick={() => dispatch({ type: 'increment' })}>+</button> 764 | <button onClick={() => dispatch({ type: 'decrement' })}>-</button> 765 | <button onClick={() => dispatch({ type: 'reset' })}>Reset</button> 766 | </div> 767 | ); 768 | } 769 | ``` 770 | 771 | ### With Immer for Immutability 772 | 773 | ```tsx 774 | import produce from 'immer'; 775 | 776 | const reducer = produce((state, action) => { 777 | switch (action.type) { 778 | case 'ADD_TODO': 779 | state.todos.push(action.payload); // Direct mutation with Immer 780 | break; 781 | case 'TOGGLE_TODO': 782 | const todo = state.todos.find(t => t.id === action.payload); 783 | if (todo) todo.completed = !todo.completed; 784 | break; 785 | } 786 | }); 787 | ``` 788 | 789 | ### Common Pattern in XMLUI (Form State) 790 | 791 | ```tsx 792 | type FormState = { 793 | values: Record<string, any>; 794 | errors: Record<string, string>; 795 | touched: Record<string, boolean>; 796 | }; 797 | 798 | type FormAction = 799 | | { type: 'SET_VALUE'; field: string; value: any } 800 | | { type: 'SET_ERROR'; field: string; error: string } 801 | | { type: 'TOUCH_FIELD'; field: string } 802 | | { type: 'RESET' }; 803 | 804 | function formReducer(state: FormState, action: FormAction): FormState { 805 | switch (action.type) { 806 | case 'SET_VALUE': 807 | return { 808 | ...state, 809 | values: { ...state.values, [action.field]: action.value }, 810 | }; 811 | case 'SET_ERROR': 812 | return { 813 | ...state, 814 | errors: { ...state.errors, [action.field]: action.error }, 815 | }; 816 | case 'TOUCH_FIELD': 817 | return { 818 | ...state, 819 | touched: { ...state.touched, [action.field]: true }, 820 | }; 821 | case 'RESET': 822 | return { values: {}, errors: {}, touched: {} }; 823 | default: 824 | return state; 825 | } 826 | } 827 | ``` 828 | 829 | ### When to Use useReducer 830 | 831 | **Use `useReducer` when:** 832 | - Multiple related state values 833 | - Complex state transitions 834 | - Next state depends on previous state 835 | - Want to separate state logic from component 836 | 837 | **Use `useState` when:** 838 | - Simple independent state values 839 | - State updates are straightforward 840 | - No complex validation or transformation 841 | 842 | --- 843 | 844 | ## `useContext` - Consume Context Values 845 | 846 | **Purpose:** Access values from React Context without prop drilling. Always used with `createContext` - see that section for full pattern. 847 | 848 | **Syntax:** `const value = useContext(MyContext)` 849 | 850 | ### Basic Usage 851 | 852 | ```tsx 853 | // Assuming ThemeContext was created with createContext 854 | const ThemeContext = createContext<'light' | 'dark'>('light'); 855 | 856 | // Consumer component 857 | function ThemedButton() { 858 | const theme = useContext(ThemeContext); 859 | 860 | return <button className={`btn-${theme}`}>Click</button>; 861 | } 862 | ``` 863 | 864 | ### Creating Custom Hooks 865 | 866 | Wrap `useContext` in custom hooks for better error messages and type safety: 867 | 868 | ```tsx 869 | // ❌ Using useContext directly - no error if outside provider 870 | function Component() { 871 | const auth = useContext(AuthContext); // Might be null! 872 | return <div>{auth?.user?.name}</div>; // Need null checks everywhere 873 | } 874 | 875 | // ✅ Custom hook with validation 876 | function useAuth() { 877 | const context = useContext(AuthContext); 878 | if (!context) { 879 | throw new Error('useAuth must be used within AuthProvider'); 880 | } 881 | return context; // TypeScript knows it's not null 882 | } 883 | 884 | function Component() { 885 | const { user } = useAuth(); // Guaranteed to exist or throw 886 | return <div>{user.name}</div>; // No null checks needed 887 | } 888 | ``` 889 | 890 | ### Common Pattern in XMLUI 891 | 892 | ```tsx 893 | // Pattern: Create context + custom hook together 894 | const FormContext = createContext<FormContextValue | null>(null); 895 | 896 | export function useFormContext() { 897 | const context = useContext(FormContext); 898 | if (!context) { 899 | throw new Error('Form fields must be used within a Form component'); 900 | } 901 | return context; 902 | } 903 | 904 | // Usage in components 905 | function FormField({ name }: Props) { 906 | const { values, errors, setFieldValue } = useFormContext(); 907 | 908 | return ( 909 | <div> 910 | <input 911 | value={values[name]} 912 | onChange={e => setFieldValue(name, e.target.value)} 913 | /> 914 | {errors[name] && <span>{errors[name]}</span>} 915 | </div> 916 | ); 917 | } 918 | ``` 919 | 920 | ### When to Use useContext 921 | 922 | **Use `useContext` for:** 923 | - Consuming values from context created with `createContext` 924 | - Avoiding prop drilling through many component levels 925 | - Accessing global/shared state (theme, auth, config) 926 | 927 | **Always:** 928 | - Wrap in custom hook with error checking 929 | - Document that component must be used within a provider 930 | 931 | **Note:** See `createContext` section for complete examples of creating contexts, providers, and performance optimization. 932 | 933 | --- 934 | 935 | ## `useTransition` - Non-Urgent Updates 936 | 937 | **Purpose:** Mark state updates as non-urgent to keep UI responsive during slow updates. 938 | 939 | **Syntax:** `const [isPending, startTransition] = useTransition()` 940 | 941 | ### Basic Usage 942 | 943 | ```tsx 944 | function SearchResults() { 945 | const [query, setQuery] = useState(''); 946 | const [results, setResults] = useState([]); 947 | const [isPending, startTransition] = useTransition(); 948 | 949 | const handleSearch = (value: string) => { 950 | setQuery(value); // Urgent: Update input immediately 951 | 952 | startTransition(() => { 953 | // Non-urgent: Filter can be slow, won't block input 954 | const filtered = largeDataset.filter(item => item.includes(value)); 955 | setResults(filtered); 956 | }); 957 | }; 958 | 959 | return ( 960 | <div> 961 | <input value={query} onChange={e => handleSearch(e.target.value)} /> 962 | {isPending && <div>Loading...</div>} 963 | <ul>{results.map(r => <li key={r.id}>{r.name}</li>)}</ul> 964 | </div> 965 | ); 966 | } 967 | ``` 968 | 969 | ### Usage in XMLUI 970 | 971 | XMLUI uses `useTransition` in container infrastructure to batch state changes during script execution, preventing excessive re-renders. 972 | 973 | ```tsx 974 | function StatementExecutor({ statements }: Props) { 975 | const [, startTransition] = useTransition(); 976 | 977 | const executeStatements = () => { 978 | startTransition(() => { 979 | // Multiple state changes batched as low-priority 980 | statements.forEach(stmt => { 981 | executeStatement(stmt); // May trigger multiple state updates 982 | }); 983 | }); 984 | }; 985 | 986 | return <button onClick={executeStatements}>Execute</button>; 987 | } 988 | ``` 989 | 990 | --- 991 | 992 | ## `useLayoutEffect` - Synchronous DOM Effects 993 | 994 | **Purpose:** Run effects synchronously after DOM mutations but before browser paint. 995 | 996 | **Syntax:** `useLayoutEffect(() => { /* effect */ }, [dependencies])` 997 | 998 | ### When to Use 999 | 1000 | ```tsx 1001 | function Tooltip() { 1002 | const [position, setPosition] = useState({ x: 0, y: 0 }); 1003 | const ref = useRef<HTMLDivElement>(null); 1004 | 1005 | // ✅ CORRECT - Use useLayoutEffect to measure DOM before paint 1006 | useLayoutEffect(() => { 1007 | if (ref.current) { 1008 | const rect = ref.current.getBoundingClientRect(); 1009 | setPosition({ 1010 | x: rect.left + rect.width / 2, 1011 | y: rect.top - 10, 1012 | }); 1013 | } 1014 | }, []); 1015 | 1016 | return <div ref={ref} style={{ left: position.x, top: position.y }}>Tooltip</div>; 1017 | } 1018 | 1019 | // ❌ WRONG - useEffect causes flicker as position updates after paint 1020 | useEffect(() => { 1021 | // DOM measurements happen after screen paint 1022 | // User sees element jump from old to new position 1023 | }, []); 1024 | ``` 1025 | 1026 | ### Use Cases 1027 | 1028 | - Measuring DOM elements before paint 1029 | - Synchronizing scrollbar position 1030 | - Preventing layout shift/flicker 1031 | - Integrating with DOM-based libraries 1032 | 1033 | **Warning:** Blocks visual updates. Use `useEffect` unless you specifically need synchronous behavior. 1034 | 1035 | --- 1036 | 1037 | ## `useId` - Unique ID Generation 1038 | 1039 | **Purpose:** Generate stable unique IDs for accessibility attributes. 1040 | 1041 | **Syntax:** `const id = useId()` 1042 | 1043 | ### Basic Usage 1044 | 1045 | ```tsx 1046 | function FormField({ label }: Props) { 1047 | const id = useId(); 1048 | 1049 | return ( 1050 | <div> 1051 | <label htmlFor={id}>{label}</label> 1052 | <input id={id} /> 1053 | </div> 1054 | ); 1055 | } 1056 | ``` 1057 | 1058 | ### Multiple IDs 1059 | 1060 | ```tsx 1061 | function ComplexForm() { 1062 | const id = useId(); 1063 | 1064 | return ( 1065 | <div> 1066 | <label htmlFor={`${id}-name`}>Name</label> 1067 | <input id={`${id}-name`} aria-describedby={`${id}-name-hint`} /> 1068 | <span id={`${id}-name-hint`}>Enter your full name</span> 1069 | 1070 | <label htmlFor={`${id}-email`}>Email</label> 1071 | <input id={`${id}-email`} /> 1072 | </div> 1073 | ); 1074 | } 1075 | ``` 1076 | 1077 | **Why not just use a counter?** `useId` generates IDs that are stable across server and client rendering, preventing hydration mismatches. 1078 | 1079 | --- 1080 | 1081 | ## `useInsertionEffect` - Style Injection 1082 | 1083 | **Purpose:** Insert styles into DOM before layout effects run. Used by CSS-in-JS libraries. 1084 | 1085 | **Syntax:** `useInsertionEffect(() => { /* insert styles */ }, [dependencies])` 1086 | 1087 | ### Typical Usage (CSS-in-JS libraries) 1088 | 1089 | ```tsx 1090 | function useCSS(rule: string) { 1091 | useInsertionEffect(() => { 1092 | // Inject styles before layout reads 1093 | const style = document.createElement('style'); 1094 | style.textContent = rule; 1095 | document.head.appendChild(style); 1096 | 1097 | return () => document.head.removeChild(style); 1098 | }, [rule]); 1099 | } 1100 | ``` 1101 | 1102 | **Note:** Rarely used directly in application code. Primarily for library authors building CSS-in-JS solutions. XMLUI uses this in `StyleContext` for theme style injection. 1103 | 1104 | **Effect execution order:** 1105 | 1. `useInsertionEffect` - Insert styles 1106 | 2. `useLayoutEffect` - Measure layout 1107 | 3. Browser paints 1108 | 4. `useEffect` - Other side effects 1109 | 1110 | --- 1111 | 1112 | ## `forwardRef` - Ref Forwarding to Child Components 1113 | 1114 | **Purpose:** Allow parent components to access DOM nodes or component instances of child components by forwarding refs through component boundaries. 1115 | 1116 | **Syntax:** `const Component = forwardRef((props, ref) => { ... })` 1117 | 1118 | ### Basic Usage 1119 | 1120 | ```tsx 1121 | const TextInput = forwardRef<HTMLInputElement, Props>( 1122 | function TextInput({ label, ...props }, forwardedRef) { 1123 | return ( 1124 | <div> 1125 | <label>{label}</label> 1126 | <input ref={forwardedRef} {...props} /> 1127 | </div> 1128 | ); 1129 | } 1130 | ); 1131 | 1132 | // Parent can now access the input element 1133 | function Form() { 1134 | const inputRef = useRef<HTMLInputElement>(null); 1135 | 1136 | const focusInput = () => { 1137 | inputRef.current?.focus(); 1138 | }; 1139 | 1140 | return ( 1141 | <div> 1142 | <TextInput ref={inputRef} label="Name" /> 1143 | <button onClick={focusInput}>Focus Input</button> 1144 | </div> 1145 | ); 1146 | } 1147 | ``` 1148 | 1149 | ### TypeScript Generic Syntax 1150 | 1151 | ```tsx 1152 | // Explicitly type both the ref and props 1153 | const Component = forwardRef<RefType, PropsType>( 1154 | function Component(props, ref) { 1155 | return <div ref={ref}>...</div>; 1156 | } 1157 | ); 1158 | 1159 | // Example with HTMLDivElement 1160 | const Card = forwardRef<HTMLDivElement, CardProps>( 1161 | function Card({ children, className }, ref) { 1162 | return ( 1163 | <div ref={ref} className={className}> 1164 | {children} 1165 | </div> 1166 | ); 1167 | } 1168 | ); 1169 | ``` 1170 | 1171 | **Why explicit typing matters:** 1172 | 1173 | Without generic syntax, TypeScript infers types from the function signature, which can lead to several issues: 1174 | 1175 | ```tsx 1176 | // ❌ WRONG - Without explicit generics 1177 | const Input = forwardRef(function Input(props: Props, ref) { 1178 | // TypeScript infers ref as: ForwardedRef<unknown> 1179 | // This means: 1180 | // 1. No autocomplete for ref.current properties 1181 | // 2. No type checking when assigning ref to JSX elements 1182 | // 3. Parent components can pass wrong ref type without errors 1183 | return <input ref={ref} />; // Type error: ref might not be compatible! 1184 | }); 1185 | 1186 | // Usage - TypeScript won't catch this error: 1187 | const divRef = useRef<HTMLDivElement>(null); 1188 | <Input ref={divRef} /> // Should error but doesn't - expecting HTMLInputElement! 1189 | 1190 | // ✅ CORRECT - With explicit generics 1191 | const Input = forwardRef<HTMLInputElement, Props>( 1192 | function Input(props, ref) { 1193 | // TypeScript knows ref is: ForwardedRef<HTMLInputElement> 1194 | // Benefits: 1195 | // 1. Autocomplete works: ref.current?.focus() 1196 | // 2. Type checking ensures ref matches JSX element 1197 | // 3. Parent must pass correct ref type 1198 | return <input ref={ref} />; // Type safe! 1199 | } 1200 | ); 1201 | 1202 | // Usage - TypeScript catches the error: 1203 | const divRef = useRef<HTMLDivElement>(null); 1204 | <Input ref={divRef} /> // ❌ Type error: expected RefObject<HTMLInputElement> 1205 | ``` 1206 | 1207 | **Key problems without explicit generics:** 1208 | 1. **Loss of type safety** - Parent can pass incompatible ref types 1209 | 2. **No IntelliSense** - No autocomplete for `ref.current` properties 1210 | 3. **Runtime errors** - Type mismatches only discovered at runtime 1211 | 4. **Harder refactoring** - Changes to ref type don't propagate to consumers 1212 | 1213 | **Best practice:** Always specify both generic parameters explicitly in XMLUI components. 1214 | 1215 | ### Composing Multiple Refs 1216 | 1217 | **The Problem:** Components often need to manage multiple refs pointing to the same DOM element: 1218 | 1. **Internal ref** - Component's own logic (measurements, animations, focus) 1219 | 2. **Forwarded ref** - Parent needs access to the DOM element 1220 | 3. **Third-party refs** - Integration with libraries (Popper, Radix UI, etc.) 1221 | 1222 | **The Solution:** Use `composeRefs` from `@radix-ui/react-compose-refs` to merge multiple refs into one. 1223 | 1224 | **Why you need to compose refs:** 1225 | 1226 | | Use Case | Example | Reason | 1227 | |----------|---------|--------| 1228 | | **Internal logic + parent access** | Auto-resize textarea | Component measures scrollHeight, parent needs focus() | 1229 | | **Library integration** | Popover/Tooltip | Popper needs ref for positioning, parent needs ref for control | 1230 | | **Wrapper components** | Container with single child | Parent ref applies to child, child has own ref | 1231 | | **Multiple behaviors** | Draggable element | Drag library needs ref, resize observer needs ref, parent needs ref | 1232 | 1233 | **Key differences: Inner vs Forwarded refs:** 1234 | 1235 | | Aspect | Inner Ref | Forwarded Ref | 1236 | |--------|-----------|---------------| 1237 | | **Created by** | Component itself with `useRef()` | Parent component | 1238 | | **Purpose** | Internal component logic | Parent needs DOM access | 1239 | | **Type** | Always `RefObject<T>` | Can be `RefObject<T>`, `RefCallback<T>`, or `null` | 1240 | | **Guaranteed to exist** | Yes - always has `.current` property | No - parent might not pass a ref | 1241 | | **When to use** | Component needs DOM access for its own behavior | Expose DOM element to parent | 1242 | 1243 | **Example 1: Internal + Forwarded (most common in XMLUI):** 1244 | 1245 | ```tsx 1246 | import { composeRefs } from "@radix-ui/react-compose-refs"; 1247 | 1248 | function TextArea({ value, onChange }: Props, forwardedRef: Ref<HTMLTextAreaElement>) { 1249 | // Inner ref: Component creates and owns this for auto-resize logic 1250 | const innerRef = useRef<HTMLTextAreaElement>(null); 1251 | 1252 | // Compose both refs - textarea element needs both 1253 | const composedRef = forwardedRef 1254 | ? composeRefs(innerRef, forwardedRef) 1255 | : innerRef; 1256 | 1257 | useEffect(() => { 1258 | // ✅ CORRECT: Use innerRef for internal logic 1259 | // It's guaranteed to exist and have .current property 1260 | if (innerRef.current) { 1261 | innerRef.current.style.height = 'auto'; 1262 | innerRef.current.style.height = `${innerRef.current.scrollHeight}px`; 1263 | } 1264 | 1265 | // ❌ WRONG: Don't use forwardedRef directly 1266 | // if (forwardedRef?.current) { ... } // Type error: Ref<T> might be a callback! 1267 | }, [value]); 1268 | 1269 | return <textarea ref={composedRef} value={value} onChange={onChange} />; 1270 | } 1271 | 1272 | export const AutoResizeTextArea = forwardRef(TextArea); 1273 | 1274 | // Parent usage: 1275 | function Form() { 1276 | const textareaRef = useRef<HTMLTextAreaElement>(null); 1277 | 1278 | const focusTextarea = () => { 1279 | textareaRef.current?.focus(); // Parent can access via forwarded ref 1280 | }; 1281 | 1282 | return <AutoResizeTextArea ref={textareaRef} />; // Component auto-resizes via inner ref 1283 | } 1284 | ``` 1285 | 1286 | **Example 2: Library Integration (Popper + Forwarded):** 1287 | 1288 | ```tsx 1289 | function Select({ options }: Props, forwardedRef: Ref<HTMLButtonElement>) { 1290 | // Popper library needs a ref for positioning 1291 | const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null); 1292 | 1293 | // Compose library ref setter with forwarded ref 1294 | const composedRef = forwardedRef 1295 | ? composeRefs(setReferenceElement, forwardedRef) 1296 | : setReferenceElement; 1297 | 1298 | return ( 1299 | <> 1300 | <button ref={composedRef}>Select</button> 1301 | <Popper referenceElement={referenceElement}> 1302 | {/* Dropdown content */} 1303 | </Popper> 1304 | </> 1305 | ); 1306 | } 1307 | 1308 | export const SelectComponent = forwardRef(Select); 1309 | ``` 1310 | 1311 | **Example 3: Wrapper Component (Parent + Child refs):** 1312 | 1313 | ```tsx 1314 | function Container({ children }: Props, ref: Ref<HTMLElement>) { 1315 | const renderedChild = renderChild(children); 1316 | 1317 | // If single child, compose parent's ref with child's existing ref 1318 | if (isValidElement(renderedChild)) { 1319 | return cloneElement(renderedChild, { 1320 | ref: composeRefs(ref, (renderedChild as any).ref), 1321 | }); 1322 | } 1323 | 1324 | return renderedChild; 1325 | } 1326 | 1327 | export const ContainerComponent = forwardRef(Container); 1328 | ``` 1329 | 1330 | **Example 4: Multiple Behaviors (Drag + Resize + Forward):** 1331 | 1332 | ```tsx 1333 | function DraggablePanel(props: Props, forwardedRef: Ref<HTMLDivElement>) { 1334 | const dragRef = useRef<HTMLDivElement>(null); 1335 | const resizeObserverRef = useRef<HTMLDivElement>(null); 1336 | 1337 | // Compose all three refs 1338 | const composedRef = composeRefs( 1339 | dragRef, 1340 | resizeObserverRef, 1341 | forwardedRef || null 1342 | ); 1343 | 1344 | useDragLogic(dragRef); 1345 | useResizeObserver(resizeObserverRef); 1346 | 1347 | return <div ref={composedRef}>Draggable and resizable</div>; 1348 | } 1349 | ``` 1350 | 1351 | **When to compose refs:** 1352 | - Component needs internal ref AND parent needs access 1353 | - Integrating with libraries that require refs (Popper, React DnD, etc.) 1354 | - Wrapping components that need to forward refs to children 1355 | - Multiple hooks/effects need refs to the same element 1356 | 1357 | **How `composeRefs` works:** 1358 | - Accepts multiple refs (RefObjects, callbacks, or null) 1359 | - Returns a single callback ref that updates all provided refs 1360 | - Handles both RefObject (sets `.current`) and callback refs (calls function) 1361 | - Safely ignores `null`/`undefined` refs 1362 | 1363 | ### When to Use forwardRef 1364 | 1365 | **Use `forwardRef` when:** 1366 | - Building reusable components that wrap DOM elements 1367 | - Parent needs direct DOM access (focus, scroll, measurements) 1368 | - Integrating with third-party libraries requiring refs 1369 | - Creating form components that need imperative control 1370 | 1371 | **Don't use `forwardRef` when:** 1372 | - Component doesn't wrap a single DOM element 1373 | - Refs aren't needed by parent components 1374 | - You can solve the problem with callbacks/props instead 1375 | 1376 | ### Common Mistakes 1377 | 1378 | ```tsx 1379 | // ❌ WRONG - Forgetting to attach ref to DOM element 1380 | const Bad = forwardRef((props, ref) => { 1381 | return <div>{props.children}</div>; // ref is ignored! 1382 | }); 1383 | 1384 | // ✅ CORRECT - Always attach ref to actual DOM element 1385 | const Good = forwardRef((props, ref) => { 1386 | return <div ref={ref}>{props.children}</div>; 1387 | }); 1388 | 1389 | // ❌ WRONG - Attaching ref to component (won't work) 1390 | const AlsoBad = forwardRef((props, ref) => { 1391 | return <CustomComponent ref={ref} />; // CustomComponent must also use forwardRef 1392 | }); 1393 | 1394 | // ✅ CORRECT - Forward through nested components 1395 | const CustomComponent = forwardRef((props, ref) => { 1396 | return <div ref={ref}>...</div>; 1397 | }); 1398 | 1399 | const AlsoGood = forwardRef((props, ref) => { 1400 | return <CustomComponent ref={ref} />; // Works because CustomComponent forwards 1401 | }); 1402 | ``` 1403 | 1404 | --- 1405 | 1406 | ## `memo` - Component Memoization 1407 | 1408 | **Purpose:** Prevent unnecessary re-renders by memoizing components. Re-renders only when props actually change. 1409 | 1410 | **Syntax:** `const MemoizedComponent = memo(Component, arePropsEqual?)` 1411 | 1412 | ### Basic Usage 1413 | 1414 | ```tsx 1415 | const ExpensiveComponent = memo(function ExpensiveComponent({ data }: Props) { 1416 | console.log('Rendering ExpensiveComponent'); // Only logs when data changes 1417 | 1418 | return ( 1419 | <div> 1420 | {data.map(item => <Item key={item.id} {...item} />)} 1421 | </div> 1422 | ); 1423 | }); 1424 | 1425 | function Parent() { 1426 | const [count, setCount] = useState(0); 1427 | const data = useMemo(() => computeData(), []); // Stable reference 1428 | 1429 | return ( 1430 | <div> 1431 | <ExpensiveComponent data={data} /> {/* Doesn't re-render when count changes */} 1432 | <button onClick={() => setCount(c => c + 1)}>Count: {count}</button> 1433 | </div> 1434 | ); 1435 | } 1436 | ``` 1437 | 1438 | ### How memo Works 1439 | 1440 | ```tsx 1441 | // ❌ WITHOUT memo - re-renders every time parent renders 1442 | function Child({ name }: Props) { 1443 | console.log('Child rendered'); // Logs on every parent render 1444 | return <div>{name}</div>; 1445 | } 1446 | 1447 | // ✅ WITH memo - only re-renders when props change 1448 | const Child = memo(function Child({ name }: Props) { 1449 | console.log('Child rendered'); // Only logs when name changes 1450 | return <div>{name}</div>; 1451 | }); 1452 | 1453 | function Parent() { 1454 | const [count, setCount] = useState(0); 1455 | 1456 | return ( 1457 | <div> 1458 | <Child name="Alice" /> {/* With memo: doesn't re-render when count changes */} 1459 | <button onClick={() => setCount(c => c + 1)}>Count: {count}</button> 1460 | </div> 1461 | ); 1462 | } 1463 | ``` 1464 | 1465 | ### Custom Comparison Function 1466 | 1467 | ```tsx 1468 | // Default: shallow comparison of props 1469 | const DefaultMemo = memo(Component); 1470 | 1471 | // Custom: deep comparison or specific logic 1472 | const CustomMemo = memo(Component, (prevProps, nextProps) => { 1473 | // Return true if props are equal (skip re-render) 1474 | // Return false if props changed (do re-render) 1475 | return prevProps.id === nextProps.id && 1476 | prevProps.data.length === nextProps.data.length; 1477 | }); 1478 | 1479 | // Example: Only re-render if specific props change 1480 | const SmartComponent = memo( 1481 | function SmartComponent({ id, data, onChange }: Props) { 1482 | return <div onClick={onChange}>{data[id]}</div>; 1483 | }, 1484 | (prev, next) => { 1485 | // Ignore onChange function changes, only check id and data 1486 | return prev.id === next.id && prev.data === next.data; 1487 | } 1488 | ); 1489 | ``` 1490 | 1491 | ### Combining memo with forwardRef 1492 | 1493 | ```tsx 1494 | // Correct order: memo(forwardRef(...)) 1495 | export const IFrame = memo(forwardRef(function IFrame( 1496 | { src, title, style }: Props, 1497 | ref: ForwardedRef<HTMLIFrameElement>, 1498 | ) { 1499 | return <iframe ref={ref} src={src} title={title} style={style} />; 1500 | })); 1501 | 1502 | // Also works with TypeScript generics 1503 | export const Avatar = memo(forwardRef<HTMLDivElement, AvatarProps>( 1504 | function Avatar({ name, src, size }, ref) { 1505 | return <div ref={ref} className={`avatar-${size}`}>{name}</div>; 1506 | } 1507 | )); 1508 | ``` 1509 | 1510 | ### Common Pattern in XMLUI 1511 | 1512 | ```tsx 1513 | // Container components are memoized to prevent re-renders 1514 | export const Container = memo( 1515 | forwardRef(function Container(props, ref) { 1516 | // Complex rendering logic 1517 | return <div ref={ref}>...</div>; 1518 | }) 1519 | ); 1520 | 1521 | // List row components are memoized for performance 1522 | const TreeRow = memo(({ index, style, data }: ListChildComponentProps) => { 1523 | const item = data.items[index]; 1524 | return <div style={style}>{item.name}</div>; 1525 | }); 1526 | 1527 | // Option components memoized to prevent re-renders during filtering 1528 | export const OptionNative = memo((props: Option) => { 1529 | return <div className="option">{props.label}</div>; 1530 | }); 1531 | ``` 1532 | 1533 | ### When to Use memo 1534 | 1535 | **Use `memo` when:** 1536 | - Component re-renders frequently with same props 1537 | - Component has expensive rendering logic 1538 | - Component is in a large list (virtualized rows) 1539 | - Parent re-renders often but child props rarely change 1540 | 1541 | **Don't use `memo` when:** 1542 | - Component already re-renders rarely 1543 | - Props change on every render anyway 1544 | - Rendering is cheap (<1ms) 1545 | - Premature optimization (profile first!) 1546 | 1547 | ### Common Pitfalls 1548 | 1549 | ```tsx 1550 | // ❌ WRONG - New object/array/function on every render defeats memo 1551 | function Parent() { 1552 | const [count, setCount] = useState(0); 1553 | 1554 | return ( 1555 | <> 1556 | <MemoChild data={{ value: 123 }} /> {/* New object every render! */} 1557 | <MemoChild items={[1, 2, 3]} /> {/* New array every render! */} 1558 | <MemoChild onClick={() => {}} /> {/* New function every render! */} 1559 | </> 1560 | ); 1561 | } 1562 | 1563 | // ✅ CORRECT - Stable references with useMemo/useCallback 1564 | function Parent() { 1565 | const [count, setCount] = useState(0); 1566 | const data = useMemo(() => ({ value: 123 }), []); 1567 | const items = useMemo(() => [1, 2, 3], []); 1568 | const handleClick = useCallback(() => {}, []); 1569 | 1570 | return ( 1571 | <> 1572 | <MemoChild data={data} /> {/* Same reference */} 1573 | <MemoChild items={items} /> {/* Same reference */} 1574 | <MemoChild onClick={handleClick} /> {/* Same reference */} 1575 | </> 1576 | ); 1577 | } 1578 | ``` 1579 | 1580 | --- 1581 | 1582 | ## `createContext` - Context Creation 1583 | 1584 | **Purpose:** Create a context for sharing state across component trees without prop drilling. 1585 | 1586 | **Syntax:** `const MyContext = createContext(defaultValue)` 1587 | 1588 | ### Basic Pattern 1589 | 1590 | ```tsx 1591 | import { createContext, useContext, useState } from 'react'; 1592 | 1593 | // 1. Create context with default value 1594 | const ThemeContext = createContext<'light' | 'dark'>('light'); 1595 | 1596 | // 2. Create provider component 1597 | function ThemeProvider({ children }: { children: ReactNode }) { 1598 | const [theme, setTheme] = useState<'light' | 'dark'>('light'); 1599 | 1600 | return ( 1601 | <ThemeContext.Provider value={theme}> 1602 | {children} 1603 | </ThemeContext.Provider> 1604 | ); 1605 | } 1606 | 1607 | // 3. Create custom hook for consuming context 1608 | function useTheme() { 1609 | return useContext(ThemeContext); 1610 | } 1611 | 1612 | // 4. Use in components 1613 | function Button() { 1614 | const theme = useTheme(); 1615 | return <button className={`btn-${theme}`}>Click</button>; 1616 | } 1617 | ``` 1618 | 1619 | ### Context with Complex State 1620 | 1621 | ```tsx 1622 | type SelectContextValue = { 1623 | multiSelect?: boolean; 1624 | value: ValueType | null; 1625 | onChange?: (selectedValue: SingleValueType) => void; 1626 | setOpen: (open: boolean) => void; 1627 | options: Set<Option>; 1628 | }; 1629 | 1630 | export const SelectContext = createContext<SelectContextValue>({ 1631 | value: null, 1632 | onChange: () => {}, 1633 | setOpen: () => {}, 1634 | options: new Set(), 1635 | }); 1636 | 1637 | export function useSelect() { 1638 | return useContext(SelectContext); 1639 | } 1640 | 1641 | // Provider usage 1642 | function Select({ children }: Props) { 1643 | const [value, setValue] = useState(null); 1644 | const [open, setOpen] = useState(false); 1645 | 1646 | const contextValue = useMemo(() => ({ 1647 | value, 1648 | onChange: setValue, 1649 | setOpen, 1650 | options: new Set(), 1651 | }), [value, open]); 1652 | 1653 | return ( 1654 | <SelectContext.Provider value={contextValue}> 1655 | {children} 1656 | </SelectContext.Provider> 1657 | ); 1658 | } 1659 | ``` 1660 | 1661 | ### Context with Type Safety 1662 | 1663 | ```tsx 1664 | // Require context to be used within provider 1665 | type AuthContext = { 1666 | user: User | null; 1667 | login: (credentials: Credentials) => Promise<void>; 1668 | logout: () => void; 1669 | }; 1670 | 1671 | const AuthContext = createContext<AuthContext | null>(null); 1672 | 1673 | export function useAuth() { 1674 | const context = useContext(AuthContext); 1675 | if (!context) { 1676 | throw new Error('useAuth must be used within AuthProvider'); 1677 | } 1678 | return context; 1679 | } 1680 | 1681 | function AuthProvider({ children }: Props) { 1682 | const [user, setUser] = useState<User | null>(null); 1683 | 1684 | const value = useMemo(() => ({ 1685 | user, 1686 | login: async (creds: Credentials) => { /* ... */ }, 1687 | logout: () => setUser(null), 1688 | }), [user]); 1689 | 1690 | return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>; 1691 | } 1692 | ``` 1693 | 1694 | ### Common Patterns in XMLUI 1695 | 1696 | **Form Context:** 1697 | ```tsx 1698 | const FormContext = createContext<FormContextValue | null>(null); 1699 | 1700 | export function useFormContext() { 1701 | const context = useContext(FormContext); 1702 | if (!context) { 1703 | throw new Error('Form fields must be used within a Form component'); 1704 | } 1705 | return context; 1706 | } 1707 | ``` 1708 | 1709 | **List Context:** 1710 | ```tsx 1711 | export const ListContext = React.createContext<IExpandableListContext>({ 1712 | expandedItems: new Set(), 1713 | toggleExpand: () => {}, 1714 | isExpanded: () => false, 1715 | }); 1716 | ``` 1717 | 1718 | **Modal Context:** 1719 | ```tsx 1720 | const ModalStateContext = React.createContext(null); 1721 | 1722 | export function useModalState() { 1723 | return useContext(ModalStateContext); 1724 | } 1725 | ``` 1726 | 1727 | ### When to Use createContext 1728 | 1729 | **Use `createContext` when:** 1730 | - State needs to be accessed by many nested components 1731 | - Prop drilling becomes cumbersome (>3 levels) 1732 | - Theme, authentication, or configuration data 1733 | - Component coordination (tabs, forms, modals) 1734 | 1735 | **Don't use when:** 1736 | - State is only needed by 1-2 components 1737 | - Simple parent-child relationships 1738 | - Performance-critical frequent updates (context triggers all consumers) 1739 | 1740 | --- 1741 | 1742 | ## `createPortal` - Render Outside Hierarchy 1743 | 1744 | **Purpose:** Render children into a DOM node outside the parent component's hierarchy. 1745 | 1746 | **Syntax:** `createPortal(children, domNode, key?)` 1747 | 1748 | ### Basic Usage 1749 | 1750 | ```tsx 1751 | import { createPortal } from 'react-dom'; 1752 | 1753 | function Modal({ isOpen, children }: Props) { 1754 | if (!isOpen) return null; 1755 | 1756 | // Render into document.body instead of parent component 1757 | return createPortal( 1758 | <div className="modal-overlay"> 1759 | <div className="modal-content"> 1760 | {children} 1761 | </div> 1762 | </div>, 1763 | document.body 1764 | ); 1765 | } 1766 | 1767 | // Usage 1768 | function App() { 1769 | return ( 1770 | <div className="app"> 1771 | <Modal isOpen={true}> 1772 | <h1>This renders in document.body, not .app!</h1> 1773 | </Modal> 1774 | </div> 1775 | ); 1776 | } 1777 | ``` 1778 | 1779 | ### Common Use Cases 1780 | 1781 | **1. Tooltips/Popovers (avoid z-index issues):** 1782 | ```tsx 1783 | function Tooltip({ targetRef, content }: Props) { 1784 | return createPortal( 1785 | <div className="tooltip" style={calculatePosition(targetRef)}> 1786 | {content} 1787 | </div>, 1788 | document.body 1789 | ); 1790 | } 1791 | ``` 1792 | 1793 | **2. Notifications/Toasts:** 1794 | ```tsx 1795 | function NotificationToast() { 1796 | const [shouldRender, setShouldRender] = useState(false); 1797 | 1798 | useEffect(() => { 1799 | setShouldRender(true); 1800 | }, []); 1801 | 1802 | if (!shouldRender) return null; 1803 | 1804 | return createPortal( 1805 | <Toaster position="top-right"> 1806 | {(t) => <ToastBar toast={t} />} 1807 | </Toaster>, 1808 | document.body 1809 | ); 1810 | } 1811 | ``` 1812 | 1813 | **3. Modal Dialogs:** 1814 | ```tsx 1815 | function ModalDialog({ isOpen, children }: Props) { 1816 | if (!isOpen) return null; 1817 | 1818 | return createPortal( 1819 | <div className="modal-backdrop"> 1820 | <div className="modal-dialog"> 1821 | {children} 1822 | </div> 1823 | </div>, 1824 | document.getElementById('modal-root') || document.body 1825 | ); 1826 | } 1827 | ``` 1828 | 1829 | **4. Full-Screen Overlays:** 1830 | ```tsx 1831 | function FullScreenOverlay({ show, children }: Props) { 1832 | if (!show) return null; 1833 | 1834 | return createPortal( 1835 | <div className="fullscreen-overlay"> 1836 | {children} 1837 | </div>, 1838 | document.body 1839 | ); 1840 | } 1841 | ``` 1842 | 1843 | ### Event Bubbling Still Works 1844 | 1845 | ```tsx 1846 | // Event bubbling works despite DOM hierarchy 1847 | function Parent() { 1848 | const handleClick = () => { 1849 | console.log('Clicked!'); // This fires even though button is portaled 1850 | }; 1851 | 1852 | return ( 1853 | <div onClick={handleClick}> 1854 | <PortaledButton /> 1855 | </div> 1856 | ); 1857 | } 1858 | 1859 | function PortaledButton() { 1860 | return createPortal( 1861 | <button>Click me</button>, 1862 | document.body 1863 | ); 1864 | } 1865 | ``` 1866 | 1867 | ### Common Pattern in XMLUI 1868 | 1869 | ```tsx 1870 | // App component portals theme styles 1871 | function App({ children }: Props) { 1872 | return ( 1873 | <> 1874 | {children} 1875 | {createPortal( 1876 | <style>{themeCSS}</style>, 1877 | document.head 1878 | )} 1879 | </> 1880 | ); 1881 | } 1882 | 1883 | // Inspector portals debugging UI 1884 | function Inspector() { 1885 | return createPortal( 1886 | <div className="inspector-panel"> 1887 | {/* Debug tools */} 1888 | </div>, 1889 | document.body 1890 | ); 1891 | } 1892 | ``` 1893 | 1894 | ### When to Use createPortal 1895 | 1896 | **Use `createPortal` when:** 1897 | - Modals, dialogs, and overlays 1898 | - Tooltips and popovers 1899 | - Notifications and toasts 1900 | - Avoiding parent overflow/z-index issues 1901 | - Rendering into different parts of DOM (head, body) 1902 | 1903 | **Don't use when:** 1904 | - Normal component rendering is sufficient 1905 | - No CSS stacking or overflow issues 1906 | - Adds unnecessary complexity 1907 | 1908 | --- 1909 | 1910 | ## `Fragment` - Grouping Without DOM Nodes 1911 | 1912 | **Purpose:** Group multiple elements without adding extra nodes to the DOM. 1913 | 1914 | **Syntax:** `<Fragment>...</Fragment>` or `<>...</>` 1915 | 1916 | ### Basic Usage 1917 | 1918 | ```tsx 1919 | // ❌ WRONG - Adds unnecessary div wrapper 1920 | function List() { 1921 | return ( 1922 | <div> 1923 | <li>Item 1</li> 1924 | <li>Item 2</li> 1925 | </div> 1926 | ); 1927 | } 1928 | 1929 | // ✅ CORRECT - No extra DOM node 1930 | function List() { 1931 | return ( 1932 | <> 1933 | <li>Item 1</li> 1934 | <li>Item 2</li> 1935 | </> 1936 | ); 1937 | } 1938 | ``` 1939 | 1940 | ### Short vs Long Syntax 1941 | 1942 | ```tsx 1943 | // Short syntax <> - Use for most cases 1944 | function Component() { 1945 | return ( 1946 | <> 1947 | <Header /> 1948 | <Content /> 1949 | </> 1950 | ); 1951 | } 1952 | 1953 | // Long syntax <Fragment> - Required when you need a key 1954 | function List({ items }: Props) { 1955 | return ( 1956 | <ul> 1957 | {items.map(item => ( 1958 | <Fragment key={item.id}> 1959 | <li>{item.name}</li> 1960 | <li>{item.description}</li> 1961 | </Fragment> 1962 | ))} 1963 | </ul> 1964 | ); 1965 | } 1966 | ``` 1967 | 1968 | **Limitations of short syntax:** 1969 | - ❌ Cannot add `key` prop (use `<Fragment key={...}>` instead) 1970 | - ❌ Cannot add any other props (only `key` is allowed on Fragment) 1971 | - ✅ Use short syntax everywhere else (cleaner, less verbose) 1972 | 1973 | ### Common Use Cases 1974 | 1975 | **1. Returning Multiple Elements:** 1976 | ```tsx 1977 | function Header() { 1978 | return ( 1979 | <> 1980 | <h1>Title</h1> 1981 | <nav>Navigation</nav> 1982 | </> 1983 | ); 1984 | } 1985 | ``` 1986 | 1987 | **2. Conditional Rendering:** 1988 | ```tsx 1989 | function Component({ showExtra }: Props) { 1990 | return ( 1991 | <div> 1992 | <h1>Always shown</h1> 1993 | {showExtra && ( 1994 | <> 1995 | <p>Extra content</p> 1996 | <button>Extra button</button> 1997 | </> 1998 | )} 1999 | </div> 2000 | ); 2001 | } 2002 | ``` 2003 | 2004 | **3. Table Rows:** 2005 | ```tsx 2006 | function TableRows({ data }: Props) { 2007 | return ( 2008 | <> 2009 | {data.map(row => ( 2010 | <Fragment key={row.id}> 2011 | <tr> 2012 | <td>{row.name}</td> 2013 | <td>{row.value}</td> 2014 | </tr> 2015 | {row.hasDetails && ( 2016 | <tr> 2017 | <td colSpan={2}>{row.details}</td> 2018 | </tr> 2019 | )} 2020 | </Fragment> 2021 | ))} 2022 | </> 2023 | ); 2024 | } 2025 | ``` 2026 | 2027 | **4. Avoiding Invalid HTML:** 2028 | ```tsx 2029 | // ❌ WRONG - div inside p is invalid HTML 2030 | function Text() { 2031 | return ( 2032 | <p> 2033 | <div>This is invalid!</div> 2034 | </p> 2035 | ); 2036 | } 2037 | 2038 | // ✅ CORRECT - Fragment doesn't create DOM node 2039 | function Text() { 2040 | return ( 2041 | <p> 2042 | <> 2043 | <span>This is valid!</span> 2044 | </> 2045 | </p> 2046 | ); 2047 | } 2048 | ``` 2049 | 2050 | ### When to Use Fragment 2051 | 2052 | **Use `Fragment` when:** 2053 | - Component must return multiple elements 2054 | - Avoiding wrapper divs that break CSS (flexbox, grid) 2055 | - Keeping HTML semantically valid 2056 | - Conditional rendering of multiple elements 2057 | 2058 | **Don't use when:** 2059 | - Single element (no need to wrap) 2060 | - Wrapper div doesn't cause issues 2061 | - Need to attach events or refs (Fragment can't have them) 2062 | 2063 | --- 2064 | 2065 | ## `cloneElement` - Clone and Modify React Elements 2066 | 2067 | **Purpose:** Clone a React element and override its props, refs, or children. 2068 | 2069 | **Syntax:** `cloneElement(element, props?, ...children?)` 2070 | 2071 | ### Basic Usage 2072 | 2073 | ```tsx 2074 | import { cloneElement, isValidElement } from 'react'; 2075 | 2076 | function Container({ children }: Props) { 2077 | if (!isValidElement(children)) { 2078 | return children; 2079 | } 2080 | 2081 | // Clone child and add extra props 2082 | return cloneElement(children, { 2083 | className: 'container-child', 2084 | style: { padding: '10px' }, 2085 | }); 2086 | } 2087 | 2088 | // Usage 2089 | <Container> 2090 | <div>Original</div> {/* Becomes <div className="container-child" style={{padding: '10px'}}>Original</div> */} 2091 | </Container> 2092 | ``` 2093 | 2094 | ### Adding Props to Children 2095 | 2096 | ```tsx 2097 | function Animation({ children, duration = 300 }: Props) { 2098 | if (!isValidElement(children)) { 2099 | return children; 2100 | } 2101 | 2102 | // Add animation props to child 2103 | return cloneElement(children, { 2104 | style: { 2105 | ...children.props.style, 2106 | transition: `all ${duration}ms`, 2107 | }, 2108 | }); 2109 | } 2110 | ``` 2111 | 2112 | ### Forwarding Refs Through Clone 2113 | 2114 | ```tsx 2115 | function Wrapper({ children, ...rest }: Props, forwardedRef: Ref<any>) { 2116 | if (!isValidElement(children)) { 2117 | return children; 2118 | } 2119 | 2120 | // Clone and forward ref + other props 2121 | return cloneElement(children, { 2122 | ...rest, 2123 | ref: forwardedRef, 2124 | }); 2125 | } 2126 | 2127 | export const WrapperComponent = forwardRef(Wrapper); 2128 | ``` 2129 | 2130 | ### Common Pattern in XMLUI 2131 | 2132 | **Container with single child ref forwarding:** 2133 | ```tsx 2134 | function Container({ children }: Props, ref: Ref<HTMLElement>) { 2135 | const renderedChild = renderChild(children); 2136 | 2137 | // If single valid child, compose refs and merge props 2138 | if (ref && renderedChild && isValidElement(renderedChild)) { 2139 | return cloneElement(renderedChild, { 2140 | ref: composeRefs(ref, (renderedChild as any).ref), 2141 | ...mergeProps(renderedChild.props, rest), 2142 | }); 2143 | } 2144 | 2145 | return renderedChild; 2146 | } 2147 | ``` 2148 | 2149 | **Form field with label integration:** 2150 | ```tsx 2151 | function ItemWithLabel({ children, label }: Props) { 2152 | const id = useId(); 2153 | 2154 | return ( 2155 | <div> 2156 | <label htmlFor={id}>{label}</label> 2157 | {cloneElement(children as ReactElement, { 2158 | id, 2159 | 'aria-labelledby': id, 2160 | })} 2161 | </div> 2162 | ); 2163 | } 2164 | ``` 2165 | 2166 | ### When to Use cloneElement 2167 | 2168 | **Use `cloneElement` when:** 2169 | - Wrapping components need to add props to children 2170 | - Forwarding refs through wrapper components 2171 | - Adding common behavior to arbitrary children 2172 | - Integrating with child elements you don't control 2173 | 2174 | **Don't use when:** 2175 | - You can pass props directly (prefer explicit props) 2176 | - You need to modify deeply nested children (use context instead) 2177 | - Children are not React elements (check with `isValidElement` first) 2178 | 2179 | ### Common Mistakes 2180 | 2181 | ```tsx 2182 | // ❌ WRONG - Not checking if child is valid element 2183 | function Bad({ children }: Props) { 2184 | return cloneElement(children, { className: 'bad' }); // Crashes if children is string/number 2185 | } 2186 | 2187 | // ✅ CORRECT - Always validate first (see isValidElement section) 2188 | function Good({ children }: Props) { 2189 | if (!isValidElement(children)) { 2190 | return children; 2191 | } 2192 | return cloneElement(children, { className: 'good' }); 2193 | } 2194 | 2195 | // ❌ WRONG - Overriding all existing props 2196 | return cloneElement(child, { style: { color: 'red' } }); // Loses child's existing style 2197 | 2198 | // ✅ CORRECT - Merge with existing props 2199 | return cloneElement(child, { 2200 | style: { ...child.props.style, color: 'red' }, 2201 | }); 2202 | ``` 2203 | 2204 | --- 2205 | 2206 | ## `isValidElement` - Type Check for React Elements 2207 | 2208 | **Purpose:** Check if a value is a valid React element (created with JSX or `createElement`). Always use before `cloneElement`. 2209 | 2210 | **Syntax:** `isValidElement(value)` 2211 | 2212 | ### Basic Usage 2213 | 2214 | ```tsx 2215 | import { isValidElement } from 'react'; 2216 | 2217 | function processChild(child: React.ReactNode) { 2218 | // child could be anything: string, number, element, array, etc. 2219 | 2220 | if (isValidElement(child)) { 2221 | // TypeScript now knows child is ReactElement 2222 | console.log(child.props); // ✅ OK - access props safely 2223 | console.log(child.type); // ✅ OK - access type safely 2224 | return child; 2225 | } 2226 | 2227 | // Not an element - return as is 2228 | return child; 2229 | } 2230 | ``` 2231 | 2232 | ### Common Pattern in XMLUI 2233 | 2234 | **Conditional element wrapping:** 2235 | ```tsx 2236 | function ConditionalWrapper({ condition, children }: Props) { 2237 | if (!condition) { 2238 | return children; 2239 | } 2240 | 2241 | // Only wrap if child is valid element 2242 | return isValidElement(children) 2243 | ? <div className="wrapper">{children}</div> 2244 | : children; 2245 | } 2246 | ``` 2247 | 2248 | ### What isValidElement Checks 2249 | 2250 | ```tsx 2251 | isValidElement(<div />); // ✅ true - JSX element 2252 | isValidElement(React.createElement('div')); // ✅ true - created element 2253 | isValidElement(<Component />); // ✅ true - component element 2254 | isValidElement('hello'); // ❌ false - string 2255 | isValidElement(123); // ❌ false - number 2256 | isValidElement(null); // ❌ false - null 2257 | isValidElement(undefined); // ❌ false - undefined 2258 | isValidElement([<div key="1" />]); // ❌ false - array of elements 2259 | ``` 2260 | 2261 | ### When to Use isValidElement 2262 | 2263 | **Use `isValidElement` when:** 2264 | - Before calling `cloneElement` (required to avoid crashes) 2265 | - Type narrowing for TypeScript (ReactNode → ReactElement) 2266 | - Validating `children` prop type 2267 | - Conditional element manipulation 2268 | 2269 | **Note:** See `cloneElement` section for examples of using these two functions together. 2270 | 2271 | --- 2272 | 2273 | ## `flushSync` - Synchronous State Updates 2274 | 2275 | **Purpose:** Force React to flush state updates synchronously, bypassing automatic batching. 2276 | 2277 | **Syntax:** `flushSync(() => { /* state updates */ })` 2278 | 2279 | **Warning:** Use sparingly - breaks React's batching optimization and can hurt performance. 2280 | 2281 | ### Basic Usage 2282 | 2283 | ```tsx 2284 | import { flushSync } from 'react-dom'; 2285 | 2286 | function Form() { 2287 | const [value, setValue] = useState(''); 2288 | 2289 | const handleSubmit = () => { 2290 | // Normal: state updates are batched 2291 | setValue(''); 2292 | setError(null); 2293 | // Both updates happen together 2294 | 2295 | // With flushSync: update happens immediately 2296 | flushSync(() => { 2297 | setValue(''); 2298 | }); 2299 | // DOM is updated here, before next line 2300 | inputRef.current?.focus(); 2301 | }; 2302 | } 2303 | ``` 2304 | 2305 | ### When DOM Must Update Immediately 2306 | 2307 | ```tsx 2308 | function Table({ data }: Props) { 2309 | const [selectedRow, setSelectedRow] = useState(0); 2310 | const rowRef = useRef<HTMLTableRowElement>(null); 2311 | 2312 | const selectRow = (index: number) => { 2313 | // Must update DOM before scrolling 2314 | flushSync(() => { 2315 | setSelectedRow(index); 2316 | }); 2317 | 2318 | // DOM is updated, can now scroll 2319 | rowRef.current?.scrollIntoView(); 2320 | }; 2321 | } 2322 | ``` 2323 | 2324 | ### Common Pattern in XMLUI 2325 | 2326 | **Form reset with focus:** 2327 | ```tsx 2328 | function Form({ onSubmit }: Props) { 2329 | const doReset = () => { 2330 | // Reset all fields 2331 | }; 2332 | 2333 | const handleSuccess = () => { 2334 | const prevFocused = document.activeElement; 2335 | 2336 | // Force synchronous reset before restoring focus 2337 | flushSync(() => { 2338 | doReset(); 2339 | }); 2340 | 2341 | // DOM is reset, restore focus 2342 | if (prevFocused && typeof (prevFocused as HTMLElement).focus === 'function') { 2343 | (prevFocused as HTMLElement).focus(); 2344 | } 2345 | }; 2346 | } 2347 | ``` 2348 | 2349 | **Table with immediate scroll:** 2350 | ```tsx 2351 | function DataTable({ data }: Props) { 2352 | const handleSort = (column: string) => { 2353 | // Update sort synchronously before scrolling 2354 | flushSync(() => { 2355 | setSortColumn(column); 2356 | setSortedData(sortData(data, column)); 2357 | }); 2358 | 2359 | // Table is re-rendered, can scroll to top 2360 | tableRef.current?.scrollTo(0, 0); 2361 | }; 2362 | } 2363 | ``` 2364 | 2365 | ### Why flushSync Exists 2366 | 2367 | ```tsx 2368 | // ❌ Problem: Without flushSync 2369 | function Component() { 2370 | const [text, setText] = useState(''); 2371 | 2372 | const update = () => { 2373 | setText('new value'); 2374 | // DOM not updated yet! 2375 | inputRef.current?.focus(); // Focuses old state 2376 | }; 2377 | } 2378 | 2379 | // ✅ Solution: With flushSync 2380 | function Component() { 2381 | const [text, setText] = useState(''); 2382 | 2383 | const update = () => { 2384 | flushSync(() => { 2385 | setText('new value'); 2386 | }); 2387 | // DOM is updated 2388 | inputRef.current?.focus(); // Focuses new state 2389 | }; 2390 | } 2391 | ``` 2392 | 2393 | ### When to Use flushSync 2394 | 2395 | **Use `flushSync` when:** 2396 | - Need DOM measurements after state change 2397 | - Synchronizing with third-party libraries 2398 | - Scrolling after state update 2399 | - Focus management after state change 2400 | 2401 | **Don't use when:** 2402 | - Normal state updates (let React batch) 2403 | - Performance-critical code paths 2404 | - You can solve it with `useLayoutEffect` 2405 | - Inside render (not allowed) 2406 | 2407 | **Performance impact:** 2408 | ```tsx 2409 | // ❌ BAD - Multiple flushSync calls 2410 | data.forEach(item => { 2411 | flushSync(() => { 2412 | processItem(item); // Forces re-render each time 2413 | }); 2414 | }); 2415 | 2416 | // ✅ GOOD - Single batch update 2417 | const processedItems = data.map(processItem); 2418 | flushSync(() => { 2419 | setItems(processedItems); // Single re-render 2420 | }); 2421 | ``` 2422 | 2423 | --- 2424 | 2425 | ## `createRoot` - React 18 Root API 2426 | 2427 | **Purpose:** Create a root to render React components into a DOM container (React 18+). 2428 | 2429 | **Syntax:** `const root = createRoot(container); root.render(<App />)` 2430 | 2431 | ### Basic Usage 2432 | 2433 | ```tsx 2434 | import { createRoot } from 'react-dom/client'; 2435 | 2436 | // Old way (React 17) 2437 | ReactDOM.render(<App />, document.getElementById('root')); 2438 | 2439 | // New way (React 18+) 2440 | const root = createRoot(document.getElementById('root')!); 2441 | root.render(<App />); 2442 | ``` 2443 | 2444 | ### With TypeScript 2445 | 2446 | ```tsx 2447 | import { createRoot } from 'react-dom/client'; 2448 | 2449 | const container = document.getElementById('root'); 2450 | if (!container) { 2451 | throw new Error('Root element not found'); 2452 | } 2453 | 2454 | const root = createRoot(container); 2455 | root.render(<App />); 2456 | ``` 2457 | 2458 | ### Unmounting 2459 | 2460 | ```tsx 2461 | const root = createRoot(container); 2462 | root.render(<App />); 2463 | 2464 | // Later: unmount 2465 | root.unmount(); 2466 | ``` 2467 | 2468 | ### Common Pattern in XMLUI 2469 | 2470 | **Standalone app rendering:** 2471 | ```tsx 2472 | function renderStandaloneApp(rootElement: HTMLElement) { 2473 | let contentRoot: Root; 2474 | 2475 | if (!contentRoot) { 2476 | contentRoot = createRoot(rootElement); 2477 | } 2478 | 2479 | contentRoot.render( 2480 | <StrictMode> 2481 | <App /> 2482 | </StrictMode> 2483 | ); 2484 | 2485 | return contentRoot; 2486 | } 2487 | ``` 2488 | 2489 | **Shadow DOM rendering:** 2490 | ```tsx 2491 | function NestedApp({ children }: Props) { 2492 | const shadowRef = useRef<ShadowRoot>(null); 2493 | const contentRootRef = useRef<Root | null>(null); 2494 | 2495 | useEffect(() => { 2496 | if (shadowRef.current && !contentRootRef.current) { 2497 | // Create root in shadow DOM 2498 | contentRootRef.current = createRoot(shadowRef.current); 2499 | contentRootRef.current.render(<NestedContent />); 2500 | } 2501 | 2502 | return () => { 2503 | contentRootRef.current?.unmount(); 2504 | }; 2505 | }, []); 2506 | } 2507 | ``` 2508 | 2509 | ### Benefits of createRoot (React 18) 2510 | 2511 | 1. **Automatic batching** - All updates batched, even in promises/setTimeout 2512 | 2. **Concurrent features** - Enables `useTransition`, `useDeferredValue`, etc. 2513 | 3. **Improved hydration** - Better SSR support 2514 | 4. **Suspense improvements** - Better streaming SSR 2515 | 2516 | ### When to Use createRoot 2517 | 2518 | **Use `createRoot` when:** 2519 | - Starting a new React 18+ application 2520 | - Rendering React into a DOM container 2521 | - Creating multiple roots in one app 2522 | - Rendering into shadow DOM 2523 | 2524 | **Migration from React 17:** 2525 | ```tsx 2526 | // React 17 2527 | import ReactDOM from 'react-dom'; 2528 | ReactDOM.render(<App />, container); 2529 | ReactDOM.unmountComponentAtNode(container); 2530 | 2531 | // React 18 2532 | import { createRoot } from 'react-dom/client'; 2533 | const root = createRoot(container); 2534 | root.render(<App />); 2535 | root.unmount(); 2536 | ``` 2537 | 2538 | --- 2539 | 2540 | ## React Hooks and Patterns Used in XMLUI 2541 | 2542 | Based on the analysis of the XMLUI codebase, here is a comprehensive list of React hooks, methods, and patterns that are actually used. These represent the fundamental React concepts that developers should understand when working with XMLUI components. 2543 | 2544 | ### Advanced Patterns 2545 | 2546 | 23. **Controlled vs Uncontrolled Component Pattern** - Different approaches to form input management 2547 | 24. **Compound Component Pattern** - Components that work together to form a complete UI 2548 | 25. **Context Provider Pattern** - Providing context values to component trees 2549 | 2550 | ### State Management Patterns 2551 | 2552 | 26. **Reducer Pattern with Immer** - Using Immer's `produce` for immutable state updates 2553 | 27. **State Lifting Pattern** - Moving state up to common ancestors 2554 | 28. **Prop Drilling Solution Pattern** - Using context to avoid deep prop passing 2555 | 2556 | ### Performance Optimization Patterns 2557 | 2558 | 29. **Memoization Strategy Pattern** - Strategic use of useMemo and useCallback 2559 | 30. **Virtualization Pattern** - Rendering only visible items in large lists 2560 | 31. **Debouncing Pattern** - Reducing frequency of expensive operations 2561 | 32. **Throttling Pattern** - Limiting execution rate of functions 2562 | 2563 | ### Event Handling Patterns 2564 | 2565 | 33. **Event Delegation Pattern** - Handling events at parent level 2566 | 34. **Synthetic Event Pattern** - React's cross-browser event wrapper 2567 | 35. **Event Callback Composition Pattern** - Combining multiple event handlers 2568 | 2569 | ### Lifecycle and Effect Patterns 2570 | 2571 | 36. **Effect Cleanup Pattern** - Properly cleaning up subscriptions and timers 2572 | 37. **Effect Dependencies Pattern** - Managing dependency arrays correctly 2573 | 38. **Layout Effect Pattern** - Using useLayoutEffect for DOM measurements 2574 | 39. **Insertion Effect Pattern** - Using useInsertionEffect for style injection 2575 | 2576 | ### TypeScript Integration Patterns 2577 | 2578 | 40. **Generic Component Pattern** - Type-safe generic components 2579 | 41. **ForwardRef with TypeScript Pattern** - Properly typing forwardRef components 2580 | 42. **Props Interface Pattern** - Defining component prop types 2581 | 43. **Ref Type Pattern** - Typing refs correctly 2582 | 2583 | ### Accessibility Patterns 2584 | 2585 | 44. **ARIA Attributes Pattern** - Adding accessibility attributes 2586 | 45. **Focus Management Pattern** - Controlling focus programmatically 2587 | 46. **Keyboard Navigation Pattern** - Implementing keyboard shortcuts and navigation 2588 | 2589 | --- 2590 | 2591 | ## Code Review Notes: Potential Derived State Anti-Patterns 2592 | 2593 | The following files contain patterns that may warrant review for potential simplification or refactoring. These are not necessarily bugs but could indicate unnecessary complexity. 2594 | 2595 | ### 1. TableNative.tsx (Lines 339-348) 2596 | 2597 | **Issue:** Mirrors props in local state and syncs with `useLayoutEffect` 2598 | 2599 | ```tsx 2600 | const [_sortBy, _setSortBy] = useState(sortBy); 2601 | const [_sortingDirection, _setSortingDirection] = useState(sortingDirection); 2602 | 2603 | useLayoutEffect(() => { 2604 | _setSortBy(sortBy); 2605 | }, [sortBy]); 2606 | 2607 | useLayoutEffect(() => { 2608 | _setSortingDirection(sortingDirection); 2609 | }, [sortingDirection]); 2610 | ``` 2611 | 2612 | **Why it might be wrong:** If `sortBy` and `sortingDirection` props are sufficient, local state is redundant. 2613 | 2614 | **Possible justification:** May be needed for controlled/uncontrolled pattern or internal sorting before syncing back to parent. 2615 | 2616 | **Review action:** Determine if local state serves a purpose or can be replaced with direct prop usage. 2617 | 2618 | --- 2619 | 2620 | ### 2. TimeInputNative.tsx (Lines 189+) 2621 | 2622 | **Issue:** Derives multiple state variables from parsed `localValue` in `useEffect` 2623 | 2624 | ```tsx 2625 | useEffect(() => { 2626 | if (localValue) { 2627 | const { amPm, hour, minute, second } = parseTimeString(localValue, is12HourFormat); 2628 | setAmPm(amPm); 2629 | setHour(hour); 2630 | setMinute(minute); 2631 | setSecond(second); 2632 | } 2633 | }, [localValue]); 2634 | ``` 2635 | 2636 | **Why it might be wrong:** Parsed values could potentially be computed directly during render with `useMemo`. 2637 | 2638 | **Possible justification:** Component needs to track individual field state for complex user interactions (validation, focus management, etc.). 2639 | 2640 | **Review action:** Evaluate if parsing can be memoized instead of stored in state, or if interactive state tracking justifies current approach. 2641 | 2642 | --- 2643 | 2644 | ### 3. DateInputNative.tsx (Similar pattern to TimeInput) 2645 | 2646 | **Issue:** Likely uses similar derived state pattern for parsing date values into day/month/year components. 2647 | 2648 | **Review action:** Check if pattern matches TimeInput and apply same analysis. 2649 | 2650 | --- 2651 | 2652 | ### General Recommendation 2653 | 2654 | Before refactoring, consider: 2655 | 1. Does the component need to maintain interactive state independent of props? 2656 | 2. Is there a controlled/uncontrolled pattern requirement? 2657 | 3. Would removing the state break existing functionality? 2658 | 4. Does the current pattern create measurable performance issues? 2659 | 2660 | Not all derived state is bad—the key is whether it serves a legitimate purpose beyond simple computation. 2661 | 2662 | ```