This is page 178 of 189. Use http://codebase.md/xmlui-org/xmlui?lines=false&page={x} to view the full context.
# Directory Structure
```
├── .changeset
│ ├── config.json
│ ├── file-input-automatic-csv-json-parsing.md
│ ├── markdown-link-new-tab.md
│ ├── markdown-link-truncation.md
│ └── markdown-table-rowspan-colspan.md
├── .editorconfig
├── .eslintrc.cjs
├── .gitattributes
├── .github
│ ├── build-checklist.png
│ ├── ISSUE_TEMPLATE
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ └── workflows
│ ├── deploy-blog-optimized.yml
│ ├── deploy-blog.yml
│ ├── deploy-docs-optimized.yml
│ ├── deploy-docs-preview.yml
│ ├── deploy-docs.yml
│ ├── deploy-standalone-playground.yml
│ ├── prepare-versions.yml
│ ├── release-packages.yml
│ ├── run-all-tests-fast.yml
│ ├── run-all-tests.yml
│ └── run-smoke-tests.yml
├── .gitignore
├── .npmrc
├── .prettierrc.js
├── .vscode
│ ├── launch.json
│ └── settings.json
├── blog
│ ├── .gitignore
│ ├── .gitkeep
│ ├── CHANGELOG.md
│ ├── extensions.ts
│ ├── index.html
│ ├── index.ts
│ ├── package.json
│ ├── public
│ │ ├── blog
│ │ │ ├── images
│ │ │ │ ├── an-advanced-codefence.gif
│ │ │ │ ├── an-advanced-codefence.mp4
│ │ │ │ ├── blog-page-component.png
│ │ │ │ ├── blog-scrabble.png
│ │ │ │ ├── cli-blog-header.svg
│ │ │ │ ├── codefence-runner.png
│ │ │ │ ├── integrated-blog-search.png
│ │ │ │ ├── lorem-ipsum.png
│ │ │ │ ├── playground-checkbox-source.png
│ │ │ │ ├── playground.png
│ │ │ │ ├── use-xmlui-mcp-to-find-a-howto.png
│ │ │ │ └── xmlui-demo-gallery.png
│ │ │ ├── introducing-the-xmlui-cli.md
│ │ │ ├── introducing-xmlui.md
│ │ │ ├── lorem-ipsum.md
│ │ │ ├── newest-post.md
│ │ │ ├── older-post.md
│ │ │ ├── xmlui-playground.md
│ │ │ └── xmlui-powered-blog.md
│ │ ├── mockServiceWorker.js
│ │ ├── resources
│ │ │ ├── favicon.ico
│ │ │ ├── files
│ │ │ │ └── for-download
│ │ │ │ └── xmlui
│ │ │ │ └── xmlui-standalone.umd.js
│ │ │ ├── github.svg
│ │ │ ├── icons
│ │ │ │ ├── github.svg
│ │ │ │ └── rss.svg
│ │ │ ├── llms.txt
│ │ │ ├── logo-dark.svg
│ │ │ ├── logo.svg
│ │ │ ├── pg-popout.svg
│ │ │ ├── rss.svg
│ │ │ └── xmlui-logo.svg
│ │ ├── serve.json
│ │ └── staticwebapp.config.json
│ ├── scripts
│ │ ├── download-latest-xmlui.js
│ │ ├── generate-rss.js
│ │ ├── get-releases.js
│ │ └── utils.js
│ ├── src
│ │ ├── components
│ │ │ ├── BlogOverview.xmlui
│ │ │ ├── BlogPage.xmlui
│ │ │ ├── LinkButton.xmlui
│ │ │ ├── PageNotFound.xmlui
│ │ │ └── Separator.xmlui
│ │ ├── config.ts
│ │ └── Main.xmlui
│ └── 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
│ │ │ ├── ResponsiveBar.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
│ │ └── 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
│ │ │ ├── buffer-a-reactive-edit.md
│ │ │ ├── chain-a-refetch.md
│ │ │ ├── control-cache-invalidation.md
│ │ │ ├── copy-billing-to-shipping.md
│ │ │ ├── debounce-user-input-for-api-calls.md
│ │ │ ├── debounce-with-changelistener.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
│ │ │ ├── implement-an-authentication-gate.md
│ │ │ ├── make-a-set-of-equal-width-cards.md
│ │ │ ├── make-a-table-responsive.md
│ │ │ ├── make-navpanel-width-responsive.md
│ │ │ ├── modify-a-value-reported-in-a-column.md
│ │ │ ├── paginate-a-list.md
│ │ │ ├── pass-data-to-a-modal-dialog.md
│ │ │ ├── react-to-button-click-not-keystrokes.md
│ │ │ ├── set-the-initial-value-of-a-select-from-fetched-data.md
│ │ │ ├── set-width-for-input-fields-in-a-horizontal-layout.md
│ │ │ ├── share-a-modaldialog-across-components.md
│ │ │ ├── sync-selections-between-table-and-list-views.md
│ │ │ ├── update-ui-optimistically.md
│ │ │ ├── use-accessors-to-simplify-complex-expressions.md
│ │ │ ├── use-built-in-form-validation.md
│ │ │ ├── use-modal-dialog-onclose.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
│ │ ├── playground-and-codefence.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
│ ├── extensions.ts
│ ├── index.html
│ ├── index.ts
│ ├── package.json
│ ├── public
│ │ ├── feed.rss
│ │ ├── mockServiceWorker.js
│ │ ├── 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
│ │ │ │ ├── products.json
│ │ │ │ ├── releases.json
│ │ │ │ ├── sample-broken.csv
│ │ │ │ ├── sample-broken.json
│ │ │ │ ├── sample-config.json
│ │ │ │ ├── sample-inventory.csv
│ │ │ │ ├── sample-products-semicolon.csv
│ │ │ │ ├── sample-products-tsv.tsv
│ │ │ │ ├── sample-products.csv
│ │ │ │ ├── sample-products.json
│ │ │ │ ├── tutorials
│ │ │ │ │ ├── datasource
│ │ │ │ │ │ └── api.ts
│ │ │ │ │ └── p2do
│ │ │ │ │ ├── api.ts
│ │ │ │ │ └── todo-logo.svg
│ │ │ │ └── xmlui.json
│ │ │ ├── icons
│ │ │ │ ├── github.svg
│ │ │ │ └── rss.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
│ │ └── staticwebapp.config.json
│ ├── 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
│ │ │ ├── LinkButton.xmlui
│ │ │ ├── NameValue.xmlui
│ │ │ ├── PageNotFound.xmlui
│ │ │ ├── PaletteItem.xmlui
│ │ │ ├── Palettes.xmlui
│ │ │ ├── SectionHeader.xmlui
│ │ │ ├── Separator.xmlui
│ │ │ ├── TBD.xmlui
│ │ │ ├── Test.xmlui
│ │ │ ├── ThemesIntro.xmlui
│ │ │ ├── ThousandThemes.xmlui
│ │ │ ├── TubeStops.xmlui
│ │ │ ├── TubeStops.xmlui.xs
│ │ │ └── TwoColumnCode.xmlui
│ │ ├── config.ts
│ │ ├── Main.xmlui
│ │ └── themes
│ │ ├── earthtone.ts
│ │ ├── xmlui-gray-on-default.ts
│ │ ├── xmlui-green-on-default.ts
│ │ └── xmlui-orange-on-default.ts
│ └── tsconfig.json
├── feature.md
├── LICENSE
├── package-lock.json
├── package.json
├── packages
│ ├── tsconfig.json
│ ├── 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
│ ├── 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
│ │ └── 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
│ ├── 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
│ ├── 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
│ ├── 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.module.scss
│ │ │ ├── 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.tsx
│ │ │ ├── StandalonePlayground.tsx
│ │ │ ├── StandalonePlaygroundNative.module.scss
│ │ │ ├── StandalonePlaygroundNative.tsx
│ │ │ ├── ThemeSwitcher.module.scss
│ │ │ ├── ThemeSwitcher.tsx
│ │ │ └── utils.ts
│ │ ├── providers
│ │ │ ├── Toast.module.scss
│ │ │ └── ToastProvider.tsx
│ │ ├── state
│ │ │ └── store.ts
│ │ ├── themes
│ │ │ └── theme.ts
│ │ └── utils
│ │ └── helpers.ts
│ ├── 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
│ ├── xmlui-spreadsheet
│ │ ├── .gitignore
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── index.tsx
│ │ ├── Spreadsheet.tsx
│ │ └── SpreadsheetNative.tsx
│ └── 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.spec.ts
│ │ ├── HeroSection.tsx
│ │ └── HeroSectionNative.tsx
│ ├── index.tsx
│ ├── ScrollToTop
│ │ ├── ScrollToTop.module.scss
│ │ ├── ScrollToTop.tsx
│ │ └── ScrollToTopNative.tsx
│ └── vite-env.d.ts
├── playground
│ ├── .gitignore
│ ├── extensions.ts
│ ├── index.html
│ ├── index.ts
│ ├── package.json
│ ├── public
│ │ ├── resources
│ │ │ ├── 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
│ │ │ ├── logo-dark.svg
│ │ │ └── logo.svg
│ │ ├── staticwebapp.config.json
│ │ └── web.config
│ ├── src
│ │ ├── 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
├── playwright.config.ts
├── README.md
├── test-alignment.xmlui
├── test-app-script-issue.xmlui
├── tools
│ ├── 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
│ ├── CHANGELOG.md
│ ├── esbuild.js
│ ├── eslint.config.mjs
│ ├── 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
│ ├── tsconfig.json
│ └── tsconfig.tsbuildinfo
├── turbo.json
└── xmlui
├── .gitignore
├── bin
│ ├── bootstrap.cjs
│ ├── 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
│ └── xmlui-in-a-nutshell.md
├── dev-docs
│ ├── accessibility.md
│ ├── build-system.md
│ ├── build-xmlui.md
│ ├── component-behaviors.md
│ ├── component-categorization.md
│ ├── component-metadata.md
│ ├── components-with-options.md
│ ├── containers.md
│ ├── conv-create-components.md
│ ├── conv-e2e-testing.md
│ ├── conv-unit-testing.md
│ ├── data-operations.md
│ ├── font-size.md
│ ├── form-design.md
│ ├── form-infrastructure-issues.md
│ ├── form-infrastructure.md
│ ├── glossary.md
│ ├── images
│ │ ├── condensed-layout-content-scroll-no-gutters-bottom.svg
│ │ ├── condensed-layout-content-scroll-no-gutters-no-overflow.svg
│ │ ├── condensed-layout-content-scroll-no-gutters-top.svg
│ │ ├── condensed-layout-content-scroll-with-gutters-bottom.svg
│ │ ├── condensed-layout-content-scroll-with-gutters-no-overflow.svg
│ │ ├── condensed-layout-content-scroll-with-gutters-top.svg
│ │ ├── condensed-layout-no-gutters-bottom.svg
│ │ ├── condensed-layout-no-gutters-mid.svg
│ │ ├── condensed-layout-no-gutters-top.svg
│ │ ├── condensed-layout-no-overflow.svg
│ │ ├── condensed-layout-with-gutters-bottom.svg
│ │ ├── condensed-layout-with-gutters-no-overflow.svg
│ │ ├── condensed-layout-with-gutters-top.svg
│ │ ├── condensed-sticky-content-scroll-no-gutters-bottom.svg
│ │ ├── condensed-sticky-content-scroll-no-gutters-no-overflow.svg
│ │ ├── condensed-sticky-content-scroll-no-gutters-top.svg
│ │ ├── condensed-sticky-content-scroll-with-gutters-bottom.svg
│ │ ├── condensed-sticky-content-scroll-with-gutters-no-overflow.svg
│ │ ├── condensed-sticky-content-scroll-with-gutters-top.svg
│ │ ├── condensed-sticky-layout-no-gutters-bottom.svg
│ │ ├── condensed-sticky-layout-no-gutters-top.svg
│ │ ├── condensed-sticky-layout-no-overflow.svg
│ │ ├── condensed-sticky-layout-with-gutters-bottom.svg
│ │ ├── condensed-sticky-layout-with-gutters-no-overflow.svg
│ │ ├── condensed-sticky-layout-with-gutters-top.svg
│ │ ├── desktop-layout-no-overflow.svg
│ │ ├── desktop-layout-overflow-bottom.svg
│ │ ├── desktop-layout-overflow-top.svg
│ │ ├── horizontal-layout-content-scroll-no-gutters-bottom.svg
│ │ ├── horizontal-layout-content-scroll-no-gutters-top.svg
│ │ ├── horizontal-layout-content-scroll-no-gutters.svg
│ │ ├── horizontal-layout-content-scroll-with-gutters-bottom.svg
│ │ ├── horizontal-layout-content-scroll-with-gutters-diagram.svg
│ │ ├── horizontal-layout-content-scroll-with-gutters-top.svg
│ │ ├── horizontal-layout-diagram.svg
│ │ ├── horizontal-layout-no-gutters-overflow-scrollbar-bottom.svg
│ │ ├── horizontal-layout-no-gutters-overflow-scrollbar-top.svg
│ │ ├── horizontal-layout-overflow-scrollbar-bottom.svg
│ │ ├── horizontal-layout-overflow-scrollbar-nogutter-bottom.svg
│ │ ├── horizontal-layout-overflow-scrollbar-nogutter-mid.svg
│ │ ├── horizontal-layout-overflow-scrollbar-top.svg
│ │ ├── horizontal-layout-with-gutters-diagram.svg
│ │ ├── horizontal-sticky-content-scroll-no-gutters-bottom.svg
│ │ ├── horizontal-sticky-content-scroll-no-gutters-top.svg
│ │ ├── horizontal-sticky-content-scroll-no-gutters.svg
│ │ ├── horizontal-sticky-content-scroll-with-gutters-bottom.svg
│ │ ├── horizontal-sticky-content-scroll-with-gutters-top.svg
│ │ ├── horizontal-sticky-content-scroll-with-gutters.svg
│ │ ├── horizontal-sticky-layout-overflow-bottom.svg
│ │ ├── horizontal-sticky-layout-overflow-top.svg
│ │ ├── horizontal-sticky-layout-with-gutters-bottom-scroll.svg
│ │ ├── horizontal-sticky-layout-with-gutters-mid-scroll.svg
│ │ ├── horizontal-sticky-layout-with-gutters-no-overflow.svg
│ │ ├── horizontal-sticky-layout-with-gutters-overflow-bottom.svg
│ │ ├── horizontal-sticky-layout-with-gutters-overflow-mid.svg
│ │ ├── horizontal-sticky-layout-with-gutters-overflow-top.svg
│ │ ├── horizontal-sticky-layout-with-gutters.svg
│ │ ├── horizontal-sticky-layout.svg
│ │ ├── vertical-full-header-content-scroll-no-gutters-overflow-bottom.svg
│ │ ├── vertical-full-header-content-scroll-no-gutters-overflow-top.svg
│ │ ├── vertical-full-header-content-scroll-no-gutters.svg
│ │ ├── vertical-full-header-content-scroll-with-gutters-overflow-bottom.svg
│ │ ├── vertical-full-header-content-scroll-with-gutters-overflow-top.svg
│ │ ├── vertical-full-header-content-scroll-with-gutters.svg
│ │ ├── vertical-full-header-layout-no-gutters-overflow-bottom.svg
│ │ ├── vertical-full-header-layout-no-gutters-overflow-top.svg
│ │ ├── vertical-full-header-layout-no-gutters.svg
│ │ ├── vertical-full-header-layout-with-gutters-overflow-bottom.svg
│ │ ├── vertical-full-header-layout-with-gutters-overflow-top.svg
│ │ ├── vertical-full-header-layout-with-gutters.svg
│ │ ├── vertical-layout-content-scroll-no-gutters-overflow-bottom.svg
│ │ ├── vertical-layout-content-scroll-no-gutters-overflow-top.svg
│ │ ├── vertical-layout-content-scroll-no-gutters.svg
│ │ ├── vertical-layout-content-scroll-with-gutters-overflow-bottom.svg
│ │ ├── vertical-layout-content-scroll-with-gutters-overflow-top.svg
│ │ ├── vertical-layout-content-scroll-with-gutters.svg
│ │ ├── vertical-layout-no-gutters-overflow-bottom.svg
│ │ ├── vertical-layout-no-gutters-overflow-top.svg
│ │ ├── vertical-layout-no-gutters.svg
│ │ ├── vertical-layout-with-gutters-overflow-bottom.svg
│ │ ├── vertical-layout-with-gutters-overflow-top.svg
│ │ ├── vertical-layout-with-gutters.svg
│ │ ├── vertical-sticky-content-scroll-no-gutters-overflow-bottom.svg
│ │ ├── vertical-sticky-content-scroll-no-gutters-overflow-top.svg
│ │ ├── vertical-sticky-content-scroll-no-gutters.svg
│ │ ├── vertical-sticky-content-scroll-with-gutters-overflow-bottom.svg
│ │ ├── vertical-sticky-content-scroll-with-gutters-overflow-top.svg
│ │ ├── vertical-sticky-content-scroll-with-gutters.svg
│ │ ├── vertical-sticky-layout-no-gutters-overflow-bottom.svg
│ │ ├── vertical-sticky-layout-no-gutters-overflow-top.svg
│ │ ├── vertical-sticky-layout-no-gutters.svg
│ │ ├── vertical-sticky-layout-with-gutters-overflow-bottom.svg
│ │ ├── vertical-sticky-layout-with-gutters-overflow-top.svg
│ │ └── vertical-sticky-layout-with-gutters.svg
│ ├── index.md
│ ├── mcp-schizophrenia.md
│ ├── next
│ │ ├── component-dev-guide.md
│ │ ├── project-structure.md
│ │ ├── theme-context.md
│ │ ├── tiptap-design-considerations.md
│ │ └── working-with-code.md
│ ├── react-fundamentals.md
│ ├── refactoring-plan-eliminate-uses.md
│ ├── release-method.md
│ ├── standalone-app.md
│ ├── theme-variables-refactoring.md
│ ├── theming-styling.md
│ ├── ud-components.md
│ └── xmlui-repo.md
├── package.json
├── scripts
│ ├── coverage-only.js
│ ├── e2e-test-summary.js
│ ├── extract-component-metadata.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
│ ├── generate-metadata-markdown.js
│ ├── get-langserver-metadata.js
│ ├── inline-links.mjs
│ └── README-e2e-summary.md
├── src
│ ├── abstractions
│ │ ├── _conventions.md
│ │ ├── ActionDefs.ts
│ │ ├── ApiDefs.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-layout-mobile.spec.ts
│ │ │ ├── App-layout.spec.ts
│ │ │ ├── app-refactor.md
│ │ │ ├── App.md
│ │ │ ├── App.module.scss
│ │ │ ├── App.spec.ts
│ │ │ ├── App.tsx
│ │ │ ├── AppLayoutContext.ts
│ │ │ ├── AppNative.tsx
│ │ │ ├── AppNavigation.ts
│ │ │ ├── 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
│ │ │ ├── SearchIndexCollector.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
│ │ ├── Br
│ │ │ ├── Br.spec.ts
│ │ │ └── Br.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.module.scss
│ │ │ │ ├── LabelList.spec.ts
│ │ │ │ ├── LabelList.tsx
│ │ │ │ └── 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
│ │ │ └── test-padding.xmlui
│ │ ├── DataSource
│ │ │ ├── DataSource.md
│ │ │ ├── DataSource.spec.ts
│ │ │ └── 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
│ │ │ ├── FormBindingWrapper.tsx
│ │ │ ├── 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-styles.md
│ │ │ ├── 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.spec.ts
│ │ │ ├── Pages.tsx
│ │ │ └── PagesNative.tsx
│ │ ├── Pagination
│ │ │ ├── Pagination.md
│ │ │ ├── Pagination.module.scss
│ │ │ ├── Pagination.spec.ts
│ │ │ ├── Pagination.tsx
│ │ │ └── PaginationNative.tsx
│ │ ├── Part
│ │ │ └── Part.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
│ │ │ ├── ResponsiveBarItem.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
│ │ ├── Toast
│ │ │ ├── Toast.spec.ts
│ │ │ ├── Toast.tsx
│ │ │ └── ToastNative.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
│ │ │ ├── Tree.tsx
│ │ │ ├── TreeComponent.module.scss
│ │ │ └── TreeNative.tsx
│ │ ├── TreeDisplay
│ │ │ ├── TreeDisplay.md
│ │ │ ├── TreeDisplay.module.scss
│ │ │ ├── TreeDisplay.tsx
│ │ │ └── TreeDisplayNative.tsx
│ │ ├── ValidationSummary
│ │ │ ├── ValidationSummary.module.scss
│ │ │ └── ValidationSummary.tsx
│ │ └── VisuallyHidden.tsx
│ ├── components-core
│ │ ├── abstractions
│ │ │ ├── ComponentRenderer.ts
│ │ │ ├── LoaderRenderer.ts
│ │ │ ├── standalone.ts
│ │ │ └── treeAbstractions.ts
│ │ ├── action
│ │ │ ├── actions.ts
│ │ │ ├── APICall.tsx
│ │ │ ├── FileDownloadAction.tsx
│ │ │ ├── FileUploadAction.tsx
│ │ │ ├── NavigateAction.tsx
│ │ │ └── TimedAction.tsx
│ │ ├── ApiBoundComponent.tsx
│ │ ├── appContext
│ │ │ ├── date-functions.ts
│ │ │ ├── math-function.ts
│ │ │ └── misc-utils.ts
│ │ ├── AppContext.tsx
│ │ ├── behaviors
│ │ │ ├── Behavior.tsx
│ │ │ ├── CoreBehaviors.tsx
│ │ │ └── FormBindingBehavior.spec.ts
│ │ ├── 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
│ │ │ ├── appState.ts
│ │ │ ├── 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
│ │ │ ├── state-plan.md
│ │ │ ├── StateContainer.tsx
│ │ │ ├── UnknownComponent.module.scss
│ │ │ ├── UnknownComponent.tsx
│ │ │ └── valueExtractor.ts
│ │ ├── reportEngineError.ts
│ │ ├── RestApiProxy.ts
│ │ ├── script-runner
│ │ │ ├── asyncProxy.ts
│ │ │ ├── AttributeValueParser.ts
│ │ │ ├── bannedFunctions.ts
│ │ │ ├── BindingTreeEvaluationContext.ts
│ │ │ ├── eval-tree-async.ts
│ │ │ ├── eval-tree-common.ts
│ │ │ ├── eval-tree-sync.ts
│ │ │ ├── ParameterParser.ts
│ │ │ ├── process-statement-async.ts
│ │ │ ├── process-statement-common.ts
│ │ │ ├── process-statement-sync.ts
│ │ │ ├── ScriptingSourceTree.ts
│ │ │ ├── simplify-expression.ts
│ │ │ ├── statement-queue.ts
│ │ │ └── visitors.ts
│ │ ├── StandaloneApp.tsx
│ │ ├── StandaloneExtensionManager.ts
│ │ ├── TableOfContentsContext.tsx
│ │ ├── theming
│ │ │ ├── _themes.scss
│ │ │ ├── component-layout-resolver.ts
│ │ │ ├── extendThemeUtils.ts
│ │ │ ├── hvar.ts
│ │ │ ├── layout-resolver.ts
│ │ │ ├── parse-layout-props.ts
│ │ │ ├── StyleContext.tsx
│ │ │ ├── StyleRegistry.ts
│ │ │ ├── ThemeContext.tsx
│ │ │ ├── ThemeProvider.tsx
│ │ │ ├── themes
│ │ │ │ ├── base-utils.ts
│ │ │ │ ├── palette.ts
│ │ │ │ ├── root.ts
│ │ │ │ ├── solid.ts
│ │ │ │ ├── theme-colors.ts
│ │ │ │ └── xmlui.ts
│ │ │ ├── themeVars.module.scss
│ │ │ ├── themeVars.ts
│ │ │ ├── transformThemeVars.ts
│ │ │ └── utils.ts
│ │ ├── utils
│ │ │ ├── actionUtils.ts
│ │ │ ├── audio-utils.ts
│ │ │ ├── base64-utils.ts
│ │ │ ├── compound-utils.ts
│ │ │ ├── css-utils.ts
│ │ │ ├── DataLoaderQueryKeyGenerator.ts
│ │ │ ├── date-utils.ts
│ │ │ ├── extractParam.ts
│ │ │ ├── hooks.tsx
│ │ │ ├── LruCache.ts
│ │ │ ├── mergeProps.ts
│ │ │ ├── misc.ts
│ │ │ ├── request-params.ts
│ │ │ ├── statementUtils.ts
│ │ │ └── treeUtils.ts
│ │ └── xmlui-parser.ts
│ ├── index-standalone.ts
│ ├── index.scss
│ ├── index.ts
│ ├── language-server
│ │ ├── server-common.ts
│ │ ├── server-web-worker.ts
│ │ ├── server.ts
│ │ ├── services
│ │ │ ├── common
│ │ │ │ ├── docs-generation.ts
│ │ │ │ ├── lsp-utils.ts
│ │ │ │ ├── metadata-utils.ts
│ │ │ │ └── syntax-node-utilities.ts
│ │ │ ├── completion.ts
│ │ │ ├── diagnostic.ts
│ │ │ ├── format.ts
│ │ │ └── hover.ts
│ │ └── xmlui-metadata-generated.js
│ ├── 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
│ │ ├── 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
│ │ │ ├── index.ts
│ │ │ ├── ModalDialogDriver.ts
│ │ │ ├── NumberBoxDriver.ts
│ │ │ ├── TextBoxDriver.ts
│ │ │ ├── TimeInputDriver.ts
│ │ │ ├── TimerDriver.ts
│ │ │ └── TreeDriver.ts
│ │ ├── fixtures.ts
│ │ ├── index.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
│ └── 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
│ │ ├── rendering
│ │ │ └── ComponentAdapter.test.tsx
│ │ ├── 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-disabled.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
│ │ └── xmluiMarkupToComponent.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
│ ├── appstate.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
│ ├── init-cleanup-events.spec.ts
│ ├── inline-styles-disabled.spec.ts
│ ├── modify-array-item-regression.spec.ts
│ ├── namespaces.spec.ts
│ ├── push-to-array-regression.spec.ts
│ ├── screen-breakpoints.spec.ts
│ ├── script-var-override.spec.ts
│ ├── scripting.spec.ts
│ ├── state-scope-in-pages.spec.ts
│ └── state-var-scopes.spec.ts
├── tsconfig.json
├── tsdown.config.ts
├── vite.config.ts
├── vitest.config.ts
└── vitest.setup.ts
```
# Files
--------------------------------------------------------------------------------
/xmlui/dev-docs/conv-create-components.md:
--------------------------------------------------------------------------------
```markdown
# Conventions: Creating XMLUI Components
This document defines conventions, patterns, and best practices for creating XMLUI components.
## Table of Contents
1. [Component Structure](#component-structure)
2. [Component Metadata](#component-metadata)
3. [Component Parts Pattern](#component-parts-pattern)
4. [Component Renderers](#component-renderers)
5. [Theme and Styling](#theme-and-styling)
6. [Component Implementation](#component-implementation)
7. [Testing](#testing)
8. [Implementation Patterns](#implementation-patterns)
## Component Structure
XMLUI components consist of four elements:
1. **Native React Component**: UI implementation using React patterns
2. **Metadata**: Complete API description (props, events, APIs, theme variables)
3. **Renderer Function**: Maps XMLUI markup to React component calls
4. **Component Registration**: Makes component available in XMLUI markup
### Core Concepts
- **Properties**: Configuration values (e.g., `size`, `variant`, `disabled`)
- **Events**: User interactions (e.g., `click`, `change`, `focus`)
- **Event Handlers**: Functions responding to events
- **Exposed Methods**: Programmatic APIs (e.g., `setValue()`, `focus()`)
- **Context Variables**: Data exposed to children via `$variableName` syntax
### Creation Rules
❌ **Never create:**
- `index.ts` files
- Example files
- Entries in `package.json`
✅ **Only when requested:**
- End-to-end tests
- Documentation
✅ **Focus on:**
- Core functionality and API design
- Proper XMLUI integration
- Component registration
### File Organization
Each component has its own directory under `src/components/`:
```
ComponentName/
├── ComponentName.tsx # Metadata + renderer (required)
├── ComponentNameNative.tsx # React implementation (required for complex components)
└── ComponentName.module.scss # Styles (optional, visual components only)
```
**Naming:**
- Component definition: `Avatar.tsx`
- Native implementation: `AvatarNative.tsx`
- SCSS module: `Avatar.module.scss`
**Dual-File Pattern:**
- **Component Definition** (`ComponentName.tsx`): Metadata, renderer, theme variables
- **Native Component** (`ComponentNameNative.tsx`): React implementation with `forwardRef`, TypeScript interfaces, `defaultProps`
> **Note**: Simple components can combine both into a single file.
### Component Registration
Components must be registered in `ComponentProvider.tsx` to be available in XMLUI markup:
```typescript
// Import the component renderer
import { avatarComponentRenderer } from "./Avatar/Avatar";
// Register in ComponentProvider class
this.registerCoreComponent(avatarComponentRenderer);
```
## Component Metadata
Metadata is the **single source of truth** for a component's API. It describes properties, events, exposed methods, context variables, and theme variables. Used by:
- Documentation generation
- VS Code IntelliSense and validation
- Build-time type checking
- Developer tools and debugging
- Code generation and tooling
### Metadata Structure
Use `createMetadata` helper. Set `nonVisual: true` for non-visual components.
```typescript
import { createMetadata, d, dClick } from "../metadata-helpers";
const COMP = "ComponentName";
export const ComponentNameMd = createMetadata({
status: "stable" | "experimental" | "deprecated",
description: "Brief description of the component and its purpose",
props: {
propName: {
description: "What this prop does",
type: "string" | "number" | "boolean",
availableValues: optionsArray, // For enum-like props
defaultValue: defaultProps.propName,
isRequired: false,
},
},
events: {
onClick: dClick(COMP),
onCustomEvent: d("Description of custom event"),
},
apis: {
setValue: {
description: "API method description",
signature: "setValue(value: string): void",
},
},
contextVars: {
// Variables exposed to child components
},
themeVars: parseScssVar(styles.themeVars),
defaultThemeVars: {
[`property-${COMP}`]: "defaultValue",
},
});
```
### Metadata Helper Functions
- `d(...)` - General property descriptor
- `dClick(name)` - Click event
- `dInit(name)` - Init event (triggered when component is about to be rendered for the first time)
- `dGotFocus(name)` - Focus event
- `dLostFocus(name)` - Blur event
- `dInternal(...)` - Internal-only property
## Component Parts Pattern
The **parts pattern** enables referencing and styling nested sub-components. Used for complex components with multiple visual elements.
### Parts Metadata
```typescript
export const ComponentNameMd = createMetadata({
// ... other metadata
parts: {
label: {
description: "The label displayed for the component.",
},
input: {
description: "The main input area.",
},
startAdornment: {
description: "The adornment displayed at the start of the component.",
},
endAdornment: {
description: "The adornment displayed at the end of the component.",
},
},
defaultPart: "input", // Optional: specifies which part receives layout properties by default
// ... rest of metadata
});
```
### Part Implementation in Native Components
Parts are implemented in native components by applying CSS classes that mark specific DOM elements as parts. This is done using the `partClassName` function from the parts infrastructure:
```typescript
import { partClassName, PART_INPUT, PART_START_ADORNMENT, PART_END_ADORNMENT } from "../../components-core/parts";
export const ComponentNative = forwardRef(function ComponentNative(props, ref) {
return (
<div className={styles.container}>
{/* Start adornment part */}
{startAdornment && (
<div className={classnames(partClassName(PART_START_ADORNMENT), styles.adornment)}>
{startAdornment}
</div>
)}
{/* Main input part */}
<input
className={classnames(partClassName(PART_INPUT), styles.input)}
{...inputProps}
/>
{/* End adornment part */}
{endAdornment && (
<div className={classnames(partClassName(PART_END_ADORNMENT), styles.adornment)}>
{endAdornment}
</div>
)}
</div>
);
});
```
### Standard Part Names
XMLUI defines common part constants for consistency across components:
- `PART_LABEL` - For component labels
- `PART_INPUT` - For main input areas
- `PART_START_ADORNMENT` - For decorative elements at the start
- `PART_END_ADORNMENT` - For decorative elements at the end
### Benefits & Usage
**Benefits:**
- Stable selectors for testing (e.g., `_PART_input_`)
- Targeted styling via theme variables
- Precise layout control
- Auto-generated documentation
**When to use:**
- Multiple visual elements needing separate styling
- Input elements with labels/adornments
- Complex internal structure
**When to skip:**
- Simple single-element components
## Component Renderers
Renderers bridge XMLUI markup and React components. They receive `RendererContext` and return React elements.
### Renderer Context
Key properties:
- **`node`** - Component definition (props, children, metadata)
- **`state`** - Current container state
- **`renderChild`** - Renders children with optional layout context
- **`uid`** - Unique component identifier
- **`updateState`** - Updates state via reducer pattern
- **`extractValue`** - Evaluates properties (handles bindings)
- **`lookupEventHandler`** - Creates event handlers
- **`registerComponentApi`** - Registers component methods
- **`layoutCss`** - Pre-computed layout styles
- **`appContext`**, **`layoutContext`**, **`extractResourceUrl`**, **`lookupAction`**, **`lookupSyncCallback`** - Additional context
### Value Extraction
```typescript
const value = extractValue(node.props.someProperty);
const size = extractValue.asOptionalString(node.props.size, "medium");
const enabled = extractValue.asOptionalBoolean(node.props.enabled, true);
const count = extractValue.asOptionalNumber(node.props.count, 0);
const label = extractValue.asDisplayText(node.props.label);
const width = extractValue.asSize(node.props.width);
```
### Event Handlers
```typescript
onClick={lookupEventHandler("click")}
onFocus={lookupEventHandler("gotFocus")}
onBlur={lookupEventHandler("lostFocus")}
onDidChange={lookupEventHandler("didChange")}
```
### Renderer Examples
**Component with children:**
```typescript
export const buttonComponentRenderer = createComponentRenderer(
"Button",
ButtonMd,
({ node, extractValue, renderChild, lookupEventHandler, layoutCss }) => {
const iconName = extractValue.asString(node.props.icon);
const label = extractValue.asDisplayText(node.props.label);
return (
<Button
variant={extractValue.asOptionalString(node.props.variant)}
disabled={!extractValue.asOptionalBoolean(node.props.enabled, true)}
icon={iconName && <Icon name={iconName} aria-hidden />}
onClick={lookupEventHandler("click")}
onFocus={lookupEventHandler("gotFocus")}
style={layoutCss}
>
{renderChild(node.children, { type: "Stack", orientation: "horizontal" }) || label}
</Button>
);
},
);
```
**Component with state and API:**
```typescript
export const colorPickerComponentRenderer = createComponentRenderer(
"ColorPicker",
ColorPickerMd,
({ node, extractValue, state, updateState, registerComponentApi, lookupEventHandler, layoutCss }) => {
return (
<ColorPicker
value={state.value}
initialValue={extractValue(node.props.initialValue)}
updateState={updateState}
registerComponentApi={registerComponentApi}
onDidChange={lookupEventHandler("didChange")}
style={layoutCss}
enabled={extractValue.asOptionalBoolean(node.props.enabled, true)}
/>
);
},
);
```
## Theme and Styling
Non-visual components don't use styling or theme variables.
Visual components require a SCSS module with this structure:
```scss
// ComponentName.module.scss
@use "../../components-core/theming/themes" as t;
// --- This code snippet is required to collect the theme variables used in this module
$themeVars: ();
@function createThemeVar($componentVariable) {
$themeVars: t.appendThemeVar($themeVars, $componentVariable) !global;
@return t.getThemeVar($themeVars, $componentVariable);
}
// Define theme variables
$backgroundColor-ComponentName: createThemeVar("backgroundColor-ComponentName");
$borderColor-ComponentName: createThemeVar("borderColor-ComponentName");
$textColor-ComponentName: createThemeVar("textColor-ComponentName");
// --- This part defines the CSS styles
.componentName {
background-color: $backgroundColor-ComponentName;
border-color: $borderColor-ComponentName;
color: $textColor-ComponentName;
// Component-specific styles
&.variantClass {
// Variant styles
}
}
// --- We export the theme variables to add them to the component renderer
:export{
themeVars: t.json-stringify($themeVars)
}
```
This structure collects theme variables for documentation, defines customizable variables via `createThemeVar()`, uses them in CSS, and exports them for the renderer.
## Component Implementation
Implementation flow:
1. **Create metadata** - Define component API
2. **Create renderer** - Map XMLUI props to native component (won't build yet)
3. **Create basic native component** - Make code compile
4. **Register component** - Test in XMLUI markup
5. **Complete implementation** - Full functionality, styling, behavior
> **Note**: Create tests/documentation only when explicitly requested.
### Native Component Structure
Native components follow this pattern:
```typescript
import React, { forwardRef, useRef, useEffect } from "react";
import classnames from "classnames";
import styles from "./ComponentName.module.scss";
// Define props interface
type Props = {
id?: string;
// Component-specific props
children?: React.ReactNode;
style?: CSSProperties;
// Event handlers
onClick?: (event: React.MouseEvent) => void;
// Accessibility props
} & React.HTMLAttributes<HTMLElement>;
// Define default props
export const defaultProps: Required<Pick<Props, "prop1" | "prop2">> = {
prop1: "defaultValue",
prop2: "anotherDefault",
};
// Component implementation with forwardRef
export const ComponentName = forwardRef(function ComponentName(
{
prop1 = defaultProps.prop1,
prop2 = defaultProps.prop2,
children,
style,
onClick,
...rest
}: Props,
ref: React.ForwardedRef<HTMLElement>,
) {
const innerRef = useRef<HTMLElement>(null);
// Compose refs if needed
const composedRef = ref ? composeRefs(ref, innerRef) : innerRef;
// Component logic here
return (
<div
ref={composedRef}
className={classnames(styles.componentName, {
[styles.variantClass]: condition,
})}
style={style}
onClick={onClick}
{...rest}
>
{children}
</div>
);
});
// Note: We do NOT use displayName in XMLUI components
// React.displayName is not used in our component convention
```
**Key conventions:** Always use `forwardRef`, define TypeScript interfaces, export `defaultProps`, use scoped CSS modules, support standard HTML attributes via `...rest`, handle accessibility (ARIA), and do NOT set `displayName`.
## Testing
Follow patterns in [testing-conventions.md](./testing-conventions.md).
## Implementation Patterns
### Default Values Pattern
Ensure consistent defaults while allowing customization.
**Steps:**
1. **Define defaults object in Native component**:
```typescript
// In ComponentNative.tsx
export const defaultProps = {
enabled: true,
variant: "primary" as const,
size: "md" as const,
showIcon: false,
// ... other defaults
};
```
2. **Apply defaults in Native component implementation**:
```typescript
interface Props {
enabled?: boolean;
variant?: "primary" | "secondary" | "danger";
size?: "sm" | "md" | "lg";
showIcon?: boolean;
}
export const ComponentNative = ({
enabled = defaultProps.enabled,
variant = defaultProps.variant,
size = defaultProps.size,
showIcon = defaultProps.showIcon,
...rest
}: Props) => {
// Component implementation uses the resolved defaults
return (
<div
className={classnames(styles.component, {
[styles.disabled]: !enabled,
[styles[variant]]: variant,
[styles[size]]: size,
})}
{...rest}
/>
);
};
```
3. **Reference defaults in XMLUI metadata**:
```typescript
export const ComponentMd = createMetadata({
props: {
enabled: dEnabled(defaultProps.enabled),
variant: {
description: "Visual style variant",
availableValues: ["primary", "secondary", "danger"],
defaultValue: defaultProps.variant,
valueType: "string",
},
// ... other props with defaults
},
});
```
4. **Pass values directly in renderer (no fallbacks needed)**:
```typescript
export const componentRenderer = createComponentRenderer(
COMP,
ComponentMd,
({ node, extractValue }) => {
return (
<ComponentNative
enabled={extractValue.asOptionalBoolean(node.props.enabled)}
variant={extractValue(node.props.variant)}
size={extractValue(node.props.size)}
// Native component handles undefined values with its own defaults
/>
);
},
);
```
**Key Benefits**:
- Consistent behavior across all components
- Native components work correctly when used directly by other XMLUI components
- Single source of truth for default values
- Eliminates duplication between renderer and native component
- Supports both XMLUI and direct React usage patterns seamlessly
## ForwardRef Pattern
Expose DOM references and imperative APIs to parent components.
> **⚠️ Important**: Do NOT use `useImperativeHandle` - it's an antipattern. Use `registerComponentApi` instead.
**Patterns:**
1. Basic forwardRef:
```typescript
import React, { forwardRef } from "react";
interface Props {
children?: React.ReactNode;
className?: string;
// ... other props
}
export const ComponentNative = forwardRef<HTMLDivElement, Props>(
function ComponentNative({ children, className, ...rest }, ref) {
return (
<div ref={ref} className={className} {...rest}>
{children}
</div>
);
}
);
```
2. With internal ref composition:
```typescript
import React, { forwardRef, useRef } from "react";
import { composeRefs } from "../../utils/ref-utils";
export const ComponentNative = forwardRef<HTMLDivElement, Props>(
function ComponentNative({ children, ...rest }, ref) {
const internalRef = useRef<HTMLDivElement>(null);
const composedRef = composeRefs(ref, internalRef);
// Use internalRef for component logic
const handleClick = () => {
internalRef.current?.focus();
};
return (
<div ref={composedRef} onClick={handleClick} {...rest}>
{children}
</div>
);
}
);
```
3. With imperative API (recommended):
```typescript
import React, { forwardRef, useRef, useState, useEffect } from "react";
interface Props {
initialValue?: string;
registerComponentApi?: (api: any) => void;
updateState?: (state: any) => void;
onDidChange?: () => void;
}
export const ComponentNative = forwardRef<HTMLInputElement, Props>(
function ComponentNative({ initialValue, registerComponentApi, updateState, onDidChange, ...rest }, ref) {
const elementRef = useRef<HTMLInputElement>(null);
const [value, setValue] = useState(initialValue || "");
// Compose refs to expose both DOM element and internal ref
const composedRef = composeRefs(ref, elementRef);
// Register imperative API using XMLUI's registerComponentApi pattern
useEffect(() => {
if (registerComponentApi) {
registerComponentApi({
focus: () => elementRef.current?.focus(),
blur: () => elementRef.current?.blur(),
scrollIntoView: () => elementRef.current?.scrollIntoView(),
getValue: () => value,
setValue: (newValue: string) => {
setValue(newValue);
updateState?.({ value: newValue });
onDidChange?.();
},
});
}
}, [registerComponentApi, value, updateState, onDidChange]);
return (
<input
ref={composedRef}
value={value}
onChange={(e) => {
setValue(e.target.value);
updateState?.({ value: e.target.value });
onDidChange?.();
}}
{...rest}
/>
);
}
);
```
4. XMLUI renderer registration:
```typescript
export const componentRenderer = createComponentRenderer(
COMP,
ComponentMd,
({ node, extractValue, registerComponentApi, updateState, lookupEventHandler }) => {
return (
<ComponentNative
initialValue={extractValue(node.props.initialValue)}
registerComponentApi={registerComponentApi}
updateState={updateState}
onDidChange={lookupEventHandler("didChange")}
/>
);
},
);
```
## State Management Patterns
Different approaches for managing component state based on complexity and integration requirements.
**React Hooks:**
- `useState` - Local state
- `useRef` - Mutable references (no re-render)
- `useMemo` - Memoize calculations
- `useCallback` - Memoize functions
- `useEffect` - Side effects and cleanup
### Controlled vs Uncontrolled
```typescript
// Uncontrolled component - manages its own state
export const UncontrolledComponent = ({ defaultValue, onChange }: Props) => {
const [value, setValue] = useState(defaultValue || "");
const handleChange = (newValue: string) => {
setValue(newValue);
onChange?.(newValue); // Notify parent but don't depend on it
};
return <input value={value} onChange={(e) => handleChange(e.target.value)} />;
};
// Controlled component - parent controls the state
export const ControlledComponent = ({ value, onChange }: Props) => {
// No internal state - everything comes from props
const handleChange = (newValue: string) => {
onChange?.(newValue); // Parent must handle this
};
return <input value={value} onChange={(e) => handleChange(e.target.value)} />;
};
// Hybrid approach - supports both patterns
export const FlexibleComponent = ({ value, defaultValue, onChange }: Props) => {
const [internalValue, setInternalValue] = useState(defaultValue || "");
const isControlled = value !== undefined;
const effectiveValue = isControlled ? value : internalValue;
const handleChange = (newValue: string) => {
if (!isControlled) {
setInternalValue(newValue);
}
onChange?.(newValue);
};
return <input value={effectiveValue} onChange={(e) => handleChange(e.target.value)} />;
};
```
### External Synchronization
```typescript
export const SynchronizedComponent = ({ externalValue, onValueChange }: Props) => {
const [internalState, setInternalState] = useState({
value: externalValue || "",
isDirty: false,
lastSyncedValue: externalValue || "",
});
// Sync with external changes
useEffect(() => {
if (externalValue !== internalState.lastSyncedValue) {
setInternalState(prev => ({
...prev,
value: externalValue || "",
lastSyncedValue: externalValue || "",
isDirty: false,
}));
}
}, [externalValue, internalState.lastSyncedValue]);
const handleInternalChange = (newValue: string) => {
setInternalState(prev => ({
...prev,
value: newValue,
isDirty: newValue !== prev.lastSyncedValue,
}));
// Debounced external notification
onValueChange?.(newValue);
};
return (
<div>
<input
value={internalState.value}
onChange={(e) => handleInternalChange(e.target.value)}
/>
{internalState.isDirty && <span>*</span>}
</div>
);
};
```
### Context Consumption
```typescript
// Theme context example
const ThemeContext = createContext<{
theme: 'light' | 'dark';
toggleTheme: () => void;
}>({
theme: 'light',
toggleTheme: () => {},
});
export const ThemeProvider = ({ children }: { children: ReactNode }) => {
const [theme, setTheme] = useState<'light' | 'dark'>('light');
const toggleTheme = useCallback(() => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
}, []);
const value = useMemo(() => ({ theme, toggleTheme }), [theme, toggleTheme]);
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
};
// Component consuming theme context
export const ThemedComponent = ({ children }: Props) => {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<div className={`theme-${theme}`}>
{children}
<button onClick={toggleTheme}>Toggle Theme</button>
</div>
);
};
// Form context example for shared form state
const FormContext = createContext<{
formData: Record<string, any>;
updateField: (field: string, value: any) => void;
errors: Record<string, string>;
}>({
formData: {},
updateField: () => {},
errors: {},
});
export const FormFieldComponent = ({ fieldName }: { fieldName: string }) => {
const { formData, updateField, errors } = useContext(FormContext);
return (
<div>
<input
value={formData[fieldName] || ''}
onChange={(e) => updateField(fieldName, e.target.value)}
/>
{errors[fieldName] && <span className="error">{errors[fieldName]}</span>}
</div>
);
};
```
### Effect Hooks
```typescript
export const EffectfulComponent = ({ url, pollingInterval }: Props) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
// Data fetching effect
useEffect(() => {
let isCancelled = false;
const fetchData = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch(url);
const result = await response.json();
if (!isCancelled) {
setData(result);
}
} catch (err) {
if (!isCancelled) {
setError(err.message);
}
} finally {
if (!isCancelled) {
setLoading(false);
}
}
};
fetchData();
// Cleanup function
return () => {
isCancelled = true;
};
}, [url]);
// Polling effect with cleanup
useEffect(() => {
if (!pollingInterval) return;
const interval = setInterval(() => {
// Refetch data
fetch(url)
.then(res => res.json())
.then(setData)
.catch(setError);
}, pollingInterval);
return () => clearInterval(interval);
}, [url, pollingInterval]);
// Event listener effect
useEffect(() => {
const handleVisibilityChange = () => {
if (document.visibilityState === 'visible') {
// Refetch when tab becomes visible
setData(null);
}
};
document.addEventListener('visibilitychange', handleVisibilityChange);
return () => {
document.removeEventListener('visibilitychange', handleVisibilityChange);
};
}, []);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return <div>{JSON.stringify(data)}</div>;
};
```
### XMLUI State Management
```typescript
// XMLUI component renderer using updateState/state pattern
export const xmluiInputComponentRenderer = createComponentRenderer(
COMP,
ComponentMd,
({ node, state, updateState, extractValue, lookupEventHandler, registerComponentApi }) => {
// State is managed by XMLUI's container system
const currentValue = state.value || extractValue(node.props.initialValue);
return (
<InputNative
value={currentValue}
updateState={updateState} // Pass XMLUI's state updater
initialValue={extractValue(node.props.initialValue)}
onDidChange={lookupEventHandler("didChange")}
registerComponentApi={registerComponentApi}
placeholder={extractValue(node.props.placeholder)}
/>
);
},
);
// Native component using XMLUI state management
export const InputNative = ({
value,
updateState,
initialValue,
onDidChange,
registerComponentApi
}: Props) => {
const [localValue, setLocalValue] = useState(value || initialValue || "");
// Sync with XMLUI state changes
useEffect(() => {
if (value !== undefined && value !== localValue) {
setLocalValue(value);
}
}, [value, localValue]);
const handleChange = useCallback((newValue: string) => {
setLocalValue(newValue);
// Update XMLUI container state through reducer pattern
updateState({ value: newValue });
// Trigger XMLUI event handlers
onDidChange?.();
}, [updateState, onDidChange]);
// Register imperative API with XMLUI
useEffect(() => {
registerComponentApi({
setValue: (newValue: string) => {
setLocalValue(newValue);
updateState({ value: newValue });
},
getValue: () => localValue,
focus: () => {
// Focus implementation
},
});
}, [registerComponentApi, localValue, updateState]);
return (
<input
value={localValue}
onChange={(e) => handleChange(e.target.value)}
/>
);
};
// Complex state update patterns
export const ComplexStateComponent = ({ state, updateState }: Props) => {
const handleComplexUpdate = () => {
// Update multiple state properties atomically
updateState({
currentPage: 1,
filters: { category: 'electronics', minPrice: 100 },
sortBy: 'price',
lastUpdated: Date.now(),
});
};
const handleNestedUpdate = () => {
// Update nested state properties
updateState({
'user.preferences.theme': 'dark',
'user.preferences.notifications': true,
});
};
return (
<div>
<button onClick={handleComplexUpdate}>Update Multiple Fields</button>
<button onClick={handleNestedUpdate}>Update Nested Fields</button>
</div>
);
};
```
## Event Handling Patterns
### Event Callback Props
```typescript
interface Props {
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
onFocus?: (event: React.FocusEvent<HTMLButtonElement>) => void;
onKeyDown?: (event: React.KeyboardEvent<HTMLButtonElement>) => void;
onValueChange?: (value: string, event: React.ChangeEvent<HTMLInputElement>) => void;
}
export const EventHandlerComponent = ({
onClick,
onFocus,
onKeyDown,
onValueChange,
children
}: Props) => {
const [value, setValue] = useState("");
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
// Internal logic
console.log("Button clicked");
// Call optional external handler
onClick?.(event);
};
const handleKeyDown = (event: React.KeyboardEvent<HTMLButtonElement>) => {
// Handle specific keys internally
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
handleClick(event as any);
}
// Call optional external handler
onKeyDown?.(event);
};
const handleValueChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const newValue = event.target.value;
setValue(newValue);
// Pass both value and event to external handler
onValueChange?.(newValue, event);
};
return (
<div>
<input
value={value}
onChange={handleValueChange}
onFocus={onFocus}
/>
<button
onClick={handleClick}
onKeyDown={handleKeyDown}
onFocus={onFocus}
>
{children}
</button>
</div>
);
};
```
### Custom Event Objects
```typescript
// Custom event types
interface CustomChangeEvent {
type: 'change';
value: string;
previousValue: string;
isValid: boolean;
timestamp: number;
}
interface CustomSelectionEvent {
type: 'selection';
selectedItems: string[];
action: 'add' | 'remove' | 'clear';
item?: string;
}
export const CustomEventComponent = ({
onSelectionChange,
onValidatedChange
}: {
onSelectionChange?: (event: CustomSelectionEvent) => void;
onValidatedChange?: (event: CustomChangeEvent) => void;
}) => {
const [value, setValue] = useState("");
const [selectedItems, setSelectedItems] = useState<string[]>([]);
const createChangeEvent = (newValue: string, previousValue: string): CustomChangeEvent => ({
type: 'change',
value: newValue,
previousValue,
isValid: newValue.length >= 3,
timestamp: Date.now(),
});
const handleValueChange = (newValue: string) => {
const changeEvent = createChangeEvent(newValue, value);
setValue(newValue);
// Only trigger external handler if validation passes
if (changeEvent.isValid) {
onValidatedChange?.(changeEvent);
}
};
const handleItemSelection = (item: string, action: 'add' | 'remove') => {
let newSelection: string[];
if (action === 'add') {
newSelection = [...selectedItems, item];
} else {
newSelection = selectedItems.filter(i => i !== item);
}
setSelectedItems(newSelection);
const selectionEvent: CustomSelectionEvent = {
type: 'selection',
selectedItems: newSelection,
action,
item,
};
onSelectionChange?.(selectionEvent);
};
return (
<div>
<input
value={value}
onChange={(e) => handleValueChange(e.target.value)}
placeholder="Type at least 3 characters"
/>
<div>
{['apple', 'banana', 'cherry'].map(item => (
<button
key={item}
onClick={() => handleItemSelection(
item,
selectedItems.includes(item) ? 'remove' : 'add'
)}
className={selectedItems.includes(item) ? 'selected' : ''}
>
{item}
</button>
))}
</div>
</div>
);
};
```
### Keyboard Navigation
```typescript
export const AccessibleComponent = ({
items,
onItemSelect,
onEscape
}: {
items: string[];
onItemSelect?: (item: string) => void;
onEscape?: () => void;
}) => {
const [focusedIndex, setFocusedIndex] = useState(0);
const [isOpen, setIsOpen] = useState(false);
const listRef = useRef<HTMLUListElement>(null);
const itemRefs = useRef<(HTMLLIElement | null)[]>([]);
const handleKeyDown = (event: React.KeyboardEvent) => {
switch (event.key) {
case 'ArrowDown':
event.preventDefault();
setFocusedIndex(prev =>
prev < items.length - 1 ? prev + 1 : 0
);
break;
case 'ArrowUp':
event.preventDefault();
setFocusedIndex(prev =>
prev > 0 ? prev - 1 : items.length - 1
);
break;
case 'Enter':
case ' ':
event.preventDefault();
if (isOpen && items[focusedIndex]) {
onItemSelect?.(items[focusedIndex]);
setIsOpen(false);
} else {
setIsOpen(true);
}
break;
case 'Escape':
event.preventDefault();
setIsOpen(false);
onEscape?.();
break;
case 'Home':
event.preventDefault();
setFocusedIndex(0);
break;
case 'End':
event.preventDefault();
setFocusedIndex(items.length - 1);
break;
case 'Tab':
// Allow normal tab behavior
setIsOpen(false);
break;
default:
// Handle alphanumeric navigation
if (event.key.length === 1) {
const char = event.key.toLowerCase();
const nextIndex = items.findIndex((item, index) =>
index > focusedIndex &&
item.toLowerCase().startsWith(char)
);
if (nextIndex !== -1) {
setFocusedIndex(nextIndex);
}
}
break;
}
};
// Focus management
useEffect(() => {
if (isOpen && itemRefs.current[focusedIndex]) {
itemRefs.current[focusedIndex]?.focus();
}
}, [focusedIndex, isOpen]);
return (
<div
role="combobox"
aria-expanded={isOpen}
aria-haspopup="listbox"
onKeyDown={handleKeyDown}
tabIndex={0}
>
<button
onClick={() => setIsOpen(!isOpen)}
aria-label="Toggle dropdown"
>
Select Item
</button>
{isOpen && (
<ul
ref={listRef}
role="listbox"
aria-label="Available options"
>
{items.map((item, index) => (
<li
key={item}
ref={el => itemRefs.current[index] = el}
role="option"
aria-selected={index === focusedIndex}
tabIndex={-1}
onClick={() => {
onItemSelect?.(item);
setIsOpen(false);
}}
onMouseEnter={() => setFocusedIndex(index)}
className={index === focusedIndex ? 'focused' : ''}
>
{item}
</li>
))}
</ul>
)}
</div>
);
};
```
### Mouse/Touch Interactions
```typescript
export const InteractiveComponent = ({
onDragComplete,
onTap,
onLongPress
}: {
onDragComplete?: (startPos: { x: number; y: number }, endPos: { x: number; y: number }) => void;
onTap?: () => void;
onLongPress?: () => void;
}) => {
const [dragState, setDragState] = useState<{
isDragging: boolean;
startPos: { x: number; y: number } | null;
currentPos: { x: number; y: number } | null;
}>({
isDragging: false,
startPos: null,
currentPos: null,
});
const longPressTimerRef = useRef<NodeJS.Timeout>();
const tapStartTimeRef = useRef<number>(0);
const handleMouseDown = (event: React.MouseEvent) => {
const pos = { x: event.clientX, y: event.clientY };
setDragState({
isDragging: false,
startPos: pos,
currentPos: pos,
});
tapStartTimeRef.current = Date.now();
// Start long press timer
longPressTimerRef.current = setTimeout(() => {
onLongPress?.();
}, 500);
};
const handleMouseMove = (event: React.MouseEvent) => {
if (dragState.startPos) {
const currentPos = { x: event.clientX, y: event.clientY };
const distance = Math.sqrt(
Math.pow(currentPos.x - dragState.startPos.x, 2) +
Math.pow(currentPos.y - dragState.startPos.y, 2)
);
// Start dragging if moved more than 5 pixels
if (distance > 5) {
setDragState(prev => ({
...prev,
isDragging: true,
currentPos,
}));
// Cancel long press if dragging
if (longPressTimerRef.current) {
clearTimeout(longPressTimerRef.current);
}
}
}
};
const handleMouseUp = (event: React.MouseEvent) => {
const endPos = { x: event.clientX, y: event.clientY };
// Clear long press timer
if (longPressTimerRef.current) {
clearTimeout(longPressTimerRef.current);
}
if (dragState.isDragging && dragState.startPos) {
onDragComplete?.(dragState.startPos, endPos);
} else if (dragState.startPos) {
// Check if it's a tap (quick and didn't move much)
const tapDuration = Date.now() - tapStartTimeRef.current;
const distance = Math.sqrt(
Math.pow(endPos.x - dragState.startPos.x, 2) +
Math.pow(endPos.y - dragState.startPos.y, 2)
);
if (tapDuration < 500 && distance < 5) {
onTap?.();
}
}
setDragState({
isDragging: false,
startPos: null,
currentPos: null,
});
};
// Touch events for mobile support
const handleTouchStart = (event: React.TouchEvent) => {
const touch = event.touches[0];
handleMouseDown({
clientX: touch.clientX,
clientY: touch.clientY,
} as React.MouseEvent);
};
const handleTouchMove = (event: React.TouchEvent) => {
event.preventDefault(); // Prevent scrolling
const touch = event.touches[0];
handleMouseMove({
clientX: touch.clientX,
clientY: touch.clientY,
} as React.MouseEvent);
};
const handleTouchEnd = (event: React.TouchEvent) => {
const touch = event.changedTouches[0];
handleMouseUp({
clientX: touch.clientX,
clientY: touch.clientY,
} as React.MouseEvent);
};
return (
<div
style={{
width: 200,
height: 200,
backgroundColor: dragState.isDragging ? '#e0e0e0' : '#f0f0f0',
border: '2px solid #ccc',
cursor: dragState.isDragging ? 'grabbing' : 'grab',
userSelect: 'none',
position: 'relative',
}}
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
onMouseLeave={handleMouseUp} // Handle mouse leaving component
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}
>
<div>Interactive Area</div>
{dragState.isDragging && dragState.startPos && dragState.currentPos && (
<div
style={{
position: 'absolute',
left: Math.min(dragState.startPos.x, dragState.currentPos.x) - 200,
top: Math.min(dragState.startPos.y, dragState.currentPos.y) - 200,
width: Math.abs(dragState.currentPos.x - dragState.startPos.x),
height: Math.abs(dragState.currentPos.y - dragState.startPos.y),
border: '2px dashed #007acc',
backgroundColor: 'rgba(0, 122, 204, 0.1)',
pointerEvents: 'none',
}}
/>
)}
</div>
);
};
```
## API Registration Patterns
### Basic API Registration
```typescript
// Native component with imperative API
export const InputNative = forwardRef<HTMLInputElement, Props>(
function InputNative({ registerComponentApi, updateState, onDidChange }, ref) {
const inputRef = useRef<HTMLInputElement>(null);
const [value, setValue] = useState("");
// Register API methods with XMLUI framework
useEffect(() => {
if (registerComponentApi) {
registerComponentApi({
// Basic DOM operations
focus: () => inputRef.current?.focus(),
blur: () => inputRef.current?.blur(),
// Value operations
getValue: () => value,
setValue: (newValue: string) => {
setValue(newValue);
updateState?.({ value: newValue });
onDidChange?.();
},
// Validation operations
isValid: () => value.length >= 3,
validate: () => {
const valid = value.length >= 3;
updateState?.({ isValid: valid });
return valid;
},
});
}
}, [registerComponentApi, value, updateState, onDidChange]);
return (
<input
ref={inputRef}
value={value}
onChange={(e) => setValue(e.target.value)}
/>
);
}
);
// XMLUI renderer with API registration
export const inputComponentRenderer = createComponentRenderer(
"Input",
InputMd,
({ node, registerComponentApi, extractValue, updateState, lookupEventHandler }) => {
return (
<InputNative
ref={(instance) => {
// Forward imperative API to XMLUI
if (instance && registerComponentApi) {
registerComponentApi(instance);
}
}}
registerComponentApi={registerComponentApi}
updateState={updateState}
onDidChange={lookupEventHandler("didChange")}
placeholder={extractValue(node.props.placeholder)}
/>
);
},
);
```
### Async API Operations
```typescript
export const DataTableNative = forwardRef<DataTableAPI, Props>(
function DataTableNative({ registerComponentApi, dataSource, updateState }) {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const refreshData = async (): Promise<void> => {
setLoading(true);
setError(null);
try {
const response = await fetch(dataSource);
const newData = await response.json();
setData(newData);
updateState?.({ data: newData, lastRefresh: Date.now() });
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Unknown error';
setError(errorMessage);
updateState?.({ error: errorMessage });
throw err; // Re-throw for caller handling
} finally {
setLoading(false);
}
};
const exportData = async (format: 'csv' | 'json' | 'xlsx'): Promise<Blob> => {
switch (format) {
case 'csv':
const csvContent = data.map(row =>
Object.values(row).join(',')
).join('\n');
return new Blob([csvContent], { type: 'text/csv' });
case 'json':
return new Blob([JSON.stringify(data, null, 2)], {
type: 'application/json'
});
case 'xlsx':
// Simulate async Excel generation
await new Promise(resolve => setTimeout(resolve, 1000));
return new Blob(['Excel data'], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
});
default:
throw new Error(`Unsupported format: ${format}`);
}
};
const searchData = async (query: string): Promise<any[]> => {
// Simulate async search
await new Promise(resolve => setTimeout(resolve, 300));
const results = data.filter(item =>
Object.values(item).some(value =>
String(value).toLowerCase().includes(query.toLowerCase())
)
);
updateState?.({ searchResults: results, searchQuery: query });
return results;
};
// Register async API methods
useEffect(() => {
if (registerComponentApi) {
registerComponentApi({
// Async data operations
refreshData,
exportData,
searchData,
// Sync getters
getData: () => data,
getRowCount: () => data.length,
isLoading: () => loading,
hasError: () => error !== null,
getError: () => error,
// Selection operations
selectRow: (index: number) => {
updateState?.({ selectedRowIndex: index });
},
selectAll: () => {
updateState?.({ selectedRows: data.map((_, i) => i) });
},
clearSelection: () => {
updateState?.({ selectedRows: [], selectedRowIndex: -1 });
},
});
}
}, [registerComponentApi, data, loading, error, updateState]);
return (
<div className="data-table">
{loading && <div>Loading...</div>}
{error && <div className="error">Error: {error}</div>}
<table>
<tbody>
{data.map((row, index) => (
<tr key={index}>
{Object.values(row).map((value, colIndex) => (
<td key={colIndex}>{String(value)}</td>
))}
</tr>
))}
</tbody>
</table>
</div>
);
}
);
```
### Complex State Management API
```typescript
interface FormState {
values: Record<string, any>;
errors: Record<string, string>;
touched: Record<string, boolean>;
isSubmitting: boolean;
isDirty: boolean;
}
export const FormNative = forwardRef<FormAPI, Props>(
function FormNative({ registerComponentApi, updateState, onValidationChange }) {
const [formState, setFormState] = useState<FormState>({
values: {},
errors: {},
touched: {},
isSubmitting: false,
isDirty: false,
});
const validators = useRef<Record<string, (value: any) => string | null>>({});
const validateField = (fieldName: string, value: any): string | null => {
const validator = validators.current[fieldName];
return validator ? validator(value) : null;
};
const validateForm = (): Record<string, string> => {
const errors: Record<string, string> = {};
Object.keys(formState.values).forEach(fieldName => {
const error = validateField(fieldName, formState.values[fieldName]);
if (error) {
errors[fieldName] = error;
}
});
return errors;
};
const setFieldValue = (fieldName: string, value: any, shouldValidate = true) => {
setFormState(prev => {
const newValues = { ...prev.values, [fieldName]: value };
const newErrors = { ...prev.errors };
if (shouldValidate) {
const error = validateField(fieldName, value);
if (error) {
newErrors[fieldName] = error;
} else {
delete newErrors[fieldName];
}
}
const newState = {
...prev,
values: newValues,
errors: newErrors,
isDirty: true,
};
// Update XMLUI state
updateState?.({
formValues: newValues,
formErrors: newErrors,
formIsDirty: true,
});
return newState;
});
};
const resetForm = (newValues?: Record<string, any>) => {
const resetState: FormState = {
values: newValues || {},
errors: {},
touched: {},
isSubmitting: false,
isDirty: false,
};
setFormState(resetState);
updateState?.({
formValues: resetState.values,
formErrors: resetState.errors,
formIsDirty: false,
});
};
const submitForm = async (): Promise<boolean> => {
setFormState(prev => ({ ...prev, isSubmitting: true }));
try {
const errors = validateForm();
if (Object.keys(errors).length > 0) {
setFormState(prev => ({
...prev,
errors,
isSubmitting: false,
touched: Object.keys(prev.values).reduce((acc, key) => ({
...acc,
[key]: true,
}), {}),
}));
onValidationChange?.(false, errors);
return false;
}
// Form is valid, proceed with submission
onValidationChange?.(true, {});
return true;
} catch (error) {
setFormState(prev => ({ ...prev, isSubmitting: false }));
throw error;
}
};
// Register comprehensive form API
useEffect(() => {
if (registerComponentApi) {
registerComponentApi({
// Field operations
setFieldValue,
getFieldValue: (fieldName: string) => formState.values[fieldName],
setFieldError: (fieldName: string, error: string) => {
setFormState(prev => ({
...prev,
errors: { ...prev.errors, [fieldName]: error },
}));
},
clearFieldError: (fieldName: string) => {
setFormState(prev => {
const newErrors = { ...prev.errors };
delete newErrors[fieldName];
return { ...prev, errors: newErrors };
});
},
// Validation operations
validateField: (fieldName: string) => {
const error = validateField(fieldName, formState.values[fieldName]);
if (error) {
setFormState(prev => ({
...prev,
errors: { ...prev.errors, [fieldName]: error },
}));
}
return !error;
},
validateForm: () => {
const errors = validateForm();
setFormState(prev => ({ ...prev, errors }));
return Object.keys(errors).length === 0;
},
registerValidator: (fieldName: string, validator: (value: any) => string | null) => {
validators.current[fieldName] = validator;
},
// Form operations
submitForm,
resetForm,
setValues: (values: Record<string, any>) => {
setFormState(prev => ({
...prev,
values: { ...prev.values, ...values },
isDirty: true,
}));
},
// State getters
getValues: () => formState.values,
getErrors: () => formState.errors,
isDirty: () => formState.isDirty,
isSubmitting: () => formState.isSubmitting,
isValid: () => Object.keys(formState.errors).length === 0,
});
}
}, [registerComponentApi, formState, updateState, onValidationChange]);
return (
<form onSubmit={(e) => { e.preventDefault(); submitForm(); }}>
{/* Form content will be rendered by child components */}
<div className="form-content">
{/* Child components access form state through context */}
</div>
</form>
);
}
);
```
### Animation and Media Control API
```typescript
export const VideoPlayerNative = forwardRef<VideoPlayerAPI, Props>(
function VideoPlayerNative({ registerComponentApi, updateState, onPlaybackChange }) {
const videoRef = useRef<HTMLVideoElement>(null);
const [playerState, setPlayerState] = useState({
isPlaying: false,
currentTime: 0,
duration: 0,
volume: 1,
playbackRate: 1,
isFullscreen: false,
});
const play = async (): Promise<void> => {
if (videoRef.current) {
await videoRef.current.play();
setPlayerState(prev => ({ ...prev, isPlaying: true }));
updateState?.({ isPlaying: true });
onPlaybackChange?.('play');
}
};
const pause = (): void => {
if (videoRef.current) {
videoRef.current.pause();
setPlayerState(prev => ({ ...prev, isPlaying: false }));
updateState?.({ isPlaying: false });
onPlaybackChange?.('pause');
}
};
const seekTo = (timeInSeconds: number): void => {
if (videoRef.current) {
videoRef.current.currentTime = timeInSeconds;
setPlayerState(prev => ({ ...prev, currentTime: timeInSeconds }));
updateState?.({ currentTime: timeInSeconds });
}
};
const setVolume = (volume: number): void => {
const clampedVolume = Math.max(0, Math.min(1, volume));
if (videoRef.current) {
videoRef.current.volume = clampedVolume;
setPlayerState(prev => ({ ...prev, volume: clampedVolume }));
updateState?.({ volume: clampedVolume });
}
};
const setPlaybackRate = (rate: number): void => {
if (videoRef.current) {
videoRef.current.playbackRate = rate;
setPlayerState(prev => ({ ...prev, playbackRate: rate }));
updateState?.({ playbackRate: rate });
}
};
const toggleFullscreen = async (): Promise<void> => {
if (!document.fullscreenElement) {
await videoRef.current?.requestFullscreen();
setPlayerState(prev => ({ ...prev, isFullscreen: true }));
} else {
await document.exitFullscreen();
setPlayerState(prev => ({ ...prev, isFullscreen: false }));
}
};
const captureFrame = (): string | null => {
if (videoRef.current) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
if (ctx) {
canvas.width = videoRef.current.videoWidth;
canvas.height = videoRef.current.videoHeight;
ctx.drawImage(videoRef.current, 0, 0);
return canvas.toDataURL('image/png');
}
}
return null;
};
// Register media control API
useEffect(() => {
if (registerComponentApi) {
registerComponentApi({
// Playback controls
play,
pause,
stop: () => {
pause();
seekTo(0);
},
toggle: () => playerState.isPlaying ? pause() : play(),
// Navigation controls
seekTo,
seekBy: (deltaSeconds: number) => {
seekTo(playerState.currentTime + deltaSeconds);
},
seekToPercentage: (percentage: number) => {
seekTo((percentage / 100) * playerState.duration);
},
// Audio controls
setVolume,
mute: () => setVolume(0),
unmute: () => setVolume(1),
adjustVolume: (delta: number) => {
setVolume(playerState.volume + delta);
},
// Playback rate controls
setPlaybackRate,
setSpeed: setPlaybackRate, // Alias for common usage
normalSpeed: () => setPlaybackRate(1),
// Display controls
toggleFullscreen,
enterFullscreen: () => {
if (!document.fullscreenElement) {
toggleFullscreen();
}
},
exitFullscreen: () => {
if (document.fullscreenElement) {
toggleFullscreen();
}
},
// Utility functions
captureFrame,
getCurrentTime: () => playerState.currentTime,
getDuration: () => playerState.duration,
getVolume: () => playerState.volume,
getPlaybackRate: () => playerState.playbackRate,
isPlaying: () => playerState.isPlaying,
isFullscreen: () => playerState.isFullscreen,
// Advanced controls
setCurrentTime: seekTo, // Alias for clarity
getCurrentTimePercentage: () =>
playerState.duration > 0 ? (playerState.currentTime / playerState.duration) * 100 : 0,
});
}
}, [registerComponentApi, playerState, updateState, onPlaybackChange]);
return (
<video
ref={videoRef}
onTimeUpdate={(e) => {
const currentTime = e.currentTarget.currentTime;
setPlayerState(prev => ({ ...prev, currentTime }));
updateState?.({ currentTime });
}}
onLoadedMetadata={(e) => {
const duration = e.currentTarget.duration;
setPlayerState(prev => ({ ...prev, duration }));
updateState?.({ duration });
}}
controls
/>
);
}
);
```
## XMLUI Renderer Patterns
### Standard Renderer Structure
```typescript
// Import dependencies
import { createComponentRenderer } from "../renderer-helpers";
import { ComponentNameMd } from "./ComponentNameMetadata";
import { ComponentNameNative } from "./ComponentNameNative";
// Component constant for consistency
const COMP = "ComponentName";
// Standard renderer structure
export const componentNameComponentRenderer = createComponentRenderer(
COMP, // Component name (must match metadata)
ComponentNameMd, // Component metadata object
({ // Destructured renderer context
// Core context properties
node, // Component definition with props/children
extractValue, // Value extraction with binding support
renderChild, // Child rendering function
layoutCss, // Pre-computed layout styles
// State management
state, // Current component state
updateState, // State update function
// Event system
lookupEventHandler, // Event handler factory
lookupAction, // Async action lookup
// API registration
registerComponentApi, // Component API registration
// Context and utilities
appContext, // Application context
uid, // Unique component identifier
layoutContext, // Layout context information
}) => {
// Extract and process props with proper defaults and typing
const variant = extractValue.asOptionalString(node.props.variant, "primary");
const enabled = extractValue.asOptionalBoolean(node.props.enabled, true);
const label = extractValue.asDisplayText(node.props.label);
const icon = extractValue.asString(node.props.icon);
// Handle complex prop processing
const processedProps = {
variant,
enabled,
label,
// Transform XMLUI props to native component props
disabled: !enabled,
showIcon: Boolean(icon),
iconName: icon || undefined,
};
// Event handler creation with error boundaries
const eventHandlers = {
onClick: lookupEventHandler("click"),
onFocus: lookupEventHandler("gotFocus"),
onBlur: lookupEventHandler("lostFocus"),
onValueChange: lookupEventHandler("didChange"),
};
// Conditional rendering logic
if (!enabled && node.props.hideWhenDisabled) {
return null;
}
// Return rendered component with all integrations
return (
<ComponentNameNative
{...processedProps}
{...eventHandlers}
// Framework integration
updateState={updateState}
registerComponentApi={registerComponentApi}
// Layout and styling
style={layoutCss}
className={extractValue.asString(node.props.className)}
// Child content handling
children={renderChild(node.children)}
// Advanced props
ref={(instance) => {
if (instance && registerComponentApi) {
registerComponentApi(instance);
}
}}
/>
);
},
);
// Export with consistent naming
export { componentNameComponentRenderer as componentNameRenderer };
```
### Child Rendering Patterns
```typescript
// Pattern 1: Simple pass-through rendering
export const simpleContainerRenderer = createComponentRenderer(
"SimpleContainer",
SimpleContainerMd,
({ node, renderChild, layoutCss }) => {
return (
<div style={layoutCss} className="simple-container">
{/* Direct child rendering - preserves all children as-is */}
{renderChild(node.children)}
</div>
);
},
);
// Pattern 2: Layout context rendering with specific arrangements
export const stackContainerRenderer = createComponentRenderer(
"Stack",
StackMd,
({ node, extractValue, renderChild, layoutCss }) => {
const orientation = extractValue.asOptionalString(node.props.orientation, "vertical");
const spacing = extractValue.asOptionalString(node.props.spacing, "medium");
const alignment = extractValue.asOptionalString(node.props.alignment, "start");
return (
<div
style={layoutCss}
className={`stack stack-${orientation} spacing-${spacing} align-${alignment}`}
>
{/* Render children with layout context for optimal arrangement */}
{renderChild(node.children, {
type: "Stack",
orientation,
spacing,
alignment,
// Layout hints for child components
itemAlignment: alignment,
crossAxisAlignment: extractValue.asOptionalString(node.props.crossAxisAlignment),
})}
</div>
);
},
);
// Pattern 3: Conditional child rendering based on component state
export const expandableContainerRenderer = createComponentRenderer(
"ExpandableContainer",
ExpandableContainerMd,
({ node, extractValue, renderChild, state, layoutCss }) => {
const isExpanded = state.expanded || extractValue.asOptionalBoolean(node.props.defaultExpanded, false);
const renderMode = extractValue.asOptionalString(node.props.renderMode, "lazy");
return (
<div style={layoutCss} className="expandable-container">
<div className="header">
{extractValue.asDisplayText(node.props.title)}
</div>
{/* Conditional rendering with performance optimization */}
{isExpanded && (
<div className="content">
{renderMode === "lazy"
? renderChild(node.children) // Render only when expanded
: null // Children rendered separately with visibility control
}
</div>
)}
{/* Always render but control visibility for "eager" mode */}
{renderMode === "eager" && (
<div className={`content ${!isExpanded ? 'hidden' : ''}`}>
{renderChild(node.children)}
</div>
)}
</div>
);
},
);
// Pattern 4: Selective child rendering with filtering
export const tabContainerRenderer = createComponentRenderer(
"TabContainer",
TabContainerMd,
({ node, extractValue, renderChild, state, layoutCss }) => {
const activeTabIndex = state.activeTabIndex || extractValue.asOptionalNumber(node.props.defaultActiveTab, 0);
// Filter children to only render tab panels
const tabPanels = node.children?.filter(child => child.component === "TabPanel") || [];
const activePanel = tabPanels[activeTabIndex];
return (
<div style={layoutCss} className="tab-container">
<div className="tab-headers">
{tabPanels.map((panel, index) => (
<button
key={panel.uid || index}
className={`tab-header ${index === activeTabIndex ? 'active' : ''}`}
onClick={() => updateState({ activeTabIndex: index })}
>
{extractValue.asDisplayText(panel.props.title)}
</button>
))}
</div>
<div className="tab-content">
{/* Render only the active tab panel for performance */}
{activePanel && renderChild([activePanel])}
</div>
</div>
);
},
);
// Pattern 5: Hybrid rendering - mix of children and direct props
export const buttonWithContentRenderer = createComponentRenderer(
"ButtonWithContent",
ButtonWithContentMd,
({ node, extractValue, renderChild, lookupEventHandler, layoutCss }) => {
const label = extractValue.asDisplayText(node.props.label);
const hasChildren = node.children && node.children.length > 0;
return (
<button
style={layoutCss}
className="button-with-content"
onClick={lookupEventHandler("click")}
>
{/* Prioritize children over label prop */}
{hasChildren
? renderChild(node.children, {
type: "ButtonContent",
inline: true,
// Pass button context to children
buttonVariant: extractValue.asOptionalString(node.props.variant),
buttonSize: extractValue.asOptionalString(node.props.size),
})
: label || "Button"
}
</button>
);
},
);
// Pattern 6: Performance-optimized rendering with memoization
export const dataListRenderer = createComponentRenderer(
"DataList",
DataListMd,
({ node, extractValue, renderChild, state, layoutCss }) => {
const data = state.data || extractValue(node.props.data) || [];
const itemTemplate = node.children?.find(child => child.component === "ItemTemplate");
const emptyTemplate = node.children?.find(child => child.component === "EmptyTemplate");
// Memoize expensive rendering operations
const renderedItems = useMemo(() => {
if (!itemTemplate || data.length === 0) return null;
return data.map((item, index) => {
// Clone template with item data context
const itemNode = {
...itemTemplate,
uid: `${itemTemplate.uid}-${index}`,
// Inject item data into template context
props: {
...itemTemplate.props,
$item: item,
$index: index,
},
};
return renderChild([itemNode]);
});
}, [data, itemTemplate, renderChild]);
return (
<div style={layoutCss} className="data-list">
{data.length > 0
? renderedItems
: emptyTemplate && renderChild([emptyTemplate])
}
</div>
);
},
);
```
### Conditional Properties
```typescript
// Pattern 1: State-based conditional props
export const dynamicInputRenderer = createComponentRenderer(
"DynamicInput",
DynamicInputMd,
({ node, extractValue, state, updateState, lookupEventHandler, layoutCss }) => {
const inputType = extractValue.asOptionalString(node.props.type, "text");
const isPassword = inputType === "password";
const isReadOnly = state.readOnly || extractValue.asOptionalBoolean(node.props.readOnly, false);
const hasError = Boolean(state.error);
// Build dynamic props object
const dynamicProps = {
// Base props always present
type: inputType,
value: state.value || extractValue(node.props.value) || "",
placeholder: extractValue.asString(node.props.placeholder),
// Conditional props based on state and type
...(isPassword && {
autoComplete: "current-password",
spellCheck: false,
}),
...(isReadOnly && {
readOnly: true,
tabIndex: -1,
}),
...(hasError && {
"aria-invalid": true,
"aria-describedby": `${node.uid}-error`,
}),
// Performance optimization - only add expensive props when needed
...(extractValue.asOptionalBoolean(node.props.enableSpellCheck) && {
spellCheck: true,
}),
// Conditional event handlers
onChange: !isReadOnly ? lookupEventHandler("didChange") : undefined,
onFocus: !isReadOnly ? lookupEventHandler("gotFocus") : undefined,
onBlur: !isReadOnly ? lookupEventHandler("lostFocus") : undefined,
};
return (
<div style={layoutCss} className="dynamic-input-container">
<input
{...dynamicProps}
className={`input ${hasError ? 'error' : ''} ${isReadOnly ? 'readonly' : ''}`}
/>
{/* Conditionally render error message */}
{hasError && (
<div id={`${node.uid}-error`} className="error-message">
{state.error}
</div>
)}
{/* Conditionally render password toggle */}
{isPassword && !isReadOnly && (
<button
type="button"
className="password-toggle"
onClick={() => updateState({ showPassword: !state.showPassword })}
>
{state.showPassword ? "Hide" : "Show"}
</button>
)}
</div>
);
},
);
// Pattern 2: Context-aware conditional rendering
export const responsiveCardRenderer = createComponentRenderer(
"ResponsiveCard",
ResponsiveCardMd,
({ node, extractValue, renderChild, layoutContext, appContext, layoutCss }) => {
const variant = extractValue.asOptionalString(node.props.variant, "default");
const size = extractValue.asOptionalString(node.props.size, "medium");
// Determine rendering strategy based on context
const isMobile = layoutContext?.breakpoint === "mobile" || appContext?.screenWidth < 768;
const isInGrid = layoutContext?.type === "Grid";
const isInStack = layoutContext?.type === "Stack";
// Build conditional style and behavior props
const conditionalProps = {
// Base styling
className: `card card-${variant} card-${size}`,
// Context-aware modifications
...(isMobile && {
className: `card card-${variant} card-${size} card-mobile`,
// Simplified props for mobile
showSecondaryActions: false,
compactMode: true,
}),
...(isInGrid && {
// Grid-specific optimizations
className: `card card-${variant} card-${size} card-in-grid`,
aspectRatio: extractValue.asOptionalString(node.props.aspectRatio, "auto"),
}),
...(isInStack && layoutContext.orientation === "horizontal" && {
// Horizontal stack optimizations
className: `card card-${variant} card-${size} card-horizontal`,
layout: "horizontal",
}),
};
// Conditional feature rendering based on capabilities
const showAdvancedFeatures = !isMobile && appContext?.features?.advancedCardFeatures !== false;
const showAnimations = appContext?.settings?.enableAnimations !== false;
return (
<div
style={layoutCss}
{...conditionalProps}
{...(showAnimations && {
className: `${conditionalProps.className} animated`,
})}
>
{/* Always render main content */}
<div className="card-content">
{renderChild(node.children?.filter(child =>
child.component !== "CardActions" && child.component !== "CardMenu"
))}
</div>
{/* Conditionally render advanced features */}
{showAdvancedFeatures && (
<>
{node.children?.find(child => child.component === "CardActions") && (
<div className="card-actions">
{renderChild(node.children.filter(child => child.component === "CardActions"))}
</div>
)}
{node.children?.find(child => child.component === "CardMenu") && (
<div className="card-menu">
{renderChild(node.children.filter(child => child.component === "CardMenu"))}
</div>
)}
</>
)}
</div>
);
},
);
// Pattern 3: Performance-optimized conditional props with memoization
export const optimizedDataTableRenderer = createComponentRenderer(
"OptimizedDataTable",
OptimizedDataTableMd,
({ node, extractValue, state, renderChild, layoutCss }) => {
const data = state.data || [];
const columns = extractValue(node.props.columns) || [];
const enableVirtualization = extractValue.asOptionalBoolean(node.props.enableVirtualization, data.length > 100);
const enableSorting = extractValue.asOptionalBoolean(node.props.enableSorting, true);
const enableFiltering = extractValue.asOptionalBoolean(node.props.enableFiltering, false);
// Memoize expensive conditional props
const tableProps = useMemo(() => ({
// Base props
data,
columns,
// Conditional feature props - only compute when needed
...(enableVirtualization && {
virtualizer: {
itemHeight: extractValue.asOptionalNumber(node.props.rowHeight, 40),
overscan: extractValue.asOptionalNumber(node.props.overscan, 5),
},
}),
...(enableSorting && {
sortConfig: {
defaultSort: extractValue(node.props.defaultSort),
multiSort: extractValue.asOptionalBoolean(node.props.multiSort, false),
},
}),
...(enableFiltering && {
filterConfig: {
globalFilter: state.globalFilter,
columnFilters: state.columnFilters || {},
},
}),
// Performance props based on data size
...(data.length > 1000 && {
deferredRendering: true,
batchSize: 50,
}),
}), [data, columns, enableVirtualization, enableSorting, enableFiltering, state]);
// Conditional className based on features and state
const tableClassName = [
"data-table",
enableVirtualization && "virtualized",
enableSorting && "sortable",
enableFiltering && "filterable",
data.length > 1000 && "large-dataset",
state.isLoading && "loading",
].filter(Boolean).join(" ");
return (
<div style={layoutCss} className="data-table-container">
{/* Conditionally render filter UI */}
{enableFiltering && (
<div className="table-filters">
{renderChild(node.children?.filter(child => child.component === "TableFilter"))}
</div>
)}
<div className={tableClassName}>
{/* Render table with conditional props */}
<DataTableNative
{...tableProps}
updateState={updateState}
registerComponentApi={registerComponentApi}
/>
</div>
{/* Conditionally render pagination for large datasets */}
{data.length > 25 && (
<div className="table-pagination">
{renderChild(node.children?.filter(child => child.component === "TablePagination"))}
</div>
)}
</div>
);
},
);
```
## Performance Patterns
### Memoization
```typescript
export const OptimizedDataProcessor = ({
data,
filters,
sortConfig,
onDataChange,
onSelectionChange
}: Props) => {
const [selectedItems, setSelectedItems] = useState<Set<string>>(new Set());
const [searchQuery, setSearchQuery] = useState("");
// Memoize expensive data transformations
const filteredData = useMemo(() => {
console.log("Computing filtered data..."); // Only logs when dependencies change
return data.filter(item => {
// Apply multiple filters
const matchesSearch = searchQuery === "" ||
item.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
item.description.toLowerCase().includes(searchQuery.toLowerCase());
const matchesCategory = !filters.category ||
item.category === filters.category;
const matchesDateRange = !filters.dateRange ||
(item.date >= filters.dateRange.start && item.date <= filters.dateRange.end);
return matchesSearch && matchesCategory && matchesDateRange;
});
}, [data, searchQuery, filters.category, filters.dateRange]);
// Memoize expensive sorting operations
const sortedData = useMemo(() => {
console.log("Computing sorted data..."); // Only logs when dependencies change
if (!sortConfig.field) return filteredData;
return [...filteredData].sort((a, b) => {
const aVal = a[sortConfig.field];
const bVal = b[sortConfig.field];
// Handle different data types
if (typeof aVal === 'number' && typeof bVal === 'number') {
return sortConfig.direction === 'asc' ? aVal - bVal : bVal - aVal;
}
if (aVal instanceof Date && bVal instanceof Date) {
return sortConfig.direction === 'asc'
? aVal.getTime() - bVal.getTime()
: bVal.getTime() - aVal.getTime();
}
// String comparison
const result = String(aVal).localeCompare(String(bVal));
return sortConfig.direction === 'asc' ? result : -result;
});
}, [filteredData, sortConfig.field, sortConfig.direction]);
// Memoize computed statistics
const statistics = useMemo(() => {
return {
total: data.length,
filtered: filteredData.length,
selected: selectedItems.size,
categories: [...new Set(data.map(item => item.category))].length,
averageValue: data.reduce((sum, item) => sum + (item.value || 0), 0) / data.length,
};
}, [data, filteredData.length, selectedItems.size]);
// Memoize stable callback functions to prevent child re-renders
const handleItemClick = useCallback((itemId: string) => {
setSelectedItems(prev => {
const newSelection = new Set(prev);
if (newSelection.has(itemId)) {
newSelection.delete(itemId);
} else {
newSelection.add(itemId);
}
// Notify parent with stable reference
onSelectionChange?.(Array.from(newSelection));
return newSelection;
});
}, [onSelectionChange]);
const handleSelectAll = useCallback(() => {
const allIds = sortedData.map(item => item.id);
setSelectedItems(new Set(allIds));
onSelectionChange?.(allIds);
}, [sortedData, onSelectionChange]);
const handleClearSelection = useCallback(() => {
setSelectedItems(new Set());
onSelectionChange?.([]);
}, [onSelectionChange]);
// Memoize search handler with debouncing
const debouncedSearch = useCallback(
debounce((query: string) => {
setSearchQuery(query);
}, 300),
[]
);
const handleSearchChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
debouncedSearch(event.target.value);
}, [debouncedSearch]);
// Memoize expensive render functions
const renderStatistics = useCallback(() => (
<div className="statistics">
<span>Total: {statistics.total}</span>
<span>Filtered: {statistics.filtered}</span>
<span>Selected: {statistics.selected}</span>
<span>Categories: {statistics.categories}</span>
<span>Average: {statistics.averageValue.toFixed(2)}</span>
</div>
), [statistics]);
return (
<div className="optimized-data-processor">
{/* Search input - uses memoized handler */}
<input
type="text"
placeholder="Search..."
onChange={handleSearchChange}
/>
{/* Statistics - memoized component */}
{renderStatistics()}
{/* Action buttons - use memoized handlers */}
<div className="actions">
<button onClick={handleSelectAll}>Select All</button>
<button onClick={handleClearSelection}>Clear Selection</button>
</div>
{/* Data list - only re-renders when sortedData changes */}
<div className="data-list">
{sortedData.map(item => (
<MemoizedDataItem
key={item.id}
item={item}
isSelected={selectedItems.has(item.id)}
onClick={handleItemClick}
/>
))}
</div>
</div>
);
};
// Memoized child component to prevent unnecessary re-renders
const MemoizedDataItem = React.memo(({
item,
isSelected,
onClick
}: {
item: DataItem;
isSelected: boolean;
onClick: (id: string) => void;
}) => {
const handleClick = useCallback(() => {
onClick(item.id);
}, [item.id, onClick]);
return (
<div
className={`data-item ${isSelected ? 'selected' : ''}`}
onClick={handleClick}
>
<h3>{item.name}</h3>
<p>{item.description}</p>
<span>{item.category}</span>
</div>
);
});
```
### Lazy Loading
```typescript
// Dynamic component import with lazy loading
const LazyChart = React.lazy(() =>
import('./components/Chart').then(module => ({
default: module.Chart
}))
);
const LazyDataTable = React.lazy(() =>
import('./components/DataTable').then(module => ({
default: module.DataTable
}))
);
const LazyImageEditor = React.lazy(() =>
import('./components/ImageEditor').then(module => ({
default: module.ImageEditor
}))
);
export const LazyLoadingContainer = ({
activeTab,
data,
onTabChange
}: Props) => {
const [loadedTabs, setLoadedTabs] = useState<Set<string>>(new Set(['overview']));
const [imageCache, setImageCache] = useState<Map<string, string>>(new Map());
// Track which tabs have been viewed for preloading
const handleTabChange = useCallback((tabId: string) => {
setLoadedTabs(prev => new Set([...prev, tabId]));
onTabChange(tabId);
}, [onTabChange]);
// Preload next likely tab based on user behavior
useEffect(() => {
const preloadMap = {
'overview': 'analytics',
'analytics': 'reports',
'reports': 'settings',
};
const nextTab = preloadMap[activeTab as keyof typeof preloadMap];
if (nextTab && !loadedTabs.has(nextTab)) {
// Preload after a delay
const timer = setTimeout(() => {
setLoadedTabs(prev => new Set([...prev, nextTab]));
}, 2000);
return () => clearTimeout(timer);
}
}, [activeTab, loadedTabs]);
// Lazy image loading with intersection observer
const LazyImage = useCallback(({ src, alt, ...props }: ImageProps) => {
const [isLoaded, setIsLoaded] = useState(false);
const [isInView, setIsInView] = useState(false);
const imgRef = useRef<HTMLImageElement>(null);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsInView(true);
observer.disconnect();
}
},
{ threshold: 0.1 }
);
if (imgRef.current) {
observer.observe(imgRef.current);
}
return () => observer.disconnect();
}, []);
useEffect(() => {
if (isInView && !isLoaded) {
// Check cache first
if (imageCache.has(src)) {
setIsLoaded(true);
return;
}
// Preload image
const img = new Image();
img.onload = () => {
setImageCache(prev => new Map([...prev, [src, src]]));
setIsLoaded(true);
};
img.src = src;
}
}, [isInView, isLoaded, src, imageCache]);
return (
<div ref={imgRef} className="lazy-image-container" {...props}>
{isLoaded ? (
<img src={src} alt={alt} className="lazy-image loaded" />
) : (
<div className="lazy-image-placeholder">
<div className="loading-spinner" />
</div>
)}
</div>
);
}, [imageCache]);
// Lazy content loading based on visibility
const LazyContent = useCallback(({
children,
fallback = <div>Loading...</div>,
threshold = 0.1
}: LazyContentProps) => {
const [shouldLoad, setShouldLoad] = useState(false);
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setShouldLoad(true);
observer.disconnect();
}
},
{ threshold }
);
if (containerRef.current) {
observer.observe(containerRef.current);
}
return () => observer.disconnect();
}, [threshold]);
return (
<div ref={containerRef}>
{shouldLoad ? children : fallback}
</div>
);
}, []);
return (
<div className="lazy-loading-container">
<nav className="tabs">
{['overview', 'analytics', 'reports', 'settings'].map(tab => (
<button
key={tab}
className={`tab ${activeTab === tab ? 'active' : ''}`}
onClick={() => handleTabChange(tab)}
>
{tab.charAt(0).toUpperCase() + tab.slice(1)}
{!loadedTabs.has(tab) && <span className="loading-indicator" />}
</button>
))}
</nav>
<div className="tab-content">
{/* Always render overview immediately */}
{activeTab === 'overview' && (
<div className="overview-tab">
<h2>Overview</h2>
<p>Key metrics and summary information</p>
{/* Lazy load images in overview */}
<div className="image-gallery">
{data.images?.map((img, index) => (
<LazyImage
key={index}
src={img.src}
alt={img.alt}
className="gallery-image"
/>
))}
</div>
</div>
)}
{/* Lazy load analytics tab */}
{activeTab === 'analytics' && (
<Suspense fallback={<div className="loading">Loading Analytics...</div>}>
<LazyContent>
{loadedTabs.has('analytics') && (
<LazyChart
data={data.analytics}
type="line"
animated={true}
/>
)}
</LazyContent>
</Suspense>
)}
{/* Lazy load reports tab */}
{activeTab === 'reports' && (
<Suspense fallback={<div className="loading">Loading Reports...</div>}>
<LazyContent>
{loadedTabs.has('reports') && (
<LazyDataTable
data={data.reports}
pagination={true}
exportable={true}
/>
)}
</LazyContent>
</Suspense>
)}
{/* Lazy load settings tab */}
{activeTab === 'settings' && (
<Suspense fallback={<div className="loading">Loading Settings...</div>}>
<LazyContent>
{loadedTabs.has('settings') && (
<LazyImageEditor
features={['crop', 'filter', 'adjust']}
onSave={handleImageSave}
/>
)}
</LazyContent>
</Suspense>
)}
</div>
</div>
);
};
```
### Debouncing
```typescript
export const OptimizedSearchComponent = ({
onSearch,
onFilter,
onSort,
searchDelay = 300,
filterDelay = 500
}: Props) => {
const [searchQuery, setSearchQuery] = useState("");
const [filters, setFilters] = useState<FilterState>({});
const [sortConfig, setSortConfig] = useState<SortConfig>({});
const [isSearching, setIsSearching] = useState(false);
// Debounced search function
const debouncedSearch = useMemo(
() => debounce(async (query: string) => {
setIsSearching(true);
try {
await onSearch(query);
} finally {
setIsSearching(false);
}
}, searchDelay),
[onSearch, searchDelay]
);
// Debounced filter function with batching
const debouncedFilter = useMemo(
() => debounce((filterState: FilterState) => {
onFilter(filterState);
}, filterDelay),
[onFilter, filterDelay]
);
// Throttled sort function to prevent rapid sorting
const throttledSort = useMemo(
() => throttle((config: SortConfig) => {
onSort(config);
}, 200),
[onSort]
);
// Search input handler
const handleSearchChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
const query = event.target.value;
setSearchQuery(query);
// Cancel previous search and start new one
debouncedSearch.cancel();
debouncedSearch(query);
}, [debouncedSearch]);
// Filter change handler with batching
const handleFilterChange = useCallback((filterKey: string, value: any) => {
setFilters(prev => {
const newFilters = { ...prev, [filterKey]: value };
// Cancel previous filter operation
debouncedFilter.cancel();
debouncedFilter(newFilters);
return newFilters;
});
}, [debouncedFilter]);
// Sort change handler
const handleSortChange = useCallback((field: string, direction: 'asc' | 'desc') => {
const newConfig = { field, direction };
setSortConfig(newConfig);
throttledSort(newConfig);
}, [throttledSort]);
// Advanced debouncing with request cancellation
const advancedDebouncedSearch = useMemo(() => {
let currentController: AbortController | null = null;
return debounce(async (query: string) => {
// Cancel previous request
if (currentController) {
currentController.abort();
}
// Create new abort controller
currentController = new AbortController();
setIsSearching(true);
try {
const results = await fetch(`/api/search?q=${encodeURIComponent(query)}`, {
signal: currentController.signal
});
if (!results.ok) throw new Error('Search failed');
const data = await results.json();
onSearch(data);
} catch (error) {
if (error.name !== 'AbortError') {
console.error('Search error:', error);
}
} finally {
if (currentController && !currentController.signal.aborted) {
setIsSearching(false);
}
currentController = null;
}
}, searchDelay);
}, [onSearch, searchDelay]);
// Cleanup on unmount
useEffect(() => {
return () => {
debouncedSearch.cancel();
debouncedFilter.cancel();
throttledSort.cancel();
advancedDebouncedSearch.cancel();
};
}, [debouncedSearch, debouncedFilter, throttledSort, advancedDebouncedSearch]);
// Auto-save with debouncing
const [formData, setFormData] = useState({});
const [lastSaved, setLastSaved] = useState<Date | null>(null);
const debouncedAutoSave = useMemo(
() => debounce(async (data: any) => {
try {
await fetch('/api/autosave', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
setLastSaved(new Date());
} catch (error) {
console.error('Auto-save failed:', error);
}
}, 2000),
[]
);
const handleFormDataChange = useCallback((field: string, value: any) => {
setFormData(prev => {
const newData = { ...prev, [field]: value };
debouncedAutoSave(newData);
return newData;
});
}, [debouncedAutoSave]);
// Resize handler with throttling
const [windowSize, setWindowSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
const throttledResize = useMemo(
() => throttle(() => {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight
});
}, 100),
[]
);
useEffect(() => {
window.addEventListener('resize', throttledResize);
return () => {
window.removeEventListener('resize', throttledResize);
throttledResize.cancel();
};
}, [throttledResize]);
return (
<div className="optimized-search-component">
{/* Search input with debouncing */}
<div className="search-section">
<input
type="text"
value={searchQuery}
onChange={handleSearchChange}
placeholder="Search..."
className={isSearching ? 'searching' : ''}
/>
{isSearching && <div className="search-spinner" />}
</div>
{/* Filter controls with debounced updates */}
<div className="filter-section">
<select
onChange={(e) => handleFilterChange('category', e.target.value)}
value={filters.category || ''}
>
<option value="">All Categories</option>
<option value="electronics">Electronics</option>
<option value="books">Books</option>
<option value="clothing">Clothing</option>
</select>
<input
type="range"
min="0"
max="1000"
value={filters.maxPrice || 1000}
onChange={(e) => handleFilterChange('maxPrice', parseInt(e.target.value))}
/>
</div>
{/* Sort controls with throttling */}
<div className="sort-section">
<button onClick={() => handleSortChange('name', 'asc')}>
Sort by Name ↑
</button>
<button onClick={() => handleSortChange('name', 'desc')}>
Sort by Name ↓
</button>
<button onClick={() => handleSortChange('price', 'asc')}>
Sort by Price ↑
</button>
<button onClick={() => handleSortChange('price', 'desc')}>
Sort by Price ↓
</button>
</div>
{/* Auto-save indicator */}
<div className="auto-save-section">
{lastSaved && (
<span>Last saved: {lastSaved.toLocaleTimeString()}</span>
)}
</div>
{/* Window size display (responsive testing) */}
<div className="window-info">
{windowSize.width} × {windowSize.height}
</div>
</div>
);
};
// Utility functions
function debounce<T extends (...args: any[]) => any>(
func: T,
delay: number
): T & { cancel: () => void } {
let timeoutId: NodeJS.Timeout;
const debounced = (...args: Parameters<T>) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func(...args), delay);
};
debounced.cancel = () => {
clearTimeout(timeoutId);
};
return debounced as T & { cancel: () => void };
}
function throttle<T extends (...args: any[]) => any>(
func: T,
delay: number
): T & { cancel: () => void } {
let inThrottle: boolean;
let timeoutId: NodeJS.Timeout;
const throttled = (...args: Parameters<T>) => {
if (!inThrottle) {
func(...args);
inThrottle = true;
timeoutId = setTimeout(() => {
inThrottle = false;
}, delay);
}
};
throttled.cancel = () => {
clearTimeout(timeoutId);
inThrottle = false;
};
return throttled as T & { cancel: () => void };
}
```
```