This is page 39 of 187. Use http://codebase.md/xmlui-org/xmlui/xmlui/mockApiDef.js?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .changeset
│ ├── config.json
│ └── itchy-cases-deny.md
├── .eslintrc.cjs
├── .github
│ ├── build-checklist.png
│ ├── ISSUE_TEMPLATE
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ └── workflows
│ ├── deploy-blog-optimized.yml
│ ├── deploy-blog-swa.yml
│ ├── deploy-blog.yml
│ ├── deploy-docs-optimized.yml
│ ├── deploy-docs-swa.yml
│ ├── deploy-docs.yml
│ ├── prepare-versions.yml
│ ├── release-packages.yml
│ ├── run-all-tests.yml
│ └── run-smoke-tests.yml
├── .gitignore
├── .prettierrc.js
├── .vscode
│ ├── launch.json
│ └── settings.json
├── blog
│ ├── .gitignore
│ ├── .gitkeep
│ ├── CHANGELOG.md
│ ├── extensions.ts
│ ├── index.html
│ ├── index.ts
│ ├── package.json
│ ├── public
│ │ ├── blog
│ │ │ ├── images
│ │ │ │ ├── an-advanced-codefence.gif
│ │ │ │ ├── an-advanced-codefence.mp4
│ │ │ │ ├── blog-page-component.png
│ │ │ │ ├── blog-scrabble.png
│ │ │ │ ├── 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-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
│ │ │ ├── llms.txt
│ │ │ ├── logo-dark.svg
│ │ │ ├── logo.svg
│ │ │ ├── pg-popout.svg
│ │ │ ├── rss.svg
│ │ │ └── xmlui-logo.svg
│ │ ├── serve.json
│ │ ├── staticwebapp.config.json
│ │ └── web.config
│ ├── scripts
│ │ ├── download-latest-xmlui.js
│ │ ├── generate-rss.js
│ │ ├── get-releases.js
│ │ └── utils.js
│ ├── src
│ │ ├── components
│ │ │ ├── BlogOverview.xmlui
│ │ │ ├── BlogPage.xmlui
│ │ │ └── PageNotFound.xmlui
│ │ ├── config.ts
│ │ ├── Main.xmlui
│ │ └── themes
│ │ └── blog-theme.ts
│ └── tsconfig.json
├── CONTRIBUTING.md
├── docs
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── ComponentRefLinks.txt
│ ├── content
│ │ ├── _meta.json
│ │ ├── components
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── APICall.md
│ │ │ ├── App.md
│ │ │ ├── AppHeader.md
│ │ │ ├── AppState.md
│ │ │ ├── AutoComplete.md
│ │ │ ├── Avatar.md
│ │ │ ├── Backdrop.md
│ │ │ ├── Badge.md
│ │ │ ├── BarChart.md
│ │ │ ├── Bookmark.md
│ │ │ ├── Breakout.md
│ │ │ ├── Button.md
│ │ │ ├── Card.md
│ │ │ ├── Carousel.md
│ │ │ ├── ChangeListener.md
│ │ │ ├── Checkbox.md
│ │ │ ├── CHStack.md
│ │ │ ├── ColorPicker.md
│ │ │ ├── Column.md
│ │ │ ├── ContentSeparator.md
│ │ │ ├── CVStack.md
│ │ │ ├── DataSource.md
│ │ │ ├── DateInput.md
│ │ │ ├── DatePicker.md
│ │ │ ├── DonutChart.md
│ │ │ ├── DropdownMenu.md
│ │ │ ├── EmojiSelector.md
│ │ │ ├── ExpandableItem.md
│ │ │ ├── FileInput.md
│ │ │ ├── FileUploadDropZone.md
│ │ │ ├── FlowLayout.md
│ │ │ ├── Footer.md
│ │ │ ├── Form.md
│ │ │ ├── FormItem.md
│ │ │ ├── FormSection.md
│ │ │ ├── Fragment.md
│ │ │ ├── H1.md
│ │ │ ├── H2.md
│ │ │ ├── H3.md
│ │ │ ├── H4.md
│ │ │ ├── H5.md
│ │ │ ├── H6.md
│ │ │ ├── Heading.md
│ │ │ ├── HSplitter.md
│ │ │ ├── HStack.md
│ │ │ ├── Icon.md
│ │ │ ├── IFrame.md
│ │ │ ├── Image.md
│ │ │ ├── Items.md
│ │ │ ├── LabelList.md
│ │ │ ├── Legend.md
│ │ │ ├── LineChart.md
│ │ │ ├── Link.md
│ │ │ ├── List.md
│ │ │ ├── Logo.md
│ │ │ ├── Markdown.md
│ │ │ ├── MenuItem.md
│ │ │ ├── MenuSeparator.md
│ │ │ ├── ModalDialog.md
│ │ │ ├── NavGroup.md
│ │ │ ├── NavLink.md
│ │ │ ├── NavPanel.md
│ │ │ ├── NoResult.md
│ │ │ ├── NumberBox.md
│ │ │ ├── Option.md
│ │ │ ├── Page.md
│ │ │ ├── PageMetaTitle.md
│ │ │ ├── Pages.md
│ │ │ ├── Pagination.md
│ │ │ ├── PasswordInput.md
│ │ │ ├── PieChart.md
│ │ │ ├── ProgressBar.md
│ │ │ ├── Queue.md
│ │ │ ├── RadioGroup.md
│ │ │ ├── RealTimeAdapter.md
│ │ │ ├── Redirect.md
│ │ │ ├── Select.md
│ │ │ ├── Slider.md
│ │ │ ├── Slot.md
│ │ │ ├── SpaceFiller.md
│ │ │ ├── Spinner.md
│ │ │ ├── Splitter.md
│ │ │ ├── Stack.md
│ │ │ ├── StickyBox.md
│ │ │ ├── SubMenuItem.md
│ │ │ ├── Switch.md
│ │ │ ├── TabItem.md
│ │ │ ├── Table.md
│ │ │ ├── TableOfContents.md
│ │ │ ├── Tabs.md
│ │ │ ├── Text.md
│ │ │ ├── TextArea.md
│ │ │ ├── TextBox.md
│ │ │ ├── Theme.md
│ │ │ ├── TimeInput.md
│ │ │ ├── Timer.md
│ │ │ ├── ToneChangerButton.md
│ │ │ ├── ToneSwitch.md
│ │ │ ├── Tooltip.md
│ │ │ ├── Tree.md
│ │ │ ├── VSplitter.md
│ │ │ ├── VStack.md
│ │ │ ├── xmlui-animations
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── Animation.md
│ │ │ │ ├── FadeAnimation.md
│ │ │ │ ├── FadeInAnimation.md
│ │ │ │ ├── FadeOutAnimation.md
│ │ │ │ ├── ScaleAnimation.md
│ │ │ │ └── SlideInAnimation.md
│ │ │ ├── xmlui-pdf
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Pdf.md
│ │ │ ├── xmlui-spreadsheet
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Spreadsheet.md
│ │ │ └── xmlui-website-blocks
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── Carousel.md
│ │ │ ├── HelloMd.md
│ │ │ ├── HeroSection.md
│ │ │ └── ScrollToTop.md
│ │ └── extensions
│ │ ├── _meta.json
│ │ ├── xmlui-animations
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ ├── Animation.md
│ │ │ ├── FadeAnimation.md
│ │ │ ├── FadeInAnimation.md
│ │ │ ├── FadeOutAnimation.md
│ │ │ ├── ScaleAnimation.md
│ │ │ └── SlideInAnimation.md
│ │ └── xmlui-website-blocks
│ │ ├── _meta.json
│ │ ├── _overview.md
│ │ ├── Carousel.md
│ │ ├── FancyButton.md
│ │ ├── HeroSection.md
│ │ └── ScrollToTop.md
│ ├── extensions.ts
│ ├── index.html
│ ├── index.ts
│ ├── package.json
│ ├── public
│ │ ├── feed.rss
│ │ ├── mockServiceWorker.js
│ │ ├── pages
│ │ │ ├── _meta.json
│ │ │ ├── app-structure.md
│ │ │ ├── build-editor-component.md
│ │ │ ├── build-hello-world-component.md
│ │ │ ├── components-intro.md
│ │ │ ├── context-variables.md
│ │ │ ├── forms.md
│ │ │ ├── globals.md
│ │ │ ├── glossary.md
│ │ │ ├── helper-tags.md
│ │ │ ├── hosted-deployment.md
│ │ │ ├── howto
│ │ │ │ ├── assign-a-complex-json-literal-to-a-component-variable.md
│ │ │ │ ├── chain-a-refetch.md
│ │ │ │ ├── control-cache-invalidation.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
│ │ │ │ ├── make-a-set-of-equal-width-cards.md
│ │ │ │ ├── make-a-table-responsive.md
│ │ │ │ ├── make-navpanel-width-responsive.md
│ │ │ │ ├── modify-a-value-reported-in-a-column.md
│ │ │ │ ├── paginate-a-list.md
│ │ │ │ ├── pass-data-to-a-modal-dialog.md
│ │ │ │ ├── react-to-button-click-not-keystrokes.md
│ │ │ │ ├── set-the-initial-value-of-a-select-from-fetched-data.md
│ │ │ │ ├── share-a-modaldialog-across-components.md
│ │ │ │ ├── sync-selections-between-table-and-list-views.md
│ │ │ │ ├── update-ui-optimistically.md
│ │ │ │ ├── use-built-in-form-validation.md
│ │ │ │ └── use-the-same-modaldialog-to-add-or-edit.md
│ │ │ ├── howto.md
│ │ │ ├── intro.md
│ │ │ ├── layout.md
│ │ │ ├── markup.md
│ │ │ ├── mcp.md
│ │ │ ├── modal-dialogs.md
│ │ │ ├── news-and-reviews.md
│ │ │ ├── reactive-intro.md
│ │ │ ├── refactoring.md
│ │ │ ├── routing-and-links.md
│ │ │ ├── samples
│ │ │ │ ├── color-palette.xmlui
│ │ │ │ ├── color-values.xmlui
│ │ │ │ ├── shadow-sizes.xmlui
│ │ │ │ ├── spacing-sizes.xmlui
│ │ │ │ ├── swatch.xmlui
│ │ │ │ ├── theme-gallery-brief.xmlui
│ │ │ │ └── theme-gallery.xmlui
│ │ │ ├── scoping.md
│ │ │ ├── scripting.md
│ │ │ ├── styles-and-themes
│ │ │ │ ├── common-units.md
│ │ │ │ ├── layout-props.md
│ │ │ │ ├── theme-variable-defaults.md
│ │ │ │ ├── theme-variables.md
│ │ │ │ └── themes.md
│ │ │ ├── template-properties.md
│ │ │ ├── test.md
│ │ │ ├── tutorial-01.md
│ │ │ ├── tutorial-02.md
│ │ │ ├── tutorial-03.md
│ │ │ ├── tutorial-04.md
│ │ │ ├── tutorial-05.md
│ │ │ ├── tutorial-06.md
│ │ │ ├── tutorial-07.md
│ │ │ ├── tutorial-08.md
│ │ │ ├── tutorial-09.md
│ │ │ ├── tutorial-10.md
│ │ │ ├── tutorial-11.md
│ │ │ ├── tutorial-12.md
│ │ │ ├── universal-properties.md
│ │ │ ├── user-defined-components.md
│ │ │ ├── vscode.md
│ │ │ ├── working-with-markdown.md
│ │ │ ├── working-with-text.md
│ │ │ ├── xmlui-animations
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── Animation.md
│ │ │ │ ├── FadeAnimation.md
│ │ │ │ ├── FadeInAnimation.md
│ │ │ │ ├── FadeOutAnimation.md
│ │ │ │ ├── ScaleAnimation.md
│ │ │ │ └── SlideInAnimation.md
│ │ │ ├── xmlui-charts
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ ├── BarChart.md
│ │ │ │ ├── DonutChart.md
│ │ │ │ ├── LabelList.md
│ │ │ │ ├── Legend.md
│ │ │ │ ├── LineChart.md
│ │ │ │ └── PieChart.md
│ │ │ ├── xmlui-pdf
│ │ │ │ ├── _meta.json
│ │ │ │ ├── _overview.md
│ │ │ │ └── Pdf.md
│ │ │ └── xmlui-spreadsheet
│ │ │ ├── _meta.json
│ │ │ ├── _overview.md
│ │ │ └── Spreadsheet.md
│ │ ├── resources
│ │ │ ├── devdocs
│ │ │ │ ├── debug-proxy-object-2.png
│ │ │ │ ├── debug-proxy-object.png
│ │ │ │ ├── table_editor_01.png
│ │ │ │ ├── table_editor_02.png
│ │ │ │ ├── table_editor_03.png
│ │ │ │ ├── table_editor_04.png
│ │ │ │ ├── table_editor_05.png
│ │ │ │ ├── table_editor_06.png
│ │ │ │ ├── table_editor_07.png
│ │ │ │ ├── table_editor_08.png
│ │ │ │ ├── table_editor_09.png
│ │ │ │ ├── table_editor_10.png
│ │ │ │ ├── table_editor_11.png
│ │ │ │ ├── table-editor-01.png
│ │ │ │ ├── table-editor-02.png
│ │ │ │ ├── table-editor-03.png
│ │ │ │ ├── table-editor-04.png
│ │ │ │ ├── table-editor-06.png
│ │ │ │ ├── table-editor-07.png
│ │ │ │ ├── table-editor-08.png
│ │ │ │ ├── table-editor-09.png
│ │ │ │ └── xmlui-rendering-of-tiptap-markdown.png
│ │ │ ├── favicon.ico
│ │ │ ├── files
│ │ │ │ ├── clients.json
│ │ │ │ ├── daily-revenue.json
│ │ │ │ ├── dashboard-stats.json
│ │ │ │ ├── demo.xmlui
│ │ │ │ ├── demo.xmlui.xs
│ │ │ │ ├── downloads
│ │ │ │ │ └── downloads.json
│ │ │ │ ├── for-download
│ │ │ │ │ ├── index-with-api.html
│ │ │ │ │ ├── index.html
│ │ │ │ │ ├── mockApi.js
│ │ │ │ │ ├── start-darwin.sh
│ │ │ │ │ ├── start-linux.sh
│ │ │ │ │ ├── start.bat
│ │ │ │ │ └── xmlui
│ │ │ │ │ └── xmlui-standalone.umd.js
│ │ │ │ ├── getting-started
│ │ │ │ │ ├── cl-tutorial-final.zip
│ │ │ │ │ ├── cl-tutorial.zip
│ │ │ │ │ ├── cl-tutorial2.zip
│ │ │ │ │ ├── cl-tutorial3.zip
│ │ │ │ │ ├── cl-tutorial4.zip
│ │ │ │ │ ├── cl-tutorial5.zip
│ │ │ │ │ ├── cl-tutorial6.zip
│ │ │ │ │ ├── getting-started.zip
│ │ │ │ │ ├── hello-xmlui.zip
│ │ │ │ │ ├── xmlui-empty.zip
│ │ │ │ │ └── xmlui-starter.zip
│ │ │ │ ├── howto
│ │ │ │ │ └── component-icons
│ │ │ │ │ └── up-arrow.svg
│ │ │ │ ├── invoices.json
│ │ │ │ ├── monthly-status.json
│ │ │ │ ├── news-and-reviews.json
│ │ │ │ ├── products.json
│ │ │ │ ├── releases.json
│ │ │ │ ├── tutorials
│ │ │ │ │ ├── datasource
│ │ │ │ │ │ └── api.ts
│ │ │ │ │ └── p2do
│ │ │ │ │ ├── api.ts
│ │ │ │ │ └── todo-logo.svg
│ │ │ │ └── xmlui.json
│ │ │ ├── github.svg
│ │ │ ├── images
│ │ │ │ ├── apiaction-tutorial
│ │ │ │ │ ├── add-success.png
│ │ │ │ │ ├── apiaction-param.png
│ │ │ │ │ ├── change-completed.png
│ │ │ │ │ ├── change-in-progress.png
│ │ │ │ │ ├── confirm-delete.png
│ │ │ │ │ ├── data-error.png
│ │ │ │ │ ├── data-progress.png
│ │ │ │ │ ├── data-success.png
│ │ │ │ │ ├── display-1.png
│ │ │ │ │ ├── item-deleted.png
│ │ │ │ │ ├── item-updated.png
│ │ │ │ │ ├── missing-api-key.png
│ │ │ │ │ ├── new-item-added.png
│ │ │ │ │ └── test-message.png
│ │ │ │ ├── chat-api
│ │ │ │ │ └── domain-model.svg
│ │ │ │ ├── components
│ │ │ │ │ ├── image
│ │ │ │ │ │ └── breakfast.jpg
│ │ │ │ │ ├── markdown
│ │ │ │ │ │ └── colors.png
│ │ │ │ │ └── modal
│ │ │ │ │ ├── deep_link_dialog_1.jpg
│ │ │ │ │ └── deep_link_dialog_2.jpg
│ │ │ │ ├── create-apps
│ │ │ │ │ ├── collapsed-vertical.png
│ │ │ │ │ ├── using-forms-warning-dialog.png
│ │ │ │ │ └── using-forms.png
│ │ │ │ ├── datasource-tutorial
│ │ │ │ │ ├── data-with-header.png
│ │ │ │ │ ├── filtered-data.png
│ │ │ │ │ ├── filtered-items.png
│ │ │ │ │ ├── initial-page-items.png
│ │ │ │ │ ├── list-items.png
│ │ │ │ │ ├── next-page-items.png
│ │ │ │ │ ├── no-data.png
│ │ │ │ │ ├── pagination-1.jpg
│ │ │ │ │ ├── pagination-1.png
│ │ │ │ │ ├── polling-1.png
│ │ │ │ │ ├── refetch-data.png
│ │ │ │ │ ├── slow-loading.png
│ │ │ │ │ ├── test-message.png
│ │ │ │ │ ├── Thumbs.db
│ │ │ │ │ ├── unconventional-data.png
│ │ │ │ │ └── unfiltered-items.png
│ │ │ │ ├── flower.jpg
│ │ │ │ ├── get-started
│ │ │ │ │ ├── add-new-contact.png
│ │ │ │ │ ├── app-modified.png
│ │ │ │ │ ├── app-start.png
│ │ │ │ │ ├── app-with-boxes.png
│ │ │ │ │ ├── app-with-toast.png
│ │ │ │ │ ├── boilerplate-structure.png
│ │ │ │ │ ├── cl-initial.png
│ │ │ │ │ ├── cl-start.png
│ │ │ │ │ ├── contact-counts.png
│ │ │ │ │ ├── contact-dialog-title.png
│ │ │ │ │ ├── contact-dialog.png
│ │ │ │ │ ├── contact-menus.png
│ │ │ │ │ ├── contact-predicates.png
│ │ │ │ │ ├── context-menu.png
│ │ │ │ │ ├── dashboard-numbers.png
│ │ │ │ │ ├── default-contact-list.png
│ │ │ │ │ ├── delete-contact.png
│ │ │ │ │ ├── delete-task.png
│ │ │ │ │ ├── detailed-template.png
│ │ │ │ │ ├── edit-contact-details.png
│ │ │ │ │ ├── edited-contact-saved.png
│ │ │ │ │ ├── empty-sections.png
│ │ │ │ │ ├── filter-completed.png
│ │ │ │ │ ├── fullwidth-desktop.png
│ │ │ │ │ ├── fullwidth-mobile.png
│ │ │ │ │ ├── initial-table.png
│ │ │ │ │ ├── items-and-badges.png
│ │ │ │ │ ├── loading-message.png
│ │ │ │ │ ├── new-contact-button.png
│ │ │ │ │ ├── new-contact-saved.png
│ │ │ │ │ ├── no-empty-sections.png
│ │ │ │ │ ├── personal-todo-initial.png
│ │ │ │ │ ├── piechart.png
│ │ │ │ │ ├── review-today.png
│ │ │ │ │ ├── rudimentary-dashboard.png
│ │ │ │ │ ├── section-collapsed.png
│ │ │ │ │ ├── sectioned-items.png
│ │ │ │ │ ├── sections-ordered.png
│ │ │ │ │ ├── spacex-list-with-links.png
│ │ │ │ │ ├── spacex-list.png
│ │ │ │ │ ├── start-personal-todo-1.png
│ │ │ │ │ ├── submit-new-contact.png
│ │ │ │ │ ├── submit-new-task.png
│ │ │ │ │ ├── syntax-highlighting.png
│ │ │ │ │ ├── table-with-badge.png
│ │ │ │ │ ├── template-with-card.png
│ │ │ │ │ ├── test-emulated-api.png
│ │ │ │ │ ├── Thumbs.db
│ │ │ │ │ ├── todo-logo.png
│ │ │ │ │ └── xmlui-tools.png
│ │ │ │ ├── HelloApp.png
│ │ │ │ ├── HelloApp2.png
│ │ │ │ ├── logos
│ │ │ │ │ ├── xmlui1.svg
│ │ │ │ │ ├── xmlui2.svg
│ │ │ │ │ ├── xmlui3.svg
│ │ │ │ │ ├── xmlui4.svg
│ │ │ │ │ ├── xmlui5.svg
│ │ │ │ │ ├── xmlui6.svg
│ │ │ │ │ └── xmlui7.svg
│ │ │ │ ├── pdf
│ │ │ │ │ └── dummy-pdf.jpg
│ │ │ │ ├── rendering-engine
│ │ │ │ │ ├── AppEngine-flow.svg
│ │ │ │ │ ├── Component.svg
│ │ │ │ │ ├── CompoundComponent.svg
│ │ │ │ │ ├── RootComponent.svg
│ │ │ │ │ └── tree-with-containers.svg
│ │ │ │ ├── reviewers-guide
│ │ │ │ │ ├── AppEngine-flow.svg
│ │ │ │ │ └── incbutton-in-action.png
│ │ │ │ ├── tools
│ │ │ │ │ └── boilerplate-structure.png
│ │ │ │ ├── try.svg
│ │ │ │ ├── tutorial
│ │ │ │ │ ├── app-chat-history.png
│ │ │ │ │ ├── app-content-placeholder.png
│ │ │ │ │ ├── app-header-and-content.png
│ │ │ │ │ ├── app-links-channel-selected.png
│ │ │ │ │ ├── app-links-click.png
│ │ │ │ │ ├── app-navigation.png
│ │ │ │ │ ├── finished-ex01.png
│ │ │ │ │ ├── finished-ex02.png
│ │ │ │ │ ├── hello.png
│ │ │ │ │ ├── splash-screen-advanced.png
│ │ │ │ │ ├── splash-screen-after-click.png
│ │ │ │ │ ├── splash-screen-centered.png
│ │ │ │ │ ├── splash-screen-events.png
│ │ │ │ │ ├── splash-screen-expression.png
│ │ │ │ │ ├── splash-screen-reuse-after.png
│ │ │ │ │ ├── splash-screen-reuse-before.png
│ │ │ │ │ └── splash-screen.png
│ │ │ │ └── tutorial-01.png
│ │ │ ├── llms.txt
│ │ │ ├── logo-dark.svg
│ │ │ ├── logo.svg
│ │ │ ├── pg-popout.svg
│ │ │ └── xmlui-logo.svg
│ │ ├── serve.json
│ │ └── web.config
│ ├── scripts
│ │ ├── download-latest-xmlui.js
│ │ ├── generate-rss.js
│ │ ├── get-releases.js
│ │ └── utils.js
│ ├── src
│ │ ├── components
│ │ │ ├── BlogOverview.xmlui
│ │ │ ├── BlogPage.xmlui
│ │ │ ├── Boxes.xmlui
│ │ │ ├── Breadcrumb.xmlui
│ │ │ ├── ChangeLog.xmlui
│ │ │ ├── ColorPalette.xmlui
│ │ │ ├── DocumentLinks.xmlui
│ │ │ ├── DocumentPage.xmlui
│ │ │ ├── DocumentPageNoTOC.xmlui
│ │ │ ├── Icons.xmlui
│ │ │ ├── IncButton.xmlui
│ │ │ ├── IncButton2.xmlui
│ │ │ ├── NameValue.xmlui
│ │ │ ├── PageNotFound.xmlui
│ │ │ ├── PaletteItem.xmlui
│ │ │ ├── Palettes.xmlui
│ │ │ ├── SectionHeader.xmlui
│ │ │ ├── TBD.xmlui
│ │ │ ├── Test.xmlui
│ │ │ ├── ThemesIntro.xmlui
│ │ │ ├── ThousandThemes.xmlui
│ │ │ ├── TubeStops.xmlui
│ │ │ ├── TubeStops.xmlui.xs
│ │ │ └── TwoColumnCode.xmlui
│ │ ├── config.ts
│ │ ├── Main.xmlui
│ │ └── themes
│ │ ├── docs-theme.ts
│ │ ├── earthtone.ts
│ │ ├── xmlui-gray-on-default.ts
│ │ ├── xmlui-green-on-default.ts
│ │ └── xmlui-orange-on-default.ts
│ └── tsconfig.json
├── LICENSE
├── package-lock.json
├── package.json
├── packages
│ ├── 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.tsx
│ │ │ ├── ConfirmationDialog.module.scss
│ │ │ ├── ConfirmationDialog.tsx
│ │ │ ├── Editor.tsx
│ │ │ ├── Header.module.scss
│ │ │ ├── Header.tsx
│ │ │ ├── Playground.tsx
│ │ │ ├── PlaygroundContent.module.scss
│ │ │ ├── PlaygroundContent.tsx
│ │ │ ├── PlaygroundNative.module.scss
│ │ │ ├── PlaygroundNative.tsx
│ │ │ ├── Preview.module.scss
│ │ │ ├── Preview.tsx
│ │ │ ├── Select.module.scss
│ │ │ ├── StandalonePlayground.tsx
│ │ │ ├── StandalonePlaygroundNative.module.scss
│ │ │ ├── StandalonePlaygroundNative.tsx
│ │ │ ├── ThemeSwitcher.module.scss
│ │ │ ├── ThemeSwitcher.tsx
│ │ │ ├── ToneSwitcher.tsx
│ │ │ ├── Tooltip.module.scss
│ │ │ ├── Tooltip.tsx
│ │ │ └── utils.ts
│ │ ├── providers
│ │ │ ├── Toast.module.scss
│ │ │ └── ToastProvider.tsx
│ │ ├── state
│ │ │ └── store.ts
│ │ ├── themes
│ │ │ └── theme.ts
│ │ └── utils
│ │ └── helpers.ts
│ ├── 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
├── playwright.config.ts
├── README.md
├── tools
│ ├── codefence
│ │ └── xmlui-code-fence-docs.md
│ ├── create-app
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── create-app.ts
│ │ ├── helpers
│ │ │ ├── copy.ts
│ │ │ ├── get-pkg-manager.ts
│ │ │ ├── git.ts
│ │ │ ├── install.ts
│ │ │ ├── is-folder-empty.ts
│ │ │ ├── is-writeable.ts
│ │ │ ├── make-dir.ts
│ │ │ └── validate-pkg.ts
│ │ ├── index.ts
│ │ ├── package.json
│ │ ├── templates
│ │ │ ├── default
│ │ │ │ └── ts
│ │ │ │ ├── gitignore
│ │ │ │ ├── index.html
│ │ │ │ ├── index.ts
│ │ │ │ ├── public
│ │ │ │ │ ├── mockServiceWorker.js
│ │ │ │ │ ├── resources
│ │ │ │ │ │ ├── favicon.ico
│ │ │ │ │ │ └── xmlui-logo.svg
│ │ │ │ │ └── serve.json
│ │ │ │ └── src
│ │ │ │ ├── components
│ │ │ │ │ ├── ApiAware.xmlui
│ │ │ │ │ ├── Home.xmlui
│ │ │ │ │ ├── IncButton.xmlui
│ │ │ │ │ └── PagePanel.xmlui
│ │ │ │ ├── config.ts
│ │ │ │ └── Main.xmlui
│ │ │ ├── index.ts
│ │ │ └── types.ts
│ │ └── tsconfig.json
│ ├── create-xmlui-hello-world
│ │ ├── index.js
│ │ └── package.json
│ └── vscode
│ ├── .gitignore
│ ├── .vscode
│ │ ├── launch.json
│ │ └── tasks.json
│ ├── .vscodeignore
│ ├── build.sh
│ ├── CHANGELOG.md
│ ├── esbuild.js
│ ├── eslint.config.mjs
│ ├── formatter-docs.md
│ ├── generate-test-sample.sh
│ ├── LICENSE.md
│ ├── package-lock.json
│ ├── package.json
│ ├── README.md
│ ├── resources
│ │ ├── xmlui-logo.png
│ │ └── xmlui-markup-syntax-highlighting.png
│ ├── src
│ │ ├── extension.ts
│ │ └── server.ts
│ ├── syntaxes
│ │ └── xmlui.tmLanguage.json
│ ├── test-samples
│ │ └── sample.xmlui
│ ├── tsconfig.json
│ └── tsconfig.tsbuildinfo
├── turbo.json
└── xmlui
├── .gitignore
├── bin
│ ├── bootstrap.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
│ ├── copilot-conventions.md
│ ├── create-xmlui-components.md
│ ├── mermaid.md
│ ├── testing-conventions.md
│ └── xmlui-in-a-nutshell.md
├── dev-docs
│ ├── accessibility.md
│ ├── build-system.md
│ ├── build-xmlui.md
│ ├── component-behaviors.md
│ ├── component-metadata.md
│ ├── components-with-options.md
│ ├── containers.md
│ ├── data-operations.md
│ ├── glossary.md
│ ├── index.md
│ ├── next
│ │ ├── component-dev-guide.md
│ │ ├── configuration-management-enhancement-summary.md
│ │ ├── documentation-scripts-refactoring-complete-summary.md
│ │ ├── documentation-scripts-refactoring-plan.md
│ │ ├── duplicate-pattern-extraction-summary.md
│ │ ├── error-handling-standardization-summary.md
│ │ ├── generating-component-reference.md
│ │ ├── index.md
│ │ ├── logging-consistency-implementation-summary.md
│ │ ├── project-build.md
│ │ ├── project-structure.md
│ │ ├── theme-context.md
│ │ ├── tiptap-design-considerations.md
│ │ ├── working-with-code.md
│ │ ├── xmlui-runtime-architecture
│ │ └── xmlui-wcag-accessibility-report.md
│ ├── react-fundamentals.md
│ ├── release-method.md
│ ├── standalone-app.md
│ ├── theme-variables-refactoring.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
│ │ ├── AppContextDefs.ts
│ │ ├── ComponentDefs.ts
│ │ ├── ContainerDefs.ts
│ │ ├── ExtensionDefs.ts
│ │ ├── FunctionDefs.ts
│ │ ├── RendererDefs.ts
│ │ ├── scripting
│ │ │ ├── BlockScope.ts
│ │ │ ├── Compilation.ts
│ │ │ ├── LogicalThread.ts
│ │ │ ├── LoopScope.ts
│ │ │ ├── modules.ts
│ │ │ ├── ScriptParserError.ts
│ │ │ ├── Token.ts
│ │ │ ├── TryScope.ts
│ │ │ └── TryScopeExp.ts
│ │ └── ThemingDefs.ts
│ ├── components
│ │ ├── _conventions.md
│ │ ├── abstractions.ts
│ │ ├── Accordion
│ │ │ ├── Accordion.md
│ │ │ ├── Accordion.module.scss
│ │ │ ├── Accordion.spec.ts
│ │ │ ├── Accordion.tsx
│ │ │ ├── AccordionContext.tsx
│ │ │ ├── AccordionItem.tsx
│ │ │ ├── AccordionItemNative.tsx
│ │ │ └── AccordionNative.tsx
│ │ ├── Animation
│ │ │ └── AnimationNative.tsx
│ │ ├── APICall
│ │ │ ├── APICall.md
│ │ │ ├── APICall.spec.ts
│ │ │ ├── APICall.tsx
│ │ │ └── APICallNative.tsx
│ │ ├── App
│ │ │ ├── App.md
│ │ │ ├── App.module.scss
│ │ │ ├── App.spec.ts
│ │ │ ├── App.tsx
│ │ │ ├── AppLayoutContext.ts
│ │ │ ├── AppNative.tsx
│ │ │ ├── AppStateContext.ts
│ │ │ ├── doc-resources
│ │ │ │ ├── condensed-sticky.xmlui
│ │ │ │ ├── condensed.xmlui
│ │ │ │ ├── horizontal-sticky.xmlui
│ │ │ │ ├── horizontal.xmlui
│ │ │ │ ├── vertical-full-header.xmlui
│ │ │ │ ├── vertical-sticky.xmlui
│ │ │ │ └── vertical.xmlui
│ │ │ ├── IndexerContext.ts
│ │ │ ├── LinkInfoContext.ts
│ │ │ ├── SearchContext.tsx
│ │ │ ├── Sheet.module.scss
│ │ │ └── Sheet.tsx
│ │ ├── AppHeader
│ │ │ ├── AppHeader.md
│ │ │ ├── AppHeader.module.scss
│ │ │ ├── AppHeader.spec.ts
│ │ │ ├── AppHeader.tsx
│ │ │ └── AppHeaderNative.tsx
│ │ ├── AppState
│ │ │ ├── AppState.md
│ │ │ ├── AppState.spec.ts
│ │ │ ├── AppState.tsx
│ │ │ └── AppStateNative.tsx
│ │ ├── AutoComplete
│ │ │ ├── AutoComplete.md
│ │ │ ├── AutoComplete.module.scss
│ │ │ ├── AutoComplete.spec.ts
│ │ │ ├── AutoComplete.tsx
│ │ │ ├── AutoCompleteContext.tsx
│ │ │ └── AutoCompleteNative.tsx
│ │ ├── Avatar
│ │ │ ├── Avatar.md
│ │ │ ├── Avatar.module.scss
│ │ │ ├── Avatar.spec.ts
│ │ │ ├── Avatar.tsx
│ │ │ └── AvatarNative.tsx
│ │ ├── Backdrop
│ │ │ ├── Backdrop.md
│ │ │ ├── Backdrop.module.scss
│ │ │ ├── Backdrop.spec.ts
│ │ │ ├── Backdrop.tsx
│ │ │ └── BackdropNative.tsx
│ │ ├── Badge
│ │ │ ├── Badge.md
│ │ │ ├── Badge.module.scss
│ │ │ ├── Badge.spec.ts
│ │ │ ├── Badge.tsx
│ │ │ └── BadgeNative.tsx
│ │ ├── Bookmark
│ │ │ ├── Bookmark.md
│ │ │ ├── Bookmark.module.scss
│ │ │ ├── Bookmark.spec.ts
│ │ │ ├── Bookmark.tsx
│ │ │ └── BookmarkNative.tsx
│ │ ├── Breakout
│ │ │ ├── Breakout.module.scss
│ │ │ ├── Breakout.spec.ts
│ │ │ ├── Breakout.tsx
│ │ │ └── BreakoutNative.tsx
│ │ ├── Button
│ │ │ ├── Button-style.spec.ts
│ │ │ ├── Button.md
│ │ │ ├── Button.module.scss
│ │ │ ├── Button.spec.ts
│ │ │ ├── Button.tsx
│ │ │ └── ButtonNative.tsx
│ │ ├── Card
│ │ │ ├── Card.md
│ │ │ ├── Card.module.scss
│ │ │ ├── Card.spec.ts
│ │ │ ├── Card.tsx
│ │ │ └── CardNative.tsx
│ │ ├── Carousel
│ │ │ ├── Carousel.md
│ │ │ ├── Carousel.module.scss
│ │ │ ├── Carousel.spec.ts
│ │ │ ├── Carousel.tsx
│ │ │ ├── CarouselContext.tsx
│ │ │ ├── CarouselItem.tsx
│ │ │ ├── CarouselItemNative.tsx
│ │ │ └── CarouselNative.tsx
│ │ ├── ChangeListener
│ │ │ ├── ChangeListener.md
│ │ │ ├── ChangeListener.spec.ts
│ │ │ ├── ChangeListener.tsx
│ │ │ └── ChangeListenerNative.tsx
│ │ ├── chart-color-schemes.ts
│ │ ├── Charts
│ │ │ ├── AreaChart
│ │ │ │ ├── AreaChart.md
│ │ │ │ ├── AreaChart.spec.ts
│ │ │ │ ├── AreaChart.tsx
│ │ │ │ └── AreaChartNative.tsx
│ │ │ ├── BarChart
│ │ │ │ ├── BarChart.md
│ │ │ │ ├── BarChart.module.scss
│ │ │ │ ├── BarChart.spec.ts
│ │ │ │ ├── BarChart.tsx
│ │ │ │ └── BarChartNative.tsx
│ │ │ ├── DonutChart
│ │ │ │ ├── DonutChart.spec.ts
│ │ │ │ └── DonutChart.tsx
│ │ │ ├── LabelList
│ │ │ │ ├── LabelList.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
│ │ ├── DataSource
│ │ │ ├── DataSource.md
│ │ │ └── DataSource.tsx
│ │ ├── DateInput
│ │ │ ├── DateInput.md
│ │ │ ├── DateInput.module.scss
│ │ │ ├── DateInput.spec.ts
│ │ │ ├── DateInput.tsx
│ │ │ └── DateInputNative.tsx
│ │ ├── DatePicker
│ │ │ ├── DatePicker.md
│ │ │ ├── DatePicker.module.scss
│ │ │ ├── DatePicker.spec.ts
│ │ │ ├── DatePicker.tsx
│ │ │ └── DatePickerNative.tsx
│ │ ├── DropdownMenu
│ │ │ ├── DropdownMenu.md
│ │ │ ├── DropdownMenu.module.scss
│ │ │ ├── DropdownMenu.spec.ts
│ │ │ ├── DropdownMenu.tsx
│ │ │ ├── DropdownMenuNative.tsx
│ │ │ ├── MenuItem.md
│ │ │ └── SubMenuItem.md
│ │ ├── EmojiSelector
│ │ │ ├── EmojiSelector.md
│ │ │ ├── EmojiSelector.spec.ts
│ │ │ ├── EmojiSelector.tsx
│ │ │ └── EmojiSelectorNative.tsx
│ │ ├── ExpandableItem
│ │ │ ├── ExpandableItem.module.scss
│ │ │ ├── ExpandableItem.spec.ts
│ │ │ ├── ExpandableItem.tsx
│ │ │ └── ExpandableItemNative.tsx
│ │ ├── FileInput
│ │ │ ├── FileInput.md
│ │ │ ├── FileInput.module.scss
│ │ │ ├── FileInput.spec.ts
│ │ │ ├── FileInput.tsx
│ │ │ └── FileInputNative.tsx
│ │ ├── FileUploadDropZone
│ │ │ ├── FileUploadDropZone.md
│ │ │ ├── FileUploadDropZone.module.scss
│ │ │ ├── FileUploadDropZone.spec.ts
│ │ │ ├── FileUploadDropZone.tsx
│ │ │ └── FileUploadDropZoneNative.tsx
│ │ ├── FlowLayout
│ │ │ ├── FlowLayout.md
│ │ │ ├── FlowLayout.module.scss
│ │ │ ├── FlowLayout.spec.ts
│ │ │ ├── FlowLayout.spec.ts-snapshots
│ │ │ │ └── Edge-cases-boxShadow-is-not-clipped-1-non-smoke-darwin.png
│ │ │ ├── FlowLayout.tsx
│ │ │ └── FlowLayoutNative.tsx
│ │ ├── Footer
│ │ │ ├── Footer.md
│ │ │ ├── Footer.module.scss
│ │ │ ├── Footer.spec.ts
│ │ │ ├── Footer.tsx
│ │ │ └── FooterNative.tsx
│ │ ├── Form
│ │ │ ├── Form.md
│ │ │ ├── Form.module.scss
│ │ │ ├── Form.spec.ts
│ │ │ ├── Form.tsx
│ │ │ ├── formActions.ts
│ │ │ ├── FormContext.ts
│ │ │ └── FormNative.tsx
│ │ ├── FormItem
│ │ │ ├── FormItem.md
│ │ │ ├── FormItem.module.scss
│ │ │ ├── FormItem.spec.ts
│ │ │ ├── FormItem.tsx
│ │ │ ├── FormItemNative.tsx
│ │ │ ├── HelperText.module.scss
│ │ │ ├── HelperText.tsx
│ │ │ ├── ItemWithLabel.tsx
│ │ │ └── Validations.ts
│ │ ├── FormSection
│ │ │ ├── FormSection.md
│ │ │ ├── FormSection.ts
│ │ │ └── FormSection.xmlui
│ │ ├── Fragment
│ │ │ ├── Fragment.spec.ts
│ │ │ └── Fragment.tsx
│ │ ├── Heading
│ │ │ ├── abstractions.ts
│ │ │ ├── H1.md
│ │ │ ├── H1.spec.ts
│ │ │ ├── H2.md
│ │ │ ├── H2.spec.ts
│ │ │ ├── H3.md
│ │ │ ├── H3.spec.ts
│ │ │ ├── H4.md
│ │ │ ├── H4.spec.ts
│ │ │ ├── H5.md
│ │ │ ├── H5.spec.ts
│ │ │ ├── H6.md
│ │ │ ├── H6.spec.ts
│ │ │ ├── Heading.md
│ │ │ ├── Heading.module.scss
│ │ │ ├── Heading.spec.ts
│ │ │ ├── Heading.tsx
│ │ │ └── HeadingNative.tsx
│ │ ├── HoverCard
│ │ │ ├── HoverCard.tsx
│ │ │ └── HovercardNative.tsx
│ │ ├── HtmlTags
│ │ │ ├── HtmlTags.module.scss
│ │ │ ├── HtmlTags.spec.ts
│ │ │ └── HtmlTags.tsx
│ │ ├── Icon
│ │ │ ├── AdmonitionDanger.tsx
│ │ │ ├── AdmonitionInfo.tsx
│ │ │ ├── AdmonitionNote.tsx
│ │ │ ├── AdmonitionTip.tsx
│ │ │ ├── AdmonitionWarning.tsx
│ │ │ ├── ApiIcon.tsx
│ │ │ ├── ArrowDropDown.module.scss
│ │ │ ├── ArrowDropDown.tsx
│ │ │ ├── ArrowDropUp.module.scss
│ │ │ ├── ArrowDropUp.tsx
│ │ │ ├── ArrowLeft.module.scss
│ │ │ ├── ArrowLeft.tsx
│ │ │ ├── ArrowRight.module.scss
│ │ │ ├── ArrowRight.tsx
│ │ │ ├── Attach.tsx
│ │ │ ├── Binding.module.scss
│ │ │ ├── Binding.tsx
│ │ │ ├── BoardIcon.tsx
│ │ │ ├── BoxIcon.tsx
│ │ │ ├── CheckIcon.tsx
│ │ │ ├── ChevronDownIcon.tsx
│ │ │ ├── ChevronLeft.tsx
│ │ │ ├── ChevronRight.tsx
│ │ │ ├── ChevronUpIcon.tsx
│ │ │ ├── CodeFileIcon.tsx
│ │ │ ├── CodeSandbox.tsx
│ │ │ ├── CompactListIcon.tsx
│ │ │ ├── ContentCopyIcon.tsx
│ │ │ ├── DarkToLightIcon.tsx
│ │ │ ├── DatabaseIcon.module.scss
│ │ │ ├── DatabaseIcon.tsx
│ │ │ ├── DocFileIcon.tsx
│ │ │ ├── DocIcon.tsx
│ │ │ ├── DotMenuHorizontalIcon.tsx
│ │ │ ├── DotMenuIcon.tsx
│ │ │ ├── EmailIcon.tsx
│ │ │ ├── EmptyFolderIcon.tsx
│ │ │ ├── ErrorIcon.tsx
│ │ │ ├── ExpressionIcon.tsx
│ │ │ ├── FillPlusCricleIcon.tsx
│ │ │ ├── FilterIcon.tsx
│ │ │ ├── FolderIcon.tsx
│ │ │ ├── GlobeIcon.tsx
│ │ │ ├── HomeIcon.tsx
│ │ │ ├── HyperLinkIcon.tsx
│ │ │ ├── Icon.md
│ │ │ ├── Icon.module.scss
│ │ │ ├── Icon.spec.ts
│ │ │ ├── Icon.tsx
│ │ │ ├── IconNative.tsx
│ │ │ ├── ImageFileIcon.tsx
│ │ │ ├── Inspect.tsx
│ │ │ ├── LightToDark.tsx
│ │ │ ├── LinkIcon.tsx
│ │ │ ├── ListIcon.tsx
│ │ │ ├── LooseListIcon.tsx
│ │ │ ├── MoonIcon.tsx
│ │ │ ├── MoreOptionsIcon.tsx
│ │ │ ├── NoSortIcon.tsx
│ │ │ ├── PDFIcon.tsx
│ │ │ ├── PenIcon.tsx
│ │ │ ├── PhoneIcon.tsx
│ │ │ ├── PhotoIcon.tsx
│ │ │ ├── PlusIcon.tsx
│ │ │ ├── SearchIcon.tsx
│ │ │ ├── ShareIcon.tsx
│ │ │ ├── SortAscendingIcon.tsx
│ │ │ ├── SortDescendingIcon.tsx
│ │ │ ├── StarsIcon.tsx
│ │ │ ├── SunIcon.tsx
│ │ │ ├── svg
│ │ │ │ ├── admonition_danger.svg
│ │ │ │ ├── admonition_info.svg
│ │ │ │ ├── admonition_note.svg
│ │ │ │ ├── admonition_tip.svg
│ │ │ │ ├── admonition_warning.svg
│ │ │ │ ├── api.svg
│ │ │ │ ├── arrow-dropdown.svg
│ │ │ │ ├── arrow-left.svg
│ │ │ │ ├── arrow-right.svg
│ │ │ │ ├── arrow-up.svg
│ │ │ │ ├── attach.svg
│ │ │ │ ├── binding.svg
│ │ │ │ ├── box.svg
│ │ │ │ ├── bulb.svg
│ │ │ │ ├── code-file.svg
│ │ │ │ ├── code-sandbox.svg
│ │ │ │ ├── dark_to_light.svg
│ │ │ │ ├── database.svg
│ │ │ │ ├── doc.svg
│ │ │ │ ├── empty-folder.svg
│ │ │ │ ├── expression.svg
│ │ │ │ ├── eye-closed.svg
│ │ │ │ ├── eye-dark.svg
│ │ │ │ ├── eye.svg
│ │ │ │ ├── file-text.svg
│ │ │ │ ├── filter.svg
│ │ │ │ ├── folder.svg
│ │ │ │ ├── img.svg
│ │ │ │ ├── inspect.svg
│ │ │ │ ├── light_to_dark.svg
│ │ │ │ ├── moon.svg
│ │ │ │ ├── pdf.svg
│ │ │ │ ├── photo.svg
│ │ │ │ ├── share.svg
│ │ │ │ ├── stars.svg
│ │ │ │ ├── sun.svg
│ │ │ │ ├── trending-down.svg
│ │ │ │ ├── trending-level.svg
│ │ │ │ ├── trending-up.svg
│ │ │ │ ├── txt.svg
│ │ │ │ ├── unknown-file.svg
│ │ │ │ ├── unlink.svg
│ │ │ │ └── xls.svg
│ │ │ ├── TableDeleteColumnIcon.tsx
│ │ │ ├── TableDeleteRowIcon.tsx
│ │ │ ├── TableInsertColumnIcon.tsx
│ │ │ ├── TableInsertRowIcon.tsx
│ │ │ ├── TrashIcon.tsx
│ │ │ ├── TrendingDownIcon.tsx
│ │ │ ├── TrendingLevelIcon.tsx
│ │ │ ├── TrendingUpIcon.tsx
│ │ │ ├── TxtIcon.tsx
│ │ │ ├── UnknownFileIcon.tsx
│ │ │ ├── UnlinkIcon.tsx
│ │ │ ├── UserIcon.tsx
│ │ │ ├── WarningIcon.tsx
│ │ │ └── XlsIcon.tsx
│ │ ├── IconProvider.tsx
│ │ ├── IconRegistryContext.tsx
│ │ ├── IFrame
│ │ │ ├── IFrame.md
│ │ │ ├── IFrame.module.scss
│ │ │ ├── IFrame.spec.ts
│ │ │ ├── IFrame.tsx
│ │ │ └── IFrameNative.tsx
│ │ ├── Image
│ │ │ ├── Image.md
│ │ │ ├── Image.module.scss
│ │ │ ├── Image.spec.ts
│ │ │ ├── Image.tsx
│ │ │ └── ImageNative.tsx
│ │ ├── Input
│ │ │ ├── index.ts
│ │ │ ├── InputAdornment.module.scss
│ │ │ ├── InputAdornment.tsx
│ │ │ ├── InputDivider.module.scss
│ │ │ ├── InputDivider.tsx
│ │ │ ├── InputLabel.module.scss
│ │ │ ├── InputLabel.tsx
│ │ │ ├── PartialInput.module.scss
│ │ │ └── PartialInput.tsx
│ │ ├── InspectButton
│ │ │ ├── InspectButton.module.scss
│ │ │ └── InspectButton.tsx
│ │ ├── Items
│ │ │ ├── Items.md
│ │ │ ├── Items.spec.ts
│ │ │ ├── Items.tsx
│ │ │ └── ItemsNative.tsx
│ │ ├── Link
│ │ │ ├── Link.md
│ │ │ ├── Link.module.scss
│ │ │ ├── Link.spec.ts
│ │ │ ├── Link.tsx
│ │ │ └── LinkNative.tsx
│ │ ├── List
│ │ │ ├── doc-resources
│ │ │ │ └── list-component-data.js
│ │ │ ├── List.md
│ │ │ ├── List.module.scss
│ │ │ ├── List.spec.ts
│ │ │ ├── List.tsx
│ │ │ └── ListNative.tsx
│ │ ├── Logo
│ │ │ ├── doc-resources
│ │ │ │ └── xmlui-logo.svg
│ │ │ ├── Logo.md
│ │ │ ├── Logo.tsx
│ │ │ └── LogoNative.tsx
│ │ ├── Markdown
│ │ │ ├── CodeText.module.scss
│ │ │ ├── CodeText.tsx
│ │ │ ├── Markdown.md
│ │ │ ├── Markdown.module.scss
│ │ │ ├── Markdown.spec.ts
│ │ │ ├── Markdown.tsx
│ │ │ ├── MarkdownNative.tsx
│ │ │ ├── parse-binding-expr.ts
│ │ │ └── utils.ts
│ │ ├── metadata-helpers.ts
│ │ ├── ModalDialog
│ │ │ ├── ConfirmationModalContextProvider.tsx
│ │ │ ├── Dialog.module.scss
│ │ │ ├── Dialog.tsx
│ │ │ ├── ModalDialog.md
│ │ │ ├── ModalDialog.module.scss
│ │ │ ├── ModalDialog.spec.ts
│ │ │ ├── ModalDialog.tsx
│ │ │ ├── ModalDialogNative.tsx
│ │ │ └── ModalVisibilityContext.tsx
│ │ ├── NavGroup
│ │ │ ├── NavGroup.md
│ │ │ ├── NavGroup.module.scss
│ │ │ ├── NavGroup.spec.ts
│ │ │ ├── NavGroup.tsx
│ │ │ ├── NavGroupContext.ts
│ │ │ └── NavGroupNative.tsx
│ │ ├── NavLink
│ │ │ ├── NavLink.md
│ │ │ ├── NavLink.module.scss
│ │ │ ├── NavLink.spec.ts
│ │ │ ├── NavLink.tsx
│ │ │ └── NavLinkNative.tsx
│ │ ├── NavPanel
│ │ │ ├── NavPanel.md
│ │ │ ├── NavPanel.module.scss
│ │ │ ├── NavPanel.spec.ts
│ │ │ ├── NavPanel.tsx
│ │ │ └── NavPanelNative.tsx
│ │ ├── NestedApp
│ │ │ ├── AppWithCodeView.module.scss
│ │ │ ├── AppWithCodeView.tsx
│ │ │ ├── AppWithCodeViewNative.tsx
│ │ │ ├── defaultProps.tsx
│ │ │ ├── logo.svg
│ │ │ ├── NestedApp.module.scss
│ │ │ ├── NestedApp.tsx
│ │ │ ├── NestedAppNative.tsx
│ │ │ ├── Tooltip.module.scss
│ │ │ ├── Tooltip.tsx
│ │ │ └── utils.ts
│ │ ├── NoResult
│ │ │ ├── NoResult.md
│ │ │ ├── NoResult.module.scss
│ │ │ ├── NoResult.spec.ts
│ │ │ ├── NoResult.tsx
│ │ │ └── NoResultNative.tsx
│ │ ├── NumberBox
│ │ │ ├── numberbox-abstractions.ts
│ │ │ ├── NumberBox.md
│ │ │ ├── NumberBox.module.scss
│ │ │ ├── NumberBox.spec.ts
│ │ │ ├── NumberBox.tsx
│ │ │ └── NumberBoxNative.tsx
│ │ ├── Option
│ │ │ ├── Option.md
│ │ │ ├── Option.spec.ts
│ │ │ ├── Option.tsx
│ │ │ ├── OptionNative.tsx
│ │ │ └── OptionTypeProvider.tsx
│ │ ├── PageMetaTitle
│ │ │ ├── PageMetaTilteNative.tsx
│ │ │ ├── PageMetaTitle.md
│ │ │ ├── PageMetaTitle.spec.ts
│ │ │ └── PageMetaTitle.tsx
│ │ ├── Pages
│ │ │ ├── Page.md
│ │ │ ├── Pages.md
│ │ │ ├── Pages.module.scss
│ │ │ ├── Pages.tsx
│ │ │ └── PagesNative.tsx
│ │ ├── Pagination
│ │ │ ├── Pagination.md
│ │ │ ├── Pagination.module.scss
│ │ │ ├── Pagination.spec.ts
│ │ │ ├── Pagination.tsx
│ │ │ └── PaginationNative.tsx
│ │ ├── PositionedContainer
│ │ │ ├── PositionedContainer.module.scss
│ │ │ ├── PositionedContainer.tsx
│ │ │ └── PositionedContainerNative.tsx
│ │ ├── ProfileMenu
│ │ │ ├── ProfileMenu.module.scss
│ │ │ └── ProfileMenu.tsx
│ │ ├── ProgressBar
│ │ │ ├── ProgressBar.md
│ │ │ ├── ProgressBar.module.scss
│ │ │ ├── ProgressBar.spec.ts
│ │ │ ├── ProgressBar.tsx
│ │ │ └── ProgressBarNative.tsx
│ │ ├── Queue
│ │ │ ├── Queue.md
│ │ │ ├── Queue.spec.ts
│ │ │ ├── Queue.tsx
│ │ │ ├── queueActions.ts
│ │ │ └── QueueNative.tsx
│ │ ├── RadioGroup
│ │ │ ├── RadioGroup.md
│ │ │ ├── RadioGroup.module.scss
│ │ │ ├── RadioGroup.spec.ts
│ │ │ ├── RadioGroup.tsx
│ │ │ ├── RadioGroupNative.tsx
│ │ │ ├── RadioItem.tsx
│ │ │ └── RadioItemNative.tsx
│ │ ├── RealTimeAdapter
│ │ │ ├── RealTimeAdapter.tsx
│ │ │ └── RealTimeAdapterNative.tsx
│ │ ├── Redirect
│ │ │ ├── Redirect.md
│ │ │ ├── Redirect.spec.ts
│ │ │ └── Redirect.tsx
│ │ ├── ResponsiveBar
│ │ │ ├── README.md
│ │ │ ├── ResponsiveBar.md
│ │ │ ├── ResponsiveBar.module.scss
│ │ │ ├── ResponsiveBar.spec.ts
│ │ │ ├── ResponsiveBar.tsx
│ │ │ └── ResponsiveBarNative.tsx
│ │ ├── Select
│ │ │ ├── HiddenOption.tsx
│ │ │ ├── OptionContext.ts
│ │ │ ├── Select.md
│ │ │ ├── Select.module.scss
│ │ │ ├── Select.spec.ts
│ │ │ ├── Select.tsx
│ │ │ ├── SelectContext.tsx
│ │ │ └── SelectNative.tsx
│ │ ├── SelectionStore
│ │ │ ├── SelectionStore.md
│ │ │ ├── SelectionStore.tsx
│ │ │ └── SelectionStoreNative.tsx
│ │ ├── Slider
│ │ │ ├── Slider.md
│ │ │ ├── Slider.module.scss
│ │ │ ├── Slider.spec.ts
│ │ │ ├── Slider.tsx
│ │ │ └── SliderNative.tsx
│ │ ├── Slot
│ │ │ ├── Slot.md
│ │ │ ├── Slot.spec.ts
│ │ │ └── Slot.ts
│ │ ├── SlotItem.tsx
│ │ ├── SpaceFiller
│ │ │ ├── SpaceFiller.md
│ │ │ ├── SpaceFiller.module.scss
│ │ │ ├── SpaceFiller.spec.ts
│ │ │ ├── SpaceFiller.tsx
│ │ │ └── SpaceFillerNative.tsx
│ │ ├── Spinner
│ │ │ ├── Spinner.md
│ │ │ ├── Spinner.module.scss
│ │ │ ├── Spinner.spec.ts
│ │ │ ├── Spinner.tsx
│ │ │ └── SpinnerNative.tsx
│ │ ├── Splitter
│ │ │ ├── HSplitter.md
│ │ │ ├── HSplitter.spec.ts
│ │ │ ├── Splitter.md
│ │ │ ├── Splitter.module.scss
│ │ │ ├── Splitter.spec.ts
│ │ │ ├── Splitter.tsx
│ │ │ ├── SplitterNative.tsx
│ │ │ ├── utils.ts
│ │ │ ├── VSplitter.md
│ │ │ └── VSplitter.spec.ts
│ │ ├── Stack
│ │ │ ├── CHStack.md
│ │ │ ├── CHStack.spec.ts
│ │ │ ├── CVStack.md
│ │ │ ├── CVStack.spec.ts
│ │ │ ├── HStack.md
│ │ │ ├── HStack.spec.ts
│ │ │ ├── Stack.md
│ │ │ ├── Stack.module.scss
│ │ │ ├── Stack.spec.ts
│ │ │ ├── Stack.tsx
│ │ │ ├── StackNative.tsx
│ │ │ ├── VStack.md
│ │ │ └── VStack.spec.ts
│ │ ├── StickyBox
│ │ │ ├── StickyBox.md
│ │ │ ├── StickyBox.module.scss
│ │ │ ├── StickyBox.tsx
│ │ │ └── StickyBoxNative.tsx
│ │ ├── Switch
│ │ │ ├── Switch.md
│ │ │ ├── Switch.spec.ts
│ │ │ └── Switch.tsx
│ │ ├── Table
│ │ │ ├── doc-resources
│ │ │ │ └── list-component-data.js
│ │ │ ├── react-table-config.d.ts
│ │ │ ├── Table.md
│ │ │ ├── Table.module.scss
│ │ │ ├── Table.spec.ts
│ │ │ ├── Table.tsx
│ │ │ ├── TableNative.tsx
│ │ │ └── useRowSelection.tsx
│ │ ├── TableOfContents
│ │ │ ├── TableOfContents.module.scss
│ │ │ ├── TableOfContents.spec.ts
│ │ │ ├── TableOfContents.tsx
│ │ │ └── TableOfContentsNative.tsx
│ │ ├── Tabs
│ │ │ ├── TabContext.tsx
│ │ │ ├── TabItem.md
│ │ │ ├── TabItem.tsx
│ │ │ ├── TabItemNative.tsx
│ │ │ ├── Tabs.md
│ │ │ ├── Tabs.module.scss
│ │ │ ├── Tabs.spec.ts
│ │ │ ├── Tabs.tsx
│ │ │ └── TabsNative.tsx
│ │ ├── Text
│ │ │ ├── Text.md
│ │ │ ├── Text.module.scss
│ │ │ ├── Text.spec.ts
│ │ │ ├── Text.tsx
│ │ │ └── TextNative.tsx
│ │ ├── TextArea
│ │ │ ├── TextArea.md
│ │ │ ├── TextArea.module.scss
│ │ │ ├── TextArea.spec.ts
│ │ │ ├── TextArea.tsx
│ │ │ ├── TextAreaNative.tsx
│ │ │ ├── TextAreaResizable.tsx
│ │ │ └── useComposedRef.ts
│ │ ├── TextBox
│ │ │ ├── TextBox.md
│ │ │ ├── TextBox.module.scss
│ │ │ ├── TextBox.spec.ts
│ │ │ ├── TextBox.tsx
│ │ │ └── TextBoxNative.tsx
│ │ ├── Theme
│ │ │ ├── NotificationToast.tsx
│ │ │ ├── Theme.md
│ │ │ ├── Theme.module.scss
│ │ │ ├── Theme.spec.ts
│ │ │ ├── Theme.tsx
│ │ │ └── ThemeNative.tsx
│ │ ├── TimeInput
│ │ │ ├── TimeInput.md
│ │ │ ├── TimeInput.module.scss
│ │ │ ├── TimeInput.spec.ts
│ │ │ ├── TimeInput.tsx
│ │ │ ├── TimeInputNative.tsx
│ │ │ └── utils.ts
│ │ ├── Timer
│ │ │ ├── Timer.md
│ │ │ ├── Timer.spec.ts
│ │ │ ├── Timer.tsx
│ │ │ └── TimerNative.tsx
│ │ ├── Toggle
│ │ │ ├── Toggle.module.scss
│ │ │ └── Toggle.tsx
│ │ ├── ToneChangerButton
│ │ │ ├── ToneChangerButton.md
│ │ │ ├── ToneChangerButton.spec.ts
│ │ │ └── ToneChangerButton.tsx
│ │ ├── ToneSwitch
│ │ │ ├── ToneSwitch.md
│ │ │ ├── ToneSwitch.module.scss
│ │ │ ├── ToneSwitch.spec.ts
│ │ │ ├── ToneSwitch.tsx
│ │ │ └── ToneSwitchNative.tsx
│ │ ├── Tooltip
│ │ │ ├── Tooltip.md
│ │ │ ├── Tooltip.module.scss
│ │ │ ├── Tooltip.spec.ts
│ │ │ ├── Tooltip.tsx
│ │ │ └── TooltipNative.tsx
│ │ ├── Tree
│ │ │ ├── testData.ts
│ │ │ ├── Tree-dynamic.spec.ts
│ │ │ ├── Tree-icons.spec.ts
│ │ │ ├── Tree.md
│ │ │ ├── Tree.spec.ts
│ │ │ ├── TreeComponent.module.scss
│ │ │ ├── TreeComponent.tsx
│ │ │ └── TreeNative.tsx
│ │ ├── TreeDisplay
│ │ │ ├── TreeDisplay.md
│ │ │ ├── TreeDisplay.module.scss
│ │ │ ├── TreeDisplay.tsx
│ │ │ └── TreeDisplayNative.tsx
│ │ ├── ValidationSummary
│ │ │ ├── ValidationSummary.module.scss
│ │ │ └── ValidationSummary.tsx
│ │ └── VisuallyHidden.tsx
│ ├── components-core
│ │ ├── abstractions
│ │ │ ├── ComponentRenderer.ts
│ │ │ ├── LoaderRenderer.ts
│ │ │ ├── standalone.ts
│ │ │ └── treeAbstractions.ts
│ │ ├── action
│ │ │ ├── actions.ts
│ │ │ ├── APICall.tsx
│ │ │ ├── FileDownloadAction.tsx
│ │ │ ├── FileUploadAction.tsx
│ │ │ ├── NavigateAction.tsx
│ │ │ └── TimedAction.tsx
│ │ ├── ApiBoundComponent.tsx
│ │ ├── appContext
│ │ │ ├── date-functions.ts
│ │ │ ├── math-function.ts
│ │ │ └── misc-utils.ts
│ │ ├── AppContext.tsx
│ │ ├── behaviors
│ │ │ ├── Behavior.tsx
│ │ │ └── CoreBehaviors.tsx
│ │ ├── component-hooks.ts
│ │ ├── ComponentDecorator.tsx
│ │ ├── ComponentViewer.tsx
│ │ ├── CompoundComponent.tsx
│ │ ├── constants.ts
│ │ ├── DebugViewProvider.tsx
│ │ ├── descriptorHelper.ts
│ │ ├── devtools
│ │ │ ├── InspectorDialog.module.scss
│ │ │ ├── InspectorDialog.tsx
│ │ │ └── InspectorDialogVisibilityContext.tsx
│ │ ├── EngineError.ts
│ │ ├── event-handlers.ts
│ │ ├── InspectorButton.module.scss
│ │ ├── InspectorContext.tsx
│ │ ├── interception
│ │ │ ├── abstractions.ts
│ │ │ ├── ApiInterceptor.ts
│ │ │ ├── ApiInterceptorProvider.tsx
│ │ │ ├── apiInterceptorWorker.ts
│ │ │ ├── Backend.ts
│ │ │ ├── Errors.ts
│ │ │ ├── IndexedDb.ts
│ │ │ ├── initMock.ts
│ │ │ ├── InMemoryDb.ts
│ │ │ ├── ReadonlyCollection.ts
│ │ │ └── useApiInterceptorContext.tsx
│ │ ├── loader
│ │ │ ├── ApiLoader.tsx
│ │ │ ├── DataLoader.tsx
│ │ │ ├── ExternalDataLoader.tsx
│ │ │ ├── Loader.tsx
│ │ │ ├── MockLoaderRenderer.tsx
│ │ │ └── PageableLoader.tsx
│ │ ├── LoaderComponent.tsx
│ │ ├── markup-check.ts
│ │ ├── parts.ts
│ │ ├── renderers.ts
│ │ ├── rendering
│ │ │ ├── AppContent.tsx
│ │ │ ├── AppRoot.tsx
│ │ │ ├── AppWrapper.tsx
│ │ │ ├── buildProxy.ts
│ │ │ ├── collectFnVarDeps.ts
│ │ │ ├── ComponentAdapter.tsx
│ │ │ ├── ComponentWrapper.tsx
│ │ │ ├── Container.tsx
│ │ │ ├── containers.ts
│ │ │ ├── ContainerWrapper.tsx
│ │ │ ├── ErrorBoundary.module.scss
│ │ │ ├── ErrorBoundary.tsx
│ │ │ ├── InvalidComponent.module.scss
│ │ │ ├── InvalidComponent.tsx
│ │ │ ├── nodeUtils.ts
│ │ │ ├── reducer.ts
│ │ │ ├── renderChild.tsx
│ │ │ ├── StandaloneComponent.tsx
│ │ │ ├── StateContainer.tsx
│ │ │ ├── UnknownComponent.module.scss
│ │ │ ├── UnknownComponent.tsx
│ │ │ └── valueExtractor.ts
│ │ ├── reportEngineError.ts
│ │ ├── RestApiProxy.ts
│ │ ├── script-runner
│ │ │ ├── asyncProxy.ts
│ │ │ ├── AttributeValueParser.ts
│ │ │ ├── bannedFunctions.ts
│ │ │ ├── BindingTreeEvaluationContext.ts
│ │ │ ├── eval-tree-async.ts
│ │ │ ├── eval-tree-common.ts
│ │ │ ├── eval-tree-sync.ts
│ │ │ ├── ParameterParser.ts
│ │ │ ├── process-statement-async.ts
│ │ │ ├── process-statement-common.ts
│ │ │ ├── process-statement-sync.ts
│ │ │ ├── ScriptingSourceTree.ts
│ │ │ ├── simplify-expression.ts
│ │ │ ├── statement-queue.ts
│ │ │ └── visitors.ts
│ │ ├── StandaloneApp.tsx
│ │ ├── StandaloneExtensionManager.ts
│ │ ├── TableOfContentsContext.tsx
│ │ ├── theming
│ │ │ ├── _themes.scss
│ │ │ ├── component-layout-resolver.ts
│ │ │ ├── extendThemeUtils.ts
│ │ │ ├── hvar.ts
│ │ │ ├── layout-resolver.ts
│ │ │ ├── parse-layout-props.ts
│ │ │ ├── StyleContext.tsx
│ │ │ ├── StyleRegistry.ts
│ │ │ ├── ThemeContext.tsx
│ │ │ ├── ThemeProvider.tsx
│ │ │ ├── themes
│ │ │ │ ├── base-utils.ts
│ │ │ │ ├── palette.ts
│ │ │ │ ├── root.ts
│ │ │ │ ├── solid.ts
│ │ │ │ ├── theme-colors.ts
│ │ │ │ └── xmlui.ts
│ │ │ ├── themeVars.module.scss
│ │ │ ├── themeVars.ts
│ │ │ ├── transformThemeVars.ts
│ │ │ └── utils.ts
│ │ ├── utils
│ │ │ ├── actionUtils.ts
│ │ │ ├── audio-utils.ts
│ │ │ ├── base64-utils.ts
│ │ │ ├── compound-utils.ts
│ │ │ ├── css-utils.ts
│ │ │ ├── DataLoaderQueryKeyGenerator.ts
│ │ │ ├── date-utils.ts
│ │ │ ├── extractParam.ts
│ │ │ ├── hooks.tsx
│ │ │ ├── LruCache.ts
│ │ │ ├── mergeProps.ts
│ │ │ ├── misc.ts
│ │ │ ├── request-params.ts
│ │ │ ├── statementUtils.ts
│ │ │ └── treeUtils.ts
│ │ └── xmlui-parser.ts
│ ├── index-standalone.ts
│ ├── index.scss
│ ├── index.ts
│ ├── language-server
│ │ ├── server-common.ts
│ │ ├── server-web-worker.ts
│ │ ├── server.ts
│ │ ├── services
│ │ │ ├── common
│ │ │ │ ├── docs-generation.ts
│ │ │ │ ├── lsp-utils.ts
│ │ │ │ ├── metadata-utils.ts
│ │ │ │ └── syntax-node-utilities.ts
│ │ │ ├── completion.ts
│ │ │ ├── diagnostic.ts
│ │ │ ├── format.ts
│ │ │ └── hover.ts
│ │ └── xmlui-metadata-generated.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
│ │ ├── ParserError.ts
│ │ ├── scanner.ts
│ │ ├── syntax-kind.ts
│ │ ├── syntax-node.ts
│ │ ├── transform.ts
│ │ ├── utils.ts
│ │ ├── xmlui-serializer.ts
│ │ └── xmlui-tree.ts
│ ├── react-app-env.d.ts
│ ├── syntax
│ │ ├── monaco
│ │ │ ├── grammar.monacoLanguage.ts
│ │ │ ├── index.ts
│ │ │ ├── xmlui-dark.ts
│ │ │ ├── xmlui-light.ts
│ │ │ └── xmluiscript.monacoLanguage.ts
│ │ └── textMate
│ │ ├── index.ts
│ │ ├── xmlui-dark.json
│ │ ├── xmlui-light.json
│ │ ├── xmlui.json
│ │ └── xmlui.tmLanguage.json
│ ├── testing
│ │ ├── assertions.ts
│ │ ├── component-test-helpers.ts
│ │ ├── ComponentDrivers.ts
│ │ ├── drivers
│ │ │ ├── DateInputDriver.ts
│ │ │ ├── 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
│ │ └── themed-app-test-helpers.ts
│ └── vite-env.d.ts
├── tests
│ ├── components
│ │ ├── CodeBlock
│ │ │ └── hightlight-code.test.ts
│ │ ├── playground-pattern.test.ts
│ │ └── Tree
│ │ └── Tree-states.test.ts
│ ├── components-core
│ │ ├── abstractions
│ │ │ └── treeAbstractions.test.ts
│ │ ├── container
│ │ │ └── buildProxy.test.ts
│ │ ├── interception
│ │ │ ├── orderBy.test.ts
│ │ │ ├── ReadOnlyCollection.test.ts
│ │ │ └── request-param-converter.test.ts
│ │ ├── scripts-runner
│ │ │ ├── AttributeValueParser.test.ts
│ │ │ ├── eval-tree-arrow-async.test.ts
│ │ │ ├── eval-tree-arrow.test.ts
│ │ │ ├── eval-tree-func-decl-async.test.ts
│ │ │ ├── eval-tree-func-decl.test.ts
│ │ │ ├── eval-tree-pre-post.test.ts
│ │ │ ├── eval-tree-regression.test.ts
│ │ │ ├── eval-tree.test.ts
│ │ │ ├── function-proxy.test.ts
│ │ │ ├── parser-regression.test.ts
│ │ │ ├── process-event.test.ts
│ │ │ ├── process-function.test.ts
│ │ │ ├── process-implicit-context.test.ts
│ │ │ ├── process-statement-asgn.test.ts
│ │ │ ├── process-statement-destruct.test.ts
│ │ │ ├── process-statement-regs.test.ts
│ │ │ ├── process-statement-sync.test.ts
│ │ │ ├── process-statement.test.ts
│ │ │ ├── process-switch-sync.test.ts
│ │ │ ├── process-switch.test.ts
│ │ │ ├── process-try-sync.test.ts
│ │ │ ├── process-try.test.ts
│ │ │ └── test-helpers.ts
│ │ ├── test-metadata-handler.ts
│ │ ├── theming
│ │ │ ├── border-segments.test.ts
│ │ │ ├── component-layout.resolver.test.ts
│ │ │ ├── layout-property-parser.test.ts
│ │ │ ├── layout-resolver.test.ts
│ │ │ ├── layout-resolver2.test.ts
│ │ │ ├── layout-vp-override.test.ts
│ │ │ └── padding-segments.test.ts
│ │ └── utils
│ │ ├── date-utils.test.ts
│ │ ├── format-human-elapsed-time.test.ts
│ │ └── LruCache.test.ts
│ ├── language-server
│ │ ├── completion.test.ts
│ │ ├── format.test.ts
│ │ ├── hover.test.ts
│ │ └── mockData.ts
│ └── parsers
│ ├── common
│ │ └── input-stream.test.ts
│ ├── markdown
│ │ └── parse-binding-expression.test.ts
│ ├── parameter-parser.test.ts
│ ├── paremeter-parser.test.ts
│ ├── scripting
│ │ ├── eval-tree-arrow.test.ts
│ │ ├── eval-tree-pre-post.test.ts
│ │ ├── eval-tree.test.ts
│ │ ├── function-proxy.test.ts
│ │ ├── lexer-literals.test.ts
│ │ ├── lexer-misc.test.ts
│ │ ├── module-parse.test.ts
│ │ ├── parser-arrow.test.ts
│ │ ├── parser-assignments.test.ts
│ │ ├── parser-binary.test.ts
│ │ ├── parser-destructuring.test.ts
│ │ ├── parser-errors.test.ts
│ │ ├── parser-expressions.test.ts
│ │ ├── parser-function.test.ts
│ │ ├── parser-literals.test.ts
│ │ ├── parser-primary.test.ts
│ │ ├── parser-regex.test.ts
│ │ ├── parser-statements.test.ts
│ │ ├── parser-unary.test.ts
│ │ ├── process-event.test.ts
│ │ ├── process-implicit-context.test.ts
│ │ ├── process-statement-asgn.test.ts
│ │ ├── process-statement-destruct.test.ts
│ │ ├── process-statement-regs.test.ts
│ │ ├── process-statement-sync.test.ts
│ │ ├── process-statement.test.ts
│ │ ├── process-switch-sync.test.ts
│ │ ├── process-switch.test.ts
│ │ ├── process-try-sync.test.ts
│ │ ├── process-try.test.ts
│ │ ├── simplify-expression.test.ts
│ │ ├── statement-hooks.test.ts
│ │ └── test-helpers.ts
│ ├── style-parser
│ │ ├── generateHvarChain.test.ts
│ │ ├── parseHVar.test.ts
│ │ ├── parser.test.ts
│ │ └── tokens.test.ts
│ └── xmlui
│ ├── lint.test.ts
│ ├── parser.test.ts
│ ├── scanner.test.ts
│ ├── transform.attr.test.ts
│ ├── transform.circular.test.ts
│ ├── transform.element.test.ts
│ ├── transform.errors.test.ts
│ ├── transform.escape.test.ts
│ ├── transform.regression.test.ts
│ ├── transform.script.test.ts
│ ├── transform.test.ts
│ └── xmlui.ts
├── tests-e2e
│ ├── api-bound-component-regression.spec.ts
│ ├── api-call-as-extracted-component.spec.ts
│ ├── assign-to-object-or-array-regression.spec.ts
│ ├── binding-regression.spec.ts
│ ├── children-as-template-context-vars.spec.ts
│ ├── compound-component.spec.ts
│ ├── context-vars-regression.spec.ts
│ ├── data-bindings.spec.ts
│ ├── datasource-and-api-usage-in-var.spec.ts
│ ├── datasource-direct-binding.spec.ts
│ ├── datasource-onLoaded-regression.spec.ts
│ ├── modify-array-item-regression.spec.ts
│ ├── namespaces.spec.ts
│ ├── push-to-array-regression.spec.ts
│ ├── screen-breakpoints.spec.ts
│ ├── scripting.spec.ts
│ ├── state-scope-in-pages.spec.ts
│ └── state-var-scopes.spec.ts
├── tsconfig.json
├── tsdown.config.ts
├── vite.config.ts
└── vitest.config.ts
```
# Files
--------------------------------------------------------------------------------
/xmlui/src/components/Card/Card.spec.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { getBounds, SKIP_REASON } from "../../testing/component-test-helpers";
2 | import { expect, test } from "../../testing/fixtures";
3 |
4 | // =============================================================================
5 | // BASIC FUNCTIONALITY TESTS
6 | // =============================================================================
7 |
8 | test.describe("Basic Functionality", () => {
9 | test("Card renders", async ({ initTestBed, createCardDriver }) => {
10 | await initTestBed(`<Card />`);
11 | const driver = await createCardDriver();
12 | await expect(driver.component).toBeVisible();
13 | });
14 |
15 | test("Card renders with title", async ({ initTestBed, createCardDriver }) => {
16 | await initTestBed(`<Card title="Test Title" />`);
17 | const title = (await createCardDriver()).component.getByRole("heading");
18 | await expect(title).toBeVisible();
19 | await expect(title).toHaveText("Test Title");
20 | });
21 |
22 | test("Card renders with subtitle", async ({ initTestBed, createCardDriver }) => {
23 | await initTestBed(`<Card subtitle="Test Subtitle" />`);
24 | const subtitle = (await createCardDriver()).component.locator("div").first();
25 | await expect(subtitle).toBeVisible();
26 | await expect(subtitle).toHaveText("Test Subtitle");
27 | });
28 |
29 | test("Card renders with both title and subtitle", async ({ initTestBed, createCardDriver }) => {
30 | await initTestBed(`
31 | <Card title="Test Title" subtitle="Test Subtitle">
32 | <Text value="Card content" />
33 | </Card>
34 | `);
35 | const driver = await createCardDriver();
36 | const title = driver.component.getByRole("heading");
37 | const subtitle = driver.component.getByText("Test Subtitle");
38 |
39 | await expect(title).toHaveText("Test Title");
40 | await expect(subtitle).toBeVisible();
41 | });
42 |
43 | test("displays avatar when avatarUrl is provided", async ({ initTestBed, createCardDriver }) => {
44 | await initTestBed(`<Card avatarUrl="/resources/flower-640x480.jpg" />`);
45 | const avatar = (await createCardDriver()).avatar;
46 | await expect(avatar).toBeVisible();
47 | await expect(avatar).toHaveAttribute("src", "/resources/flower-640x480.jpg");
48 | });
49 |
50 | test("clicking linkTo title navigates", async ({ page, initTestBed, createCardDriver }) => {
51 | await initTestBed(`<Card title="Clickable Title" linkTo="/test-link" />`);
52 | const titleLink = (await createCardDriver()).component.getByRole("link", {
53 | name: "Clickable Title",
54 | });
55 | await titleLink.click();
56 | await expect(page).toHaveURL(/\/test-link$/);
57 | });
58 |
59 | test("showAvatar=false hides avatar even when avatarUrl is provided", async ({
60 | initTestBed,
61 | createCardDriver,
62 | }) => {
63 | await initTestBed(`
64 | <Card avatarUrl="https://i.pravatar.cc/100" showAvatar="false" title="Test Title" />
65 | `);
66 | const driver = await createCardDriver();
67 | await expect(driver.avatar).not.toBeVisible();
68 | await expect(driver.component.getByText("Test Title")).toBeVisible();
69 | });
70 |
71 | test("showAvatar displays initials with single word title", async ({
72 | initTestBed,
73 | createCardDriver,
74 | }) => {
75 | await initTestBed(`<Card showAvatar="true" title="John" />`);
76 | await expect(
77 | (await createCardDriver()).component.getByText("J", { exact: true }),
78 | ).toBeVisible();
79 | });
80 |
81 | test("showAvatar=true with empty title displays no initials", async ({
82 | initTestBed,
83 | createCardDriver,
84 | }) => {
85 | await initTestBed(`<Card showAvatar="true" />`);
86 | const avatar = (await createCardDriver()).avatar;
87 | await expect(avatar).toHaveText(/^$/);
88 | });
89 |
90 | test("linkTo without title does not create link", async ({ initTestBed, createCardDriver }) => {
91 | await initTestBed(`
92 | <Card linkTo="/test-link">
93 | <Text value="Content" />
94 | </Card>
95 | `);
96 | const driver = await createCardDriver();
97 | await expect(driver.component.getByRole("link")).not.toBeVisible();
98 | await expect(driver.component.getByText("Content")).toBeVisible();
99 | });
100 |
101 | test("orientation horizontal displays children in row", async ({
102 | initTestBed,
103 | createTextDriver,
104 | }) => {
105 | await initTestBed(`
106 | <Card orientation="horizontal">
107 | <Text testId="text-1" value="Child 1" />
108 | <Text testId="text-2" value="Child 2" />
109 | </Card>
110 | `);
111 | const text1Driver = await createTextDriver("text-1");
112 | const text2Driver = await createTextDriver("text-2");
113 |
114 | const { right: text1Right } = await getBounds(text1Driver);
115 | const { right: text2Left } = await getBounds(text2Driver);
116 | expect(text1Right).toBeLessThan(text2Left);
117 | });
118 |
119 | test("orientation vertical displays children in column", async ({
120 | initTestBed,
121 | createTextDriver,
122 | }) => {
123 | await initTestBed(`
124 | <Card orientation="vertical">
125 | <Text testId="text-1" value="Child 1" />
126 | <Text testId="text-2" value="Child 2" />
127 | </Card>
128 | `);
129 | const text1Driver = await createTextDriver("text-1");
130 | const text2Driver = await createTextDriver("text-2");
131 |
132 | const { bottom: text1Bottom } = await getBounds(text1Driver);
133 | const { top: text2Top } = await getBounds(text2Driver);
134 | expect(text1Bottom).toBeLessThan(text2Top);
135 | });
136 | });
137 |
138 | // =============================================================================
139 | // EVENT HANDLING TESTS
140 | // =============================================================================
141 |
142 | test.describe("Event Handling", () => {
143 | test("click event is triggered when Card is clicked", async ({
144 | initTestBed,
145 | createCardDriver,
146 | }) => {
147 | const { testStateDriver } = await initTestBed(`<Card onClick="testState = true" />`);
148 | await (await createCardDriver()).click();
149 | await expect.poll(testStateDriver.testState).toEqual(true);
150 | });
151 |
152 | test("Card click does not interfere with link click", async ({
153 | page,
154 | initTestBed,
155 | createCardDriver,
156 | createTextDriver,
157 | }) => {
158 | await initTestBed(`
159 | <Card title="Title" linkTo="/test-link" onClick="testState = true">
160 | <Text testId="text-1" when="{testState}" value="visible" />
161 | </Card>
162 | `);
163 | const cardDriver = await createCardDriver();
164 | const textDriver = await createTextDriver("text-1");
165 |
166 | await cardDriver.click();
167 | await expect(textDriver.component).toHaveText("visible");
168 | await expect(page).not.toHaveURL(/\/test-link$/);
169 | });
170 |
171 | test("Link click does not interfere with Card click", async ({
172 | page,
173 | initTestBed,
174 | createCardDriver,
175 | createTextDriver,
176 | }) => {
177 | const { testStateDriver } = await initTestBed(`
178 | <Card title="Title" linkTo="/test-link" onClick="testState = true">
179 | <Text testId="text-1" when="{testState}" value="visible" />
180 | </Card>
181 | `);
182 |
183 | const textDriver = await createTextDriver("text-1");
184 | const title = (await createCardDriver()).component.getByRole("heading");
185 |
186 | await expect(textDriver.component).not.toBeVisible();
187 | await title.click();
188 | await page.waitForTimeout(200);
189 | await expect(page).toHaveURL(/\/test-link$/);
190 | await expect(textDriver.component).toBeVisible();
191 | });
192 | });
193 |
```
--------------------------------------------------------------------------------
/packages/xmlui-website-blocks/src/HeroSection/HeroSection.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import styles from "./HeroSection.module.scss";
2 |
3 | import { createComponentRenderer, createMetadata, dComponent, d, parseScssVar } from "xmlui";
4 | import { HeroSection, defaultProps } from "./HeroSectionNative";
5 |
6 | const COMP = "HeroSection";
7 |
8 | export const HeroSectionMd = createMetadata({
9 | status: "experimental",
10 | description: "HeroSection",
11 | parts: {
12 | header: {
13 | description: "The header section containing all text content and CTA button",
14 | },
15 | content: {
16 | description: "The content section containing image and children",
17 | },
18 | headingSection: {
19 | description: "The heading section containing preamble, headline, and subheadline",
20 | },
21 | preamble: {
22 | description: "The preamble text for the hero section",
23 | },
24 | headline: {
25 | description: "The headline text for the hero section",
26 | },
27 | subheadline: {
28 | description: "The subheadline text for the hero section",
29 | },
30 | mainText: {
31 | description: "The main text content for the hero section",
32 | },
33 | ctaButton: {
34 | description: "The call-to-action button for the hero section",
35 | },
36 | image: {
37 | description: "The image for the hero section",
38 | },
39 | background: {
40 | description: "The background template area of the hero section",
41 | },
42 | },
43 | props: {
44 | headerAlignment: {
45 | description: "Alignment of the header content",
46 | type: "string",
47 | defaultValue: "center",
48 | options: ["start", "center", "end"],
49 | },
50 | contentPlacement: {
51 | description: "Position of the content area relative to the header",
52 | type: "string",
53 | defaultValue: defaultProps.contentPlacement,
54 | options: ["left", "right", "bottom"],
55 | },
56 | contentAlignment: {
57 | description: "Horizontal alignment of the content within its area",
58 | type: "string",
59 | defaultValue: defaultProps.contentAlignment,
60 | options: ["start", "center", "end"],
61 | },
62 | headerWidth: {
63 | description: "Width of the header section in horizontal layouts",
64 | type: "string",
65 | defaultValue: defaultProps.headerWidth,
66 | },
67 | contentWidth: {
68 | description: "Width of the hero content (header + content sections)",
69 | type: "string",
70 | defaultValue: defaultProps.contentWidth,
71 | },
72 | gap: {
73 | description: "Gap between header and content sections",
74 | type: "string",
75 | },
76 | preamble: {
77 | description: "The preamble text for the hero section",
78 | type: "string",
79 | },
80 | headline: {
81 | description: "The headline text for the hero section",
82 | type: "string",
83 | },
84 | subheadline: {
85 | description: "The subheadline text for the hero section",
86 | type: "string",
87 | },
88 | mainText: {
89 | description: "The main text content for the hero section",
90 | type: "string",
91 | },
92 | mainTextTemplate: dComponent("The template for the text content in the hero section"),
93 | ctaButtonIcon: {
94 | description: "The icon for the call-to-action button",
95 | type: "string",
96 | },
97 | ctaButtonText: {
98 | description: "The text for the call-to-action button",
99 | type: "string",
100 | },
101 | ctaButtonTemplate: dComponent("The template for the call-to-action button"),
102 | fullWidthBackground: {
103 | description: "Whether the background should span the full width of the viewport",
104 | type: "boolean",
105 | defaultValue: defaultProps.fullWidthBackground,
106 | },
107 | image: {
108 | description: "The image for the hero section",
109 | type: "string",
110 | },
111 | imageWidth: {
112 | description: "The width of the image",
113 | type: "string",
114 | },
115 | imageHeight: {
116 | description: "The height of the image",
117 | type: "string",
118 | },
119 | backgroundTemplate: dComponent("The template for the background of the hero section"),
120 | headerTone: {
121 | description: "The tone for the header section, affecting text colors",
122 | type: "string",
123 | options: ["light", "dark", "reverse"],
124 | defaultValue: "dark",
125 | },
126 | contentTone: {
127 | description: "The tone for the content section, affecting text colors",
128 | type: "string",
129 | options: ["light", "dark", "reverse"],
130 | defaultValue: "dark",
131 | },
132 | className: d("Additional CSS class names to apply to the hero section", undefined, "string"),
133 | },
134 | events: {
135 | ctaClick: d("Triggered when the call-to-action button is clicked"),
136 | },
137 | themeVars: parseScssVar(styles.themeVars),
138 | defaultThemeVars: {
139 | [`paddingTop-${COMP}`]: "$space-12",
140 | [`paddingBottom-${COMP}`]: "$space-12",
141 | [`paddingHorizontal-${COMP}`]: "$space-12",
142 | [`fontSize-headline-${COMP}`]: "3em",
143 | [`fontWeight-headline-${COMP}`]: "$fontWeight-bold",
144 | [`lineHeight-headline-${COMP}`]: "1.4em",
145 | [`gap-headline-${COMP}`]: "$space-8",
146 | [`fontSize-subheadline-${COMP}`]: "2em",
147 | [`lineHeight-subheadline-${COMP}`]: "1.1em",
148 | [`fontWeight-subheadline-${COMP}`]: "$fontWeight-bold",
149 | [`gap-subheadline-${COMP}`]: "$space-4",
150 | [`fontSize-mainText-${COMP}`]: "1.4em",
151 | [`lineHeight-mainText-${COMP}`]: "1.1em",
152 | [`gap-mainText-${COMP}`]: "$space-4",
153 | [`textColor-preamble-${COMP}`]: "$textColor-primary",
154 | [`textColor-headline-${COMP}`]: "$textColor-primary",
155 | [`textColor-subheadline-${COMP}`]: "$textColor-primary",
156 | [`textColor-mainText-${COMP}`]: "$textColor-primary",
157 | },
158 | });
159 |
160 | export const heroSectionComponentRenderer = createComponentRenderer(
161 | COMP,
162 | HeroSectionMd,
163 | ({ node, extractValue, renderChild, lookupEventHandler, className }) => {
164 | const props = (node.props as typeof HeroSectionMd.props)!;
165 | return (
166 | <HeroSection
167 | headerAlignment={extractValue(props.headerAlignment)}
168 | contentPlacement={extractValue(props.contentPlacement)}
169 | contentAlignment={extractValue(props.contentAlignment)}
170 | headerWidth={extractValue(props.headerWidth)}
171 | contentWidth={extractValue(props.contentWidth)}
172 | headerTone={extractValue(props.headerTone)}
173 | contentTone={extractValue(props.contentTone)}
174 | gap={extractValue(props.gap)}
175 | preamble={extractValue(props.preamble)}
176 | headline={extractValue(props.headline)}
177 | subheadline={extractValue(props.subheadline)}
178 | mainText={extractValue(props.mainText)}
179 | mainTextTemplate={renderChild(props.mainTextTemplate as any)}
180 | ctaButtonIcon={extractValue(props.ctaButtonIcon)}
181 | ctaButtonText={extractValue(props.ctaButtonText)}
182 | ctaButtonTemplate={renderChild(props.ctaButtonTemplate as any)}
183 | image={extractValue(props.image)}
184 | imageWidth={extractValue(props.imageWidth)}
185 | imageHeight={extractValue(props.imageHeight)}
186 | fullWidthBackground={extractValue.asOptionalBoolean(props.fullWidthBackground)}
187 | className={extractValue(className)}
188 | onCtaClick={lookupEventHandler("ctaClick")}
189 | backgroundTemplate={renderChild(props.backgroundTemplate as any)}
190 | >
191 | {renderChild(node.children)}
192 | </HeroSection>
193 | );
194 | },
195 | );
196 |
```
--------------------------------------------------------------------------------
/xmlui/src/components/NestedApp/AppWithCodeViewNative.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import { type ReactNode, useCallback, useRef, useState } from "react";
2 | import { IndexAwareNestedApp } from "./NestedAppNative";
3 | import { Markdown } from "../Markdown/Markdown";
4 | import type { ThemeTone } from "../../abstractions/ThemingDefs";
5 | import { Button } from "../Button/ButtonNative";
6 | import styles from "./NestedApp.module.scss";
7 | import { Tooltip } from "./Tooltip";
8 | import { RxOpenInNewWindow } from "react-icons/rx";
9 | import { LiaUndoAltSolid } from "react-icons/lia";
10 | import { createQueryString, withoutTrailingSlash } from "./utils";
11 | import { useAppContext } from "../../components-core/AppContext";
12 | import classnames from "classnames";
13 | import Logo from "./logo.svg?react";
14 | import { useTheme } from "../../components-core/theming/ThemeContext";
15 |
16 | type AppWithCodeViewNativeProps = {
17 | // Markdown content to display in the left column
18 | markdown: string;
19 | // Display layout in side-by-side mode (horizontal) when true,
20 | // or stacked (vertical) when false or undefined
21 | splitView?: boolean;
22 | // Indicates that the split view should initially show the code
23 | initiallyShowCode?: boolean;
24 | // Optional URL for the playground pop-out
25 | popOutUrl?: string;
26 | api?: any;
27 | app: string;
28 | components?: any[];
29 | config?: any;
30 | activeTone?: ThemeTone;
31 | activeTheme?: string;
32 | title?: string;
33 | height?: string | number;
34 | allowPlaygroundPopup?: boolean;
35 | withFrame?: boolean;
36 | noHeader?: boolean;
37 | immediate?: boolean;
38 | withSplashScreen?: boolean;
39 | closeButton?: ReactNode;
40 | controlsWidth?: string | number;
41 | };
42 |
43 | /**
44 | * A component that displays markdown content on the left and a NestedApp on the right
45 | */
46 | export function AppWithCodeViewNative({
47 | markdown,
48 | splitView,
49 | withFrame = true,
50 | noHeader = false,
51 | initiallyShowCode = false,
52 | popOutUrl,
53 | app,
54 | api,
55 | components = [],
56 | config,
57 | activeTone,
58 | activeTheme,
59 | title,
60 | height,
61 | allowPlaygroundPopup,
62 | withSplashScreen,
63 | immediate,
64 | closeButton = null,
65 | controlsWidth,
66 | }: AppWithCodeViewNativeProps): ReactNode {
67 | const [showCode, setShowCode] = useState(initiallyShowCode);
68 | const appContext = useAppContext();
69 | const [refreshVersion, setRefreshVersion] = useState(0);
70 | const { activeTheme: currentTheme, activeThemeTone, activeThemeId } = useTheme();
71 |
72 | const safePopOutUrl = withoutTrailingSlash(
73 | popOutUrl || appContext?.appGlobals?.popOutUrl || "https://playground.xmlui.org/#/playground",
74 | );
75 | const openPlayground = useCallback(async () => {
76 | const data = {
77 | standalone: {
78 | app,
79 | components,
80 | config: {
81 | name: title,
82 | themes: [currentTheme],
83 | defaultTheme: activeTheme,
84 | },
85 | api: api,
86 | },
87 | options: {
88 | fixedTheme: false,
89 | swapped: false,
90 | previewMode: false,
91 | orientation: "horizontal",
92 | activeTheme: activeThemeId || activeTheme,
93 | activeTone: activeTone || activeThemeTone,
94 | content: "app",
95 | },
96 | };
97 | const appQueryString = await createQueryString(JSON.stringify(data));
98 | window.open(`${safePopOutUrl}/#${appQueryString}`, "_blank");
99 | }, [app, components, title, activeTheme, api, activeTone, safePopOutUrl]);
100 |
101 | if (withFrame) {
102 | return (
103 | <>
104 | {!!markdown && !splitView && <Markdown>{markdown}</Markdown>}
105 | <div className={styles.nestedAppContainer} style={{ height }}>
106 | {!noHeader && (
107 | <div className={styles.header}>
108 | {!splitView && <span className={styles.headerText}>{title}</span>}
109 | {splitView && (
110 | <>
111 | <div className={styles.wrapper} style={{ width: controlsWidth }}>
112 | <Logo className={styles.logo} />
113 | </div>
114 | <div className={styles.viewControls}>
115 | <Button
116 | onClick={() => setShowCode(true)}
117 | className={classnames(styles.splitViewButton, {
118 | [styles.show]: showCode,
119 | [styles.hide]: !showCode,
120 | })}
121 | >
122 | XML
123 | </Button>
124 | <Button
125 | onClick={() => setShowCode(false)}
126 | className={classnames(styles.splitViewButton, {
127 | [styles.show]: !showCode,
128 | [styles.hide]: showCode,
129 | })}
130 | >
131 | UI
132 | </Button>
133 | </div>
134 | </>
135 | )}
136 | <div className={styles.wrapper} style={{ width: controlsWidth }}>
137 | {allowPlaygroundPopup && (
138 | <Tooltip
139 | trigger={
140 | <button
141 | className={styles.headerButton}
142 | onClick={() => {
143 | void openPlayground();
144 | }}
145 | >
146 | <RxOpenInNewWindow />
147 | </button>
148 | }
149 | label="View and edit in new full-width window"
150 | />
151 | )}
152 | <Tooltip
153 | trigger={
154 | <button
155 | className={styles.headerButton}
156 | onClick={() => {
157 | setShowCode(false);
158 | setRefreshVersion(refreshVersion + 1);
159 | }}
160 | >
161 | <LiaUndoAltSolid />
162 | </button>
163 | }
164 | label="Reset the app"
165 | />
166 | {closeButton}
167 | </div>
168 | </div>
169 | )}
170 | <div className={styles.contentContainer}>
171 | <Markdown
172 | className={classnames(styles.splitViewMarkdown, { [styles.hidden]: !showCode })}
173 | >
174 | {markdown}
175 | </Markdown>
176 | <IndexAwareNestedApp
177 | className={classnames({ [styles.hidden]: showCode })}
178 | height="100%"
179 | app={app}
180 | api={api}
181 | components={components}
182 | config={config}
183 | activeTone={activeTone}
184 | activeTheme={activeTheme}
185 | refreshVersion={refreshVersion}
186 | withSplashScreen={withSplashScreen}
187 | immediate={immediate}
188 | />
189 | </div>
190 | </div>
191 | </>
192 | );
193 | }
194 | return (
195 | <>
196 | {!!markdown && <Markdown>{markdown}</Markdown>}
197 | <IndexAwareNestedApp
198 | height={height}
199 | app={app}
200 | api={api}
201 | components={components}
202 | config={config}
203 | activeTone={activeTone}
204 | activeTheme={activeTheme}
205 | refreshVersion={refreshVersion}
206 | withSplashScreen={withSplashScreen}
207 | immediate={immediate}
208 | />
209 | </>
210 | );
211 | }
212 |
213 | /**
214 | * Export a named default for easier imports
215 | */
216 | export default AppWithCodeViewNative;
217 |
```
--------------------------------------------------------------------------------
/packages/xmlui-website-blocks/src/FancyButton/FancyButton.module.scss:
--------------------------------------------------------------------------------
```scss
1 | // FancyButton.module.scss
2 | @use "../../../../xmlui/src/components-core/theming/themes" as t;
3 |
4 | // --- This code snippet is required to collect the theme variables used in this module
5 | $themeVars: ();
6 | @function createThemeVar($componentVariable) {
7 | $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global;
8 | @return t.getThemeVar($themeVars, $componentVariable);
9 | }
10 |
11 | // Define theme variables
12 | $component: "FancyButton";
13 | $fontSize-FancyButton: createThemeVar("fontSize-#{$component}");
14 | $fontWeight-FancyButton: createThemeVar("fontWeight-#{$component}");
15 | $textColor-FancyButton: createThemeVar("textColor-#{$component}");
16 | $gap-FancyButton: createThemeVar("gap-#{$component}");
17 | $borderStyle-FancyButton: createThemeVar("borderStyle-#{$component}");
18 | $borderWidth-FancyButton: createThemeVar("borderWidth-#{$component}");
19 | $outlineColor-FancyButton--focus: createThemeVar("outlineColor-#{$component}--focus");
20 | $outlineWidth-FancyButton--focus: createThemeVar("outlineWidth-#{$component}--focus");
21 | $outlineStyle-FancyButton--focus: createThemeVar("outlineStyle-#{$component}--focus");
22 | $outlineOffset-FancyButton--focus: createThemeVar("outlineOffset-#{$component}--focus");
23 |
24 | // Disabled state variables
25 | $backgroundColor-FancyButton--disabled: createThemeVar("backgroundColor-#{$component}--disabled");
26 | $borderColor-FancyButton--disabled: createThemeVar("borderColor-#{$component}--disabled");
27 | $textColor-FancyButton--disabled: createThemeVar("textColor-#{$component}--disabled");
28 |
29 | @mixin sizeVariant($size) {
30 | padding: createThemeVar("paddingVertical-#{$component}-#{$size}")
31 | createThemeVar("paddingHorizontal-#{$component}-#{$size}");
32 | font-size: createThemeVar("fontSize-#{$component}-#{$size}");
33 | gap: createThemeVar("gap-#{$component}-#{$size}");
34 | }
35 |
36 | @mixin themeVariant($variant) {
37 | border-radius: createThemeVar("borderRadius-#{$component}-#{$variant}");
38 | background-color: createThemeVar("backgroundColor-#{$component}-#{$variant}");
39 | color: createThemeVar("textColor-#{$component}-#{$variant}");
40 | border: createThemeVar("border-#{$component}-#{$variant}");
41 | border-color: createThemeVar("borderColor-#{$component}-#{$variant}");
42 | border-style: createThemeVar("borderStyle-#{$component}-#{$variant}");
43 | border-width: createThemeVar("borderWidth-#{$component}-#{$variant}");
44 |
45 | &.xs {
46 | border-radius: createThemeVar("borderRadius-#{$component}-#{$variant}-xs");
47 | border: createThemeVar("border-#{$component}-#{$variant}-xs");
48 | border-color: createThemeVar("borderColor-#{$component}-#{$variant}-xs");
49 | border-style: createThemeVar("borderStyle-#{$component}-#{$variant}-xs");
50 | border-width: createThemeVar("borderWidth-#{$component}-#{$variant}-xs");
51 | }
52 |
53 | &.sm {
54 | border-radius: createThemeVar("borderRadius-#{$component}-#{$variant}-sm");
55 | border: createThemeVar("border-#{$component}-#{$variant}-sm");
56 | border-color: createThemeVar("borderColor-#{$component}-#{$variant}-sm");
57 | border-style: createThemeVar("borderStyle-#{$component}-#{$variant}-sm");
58 | border-width: createThemeVar("borderWidth-#{$component}-#{$variant}-sm");
59 | }
60 |
61 | &.md {
62 | border-radius: createThemeVar("borderRadius-#{$component}-#{$variant}-md");
63 | border: createThemeVar("border-#{$component}-#{$variant}-md");
64 | border-color: createThemeVar("borderColor-#{$component}-#{$variant}-md");
65 | border-style: createThemeVar("borderStyle-#{$component}-#{$variant}-md");
66 | border-width: createThemeVar("borderWidth-#{$component}-#{$variant}-md");
67 | }
68 |
69 | &.lg {
70 | border-radius: createThemeVar("borderRadius-#{$component}-#{$variant}-lg");
71 | border: createThemeVar("border-#{$component}-#{$variant}-lg");
72 | border-color: createThemeVar("borderColor-#{$component}-#{$variant}-lg");
73 | border-style: createThemeVar("borderStyle-#{$component}-#{$variant}-lg");
74 | border-width: createThemeVar("borderWidth-#{$component}-#{$variant}-lg");
75 | }
76 |
77 | &.xl {
78 | border-radius: createThemeVar("borderRadius-#{$component}-#{$variant}-xl");
79 | border: createThemeVar("border-#{$component}-#{$variant}-xl");
80 | border-color: createThemeVar("borderColor-#{$component}-#{$variant}-xl");
81 | border-style: createThemeVar("borderStyle-#{$component}-#{$variant}-xl");
82 | border-width: createThemeVar("borderWidth-#{$component}-#{$variant}-xl");
83 | }
84 |
85 | &:hover:not(.fancyButton--disabled) {
86 | background-color: createThemeVar("backgroundColor-#{$component}-#{$variant}--hover");
87 | border-color: createThemeVar("borderColor-#{$component}-#{$variant}--hover");
88 | }
89 |
90 | &:active:not(.fancyButton--disabled) {
91 | background-color: createThemeVar("backgroundColor-#{$component}-#{$variant}--active");
92 | }
93 | }
94 |
95 | // --- This part defines the CSS styles
96 |
97 | @layer components {
98 | .fancyButton {
99 | display: inline-flex;
100 | flex-direction: row;
101 | align-items: center;
102 | justify-content: center;
103 | gap: $gap-FancyButton;
104 | cursor: pointer;
105 | transition: all 0.2s ease-in-out;
106 | border: none;
107 | outline: none;
108 | font-family: inherit;
109 | text-decoration: none;
110 | user-select: none;
111 | position: relative;
112 |
113 | width: fit-content;
114 | height: fit-content;
115 | font-size: $fontSize-FancyButton;
116 | font-weight: $fontWeight-FancyButton;
117 | color: $textColor-FancyButton;
118 | border-style: $borderStyle-FancyButton;
119 | border-width: $borderWidth-FancyButton;
120 |
121 | &:focus-visible {
122 | outline: $outlineStyle-FancyButton--focus $outlineWidth-FancyButton--focus
123 | $outlineColor-FancyButton--focus;
124 | outline-offset: $outlineOffset-FancyButton--focus;
125 | }
126 |
127 | // Size variations
128 | &.xs {
129 | @include sizeVariant("xs");
130 | }
131 |
132 | &.sm {
133 | @include sizeVariant("sm");
134 | }
135 |
136 | &.md {
137 | @include sizeVariant("md");
138 | }
139 |
140 | &.lg {
141 | @include sizeVariant("lg");
142 | }
143 |
144 | &.xl {
145 | @include sizeVariant("xl");
146 | }
147 |
148 | // Rounded variant
149 | &.rounded {
150 | @include themeVariant("rounded");
151 | }
152 |
153 | // Square variant
154 | &.square {
155 | @include themeVariant("square");
156 | }
157 |
158 | // Pill variant
159 | &.pill {
160 | @include themeVariant("pill");
161 | }
162 |
163 | // Outlines pill variant
164 | &.outlinedPill {
165 | @include themeVariant("outlinedPill");
166 | }
167 |
168 | // Disabled state
169 | &.disabled {
170 | cursor: not-allowed;
171 | background-color: $backgroundColor-FancyButton--disabled !important;
172 | border-color: $borderColor-FancyButton--disabled !important;
173 | color: $textColor-FancyButton--disabled !important;
174 | }
175 |
176 | // Icon only styling
177 | &.iconOnly {
178 | aspect-ratio: 1;
179 | padding: 0.5rem;
180 | }
181 |
182 | // Content position
183 | &.contentPositionStart {
184 | justify-content: flex-start;
185 | }
186 |
187 | &.contentPositionCenter {
188 | justify-content: center;
189 | }
190 |
191 | &.contentPositionEnd {
192 | justify-content: flex-end;
193 | }
194 |
195 | // Icon position (when both icon and content exist)
196 | &.iconPositionEnd {
197 | flex-direction: row-reverse;
198 | }
199 | }
200 | }
201 |
202 | // --- We export the theme variables to add them to the component renderer
203 | :export {
204 | themeVars: t.json-stringify($themeVars);
205 | }
206 |
```
--------------------------------------------------------------------------------
/xmlui/src/components/Markdown/Markdown.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import styles from "./Markdown.module.scss";
2 |
3 | import { createComponentRenderer } from "../../components-core/renderers";
4 | import { parseScssVar } from "../../components-core/theming/themeVars";
5 | import { Markdown, defaultProps } from "./MarkdownNative";
6 | import type React from "react";
7 | import { forwardRef, useMemo } from "react";
8 | import type { ValueExtractor } from "../../abstractions/RendererDefs";
9 | import { parseBindingExpression } from "./parse-binding-expr";
10 | import type { CodeHighlighter } from "../CodeBlock/highlight-code";
11 | import {
12 | convertPlaygroundPatternToMarkdown,
13 | convertTreeDisplayToMarkdown,
14 | observePlaygroundPattern,
15 | observeTreeDisplay,
16 | } from "./utils";
17 | import { createMetadata, d } from "../metadata-helpers";
18 |
19 | const COMP = "Markdown";
20 |
21 | export const MarkdownMd = createMetadata({
22 | status: "stable",
23 | description:
24 | "`Markdown` renders formatted text using markdown syntax. Use " +
25 | "[Text](/working-with-text) for simple, styled text content, and `Markdown` " +
26 | "when you need [rich formatting](/working-with-markdown).",
27 | themeVars: parseScssVar(styles.themeVars),
28 | props: {
29 | content: d(
30 | "This property sets the markdown content to display. Alternatively, you can nest " +
31 | "the markdown content as a child in a CDATA section. In neither this property " +
32 | "value nor any child is defined, empty content is displayed.",
33 | ),
34 | codeHighlighter: {
35 | description: "This property sets the code highlighter to use.",
36 | isInternal: true,
37 | },
38 | removeIndents: {
39 | description:
40 | "This boolean property specifies whether leading indents should be " +
41 | "removed from the markdown content. If set to `true`, the shortest " +
42 | "indent found at the start of the content lines is removed from the " +
43 | "beginning of every line.",
44 | valueType: "boolean",
45 | defaultValue: defaultProps.removeIndents,
46 | },
47 | showHeadingAnchors: {
48 | description:
49 | "This boolean property specifies whether heading anchors should be " +
50 | "displayed. If set to `true`, heading anchors will be displayed on hover " +
51 | "next to headings.",
52 | valueType: "boolean",
53 | },
54 | },
55 |
56 | defaultThemeVars: {
57 | "backgroundColor-Admonition": "$color-primary-100",
58 | "border-Admonition": "1px solid $color-primary-300",
59 | "backgroundColor-Admonition-warning": "$color-warn-100",
60 | "borderColor-Admonition-warning": "$color-warn-300",
61 | "backgroundColor-Admonition-danger": "$color-danger-100",
62 | "borderColor-Admonition-danger": "$color-danger-300",
63 | "borderRadius-Admonition": "$space-2",
64 | "size-icon-Admonition": "$space-5",
65 | "paddingLeft-Admonition": "$space-2",
66 | "paddingRight-Admonition": "$space-6",
67 | "paddingTop-Admonition": "$space-3",
68 | "paddingBottom-Admonition": "$space-2",
69 | "marginLeft-Admonition-content": "$space-1_5",
70 | "marginTop-Admonition": "$space-7",
71 | "marginBottom-Admonition": "$space-7",
72 |
73 | "marginTop-Blockquote": "$space-7",
74 | "marginBottom-Blockquote": "$space-7",
75 | "paddingHorizontal-Blockquote": "$space-6",
76 | "paddingTop-Blockquote": "$space-3",
77 | "paddingBottom-Blockquote": "$space-2_5",
78 | "backgroundColor-Blockquote": "$color-surface-100",
79 | "width-accent-Blockquote": "3px",
80 | "color-accent-Blockquote": "$color-surface-500",
81 |
82 | "marginTop-HtmlLi": "$space-2_5",
83 | "marginBottom-HtmlLi": "$space-2_5",
84 |
85 | "marginTop-Image-markdown": "$space-4",
86 | "marginBottom-Image-markdown": "$space-4",
87 | "marginLeft-Image-markdown": "$space-0",
88 | "marginRight-Image-markdown": "$space-0",
89 |
90 | "marginTop-Text-markdown": "$space-3",
91 | "marginBottom-Text-markdown": "$space-6",
92 | "fontSize-Text-markdown": "fontSize-${COMP}",
93 | "fontWeight-Text-markdown": "fontWeight-Text",
94 |
95 | light: {
96 | // --- No light-specific theme vars
97 | },
98 | dark: {
99 | "backgroundColor-Blockquote": "$color-surface-50",
100 | "backgroundColor-Admonition": "$color-primary-200",
101 | },
102 | },
103 | });
104 |
105 | export const markdownComponentRenderer = createComponentRenderer(
106 | COMP,
107 | MarkdownMd,
108 | ({ node, extractValue, renderChild, className }) => {
109 | let renderedChildren = "";
110 |
111 | // 1. Static content prop fallback
112 | if (!renderedChildren) {
113 | renderedChildren = extractValue.asString(node.props.content);
114 | }
115 |
116 | // 2. "data" property fallback
117 | if (!renderedChildren && typeof (node.props as any).data === "string") {
118 | renderedChildren = extractValue.asString((node.props as any).data);
119 | }
120 |
121 | // 3. Children fallback
122 | if (!renderedChildren) {
123 | (node.children ?? []).forEach((child) => {
124 | const renderedChild = renderChild(child);
125 | if (typeof renderedChild === "string") {
126 | renderedChildren += renderedChild;
127 | }
128 | });
129 | }
130 |
131 | return (
132 | <TransformedMarkdown
133 | className={className}
134 | removeIndents={extractValue.asOptionalBoolean(node.props.removeIndents, true)}
135 | codeHighlighter={extractValue(node.props.codeHighlighter)}
136 | extractValue={extractValue}
137 | showHeadingAnchors={extractValue.asOptionalBoolean(node.props.showHeadingAnchors)}
138 | >
139 | {renderedChildren}
140 | </TransformedMarkdown>
141 | );
142 | },
143 | );
144 |
145 | type TransformedMarkdownProps = {
146 | children: React.ReactNode;
147 | removeIndents?: boolean;
148 | className?: string;
149 | extractValue: ValueExtractor;
150 | codeHighlighter?: CodeHighlighter;
151 | showHeadingAnchors?: boolean;
152 | };
153 |
154 | const TransformedMarkdown = forwardRef<HTMLDivElement, TransformedMarkdownProps>(
155 | (
156 | {
157 | children,
158 | removeIndents,
159 | className,
160 | extractValue,
161 | codeHighlighter,
162 | showHeadingAnchors,
163 | }: TransformedMarkdownProps,
164 | ref,
165 | ) => {
166 | const markdownContent = useMemo(() => {
167 | if (typeof children !== "string") {
168 | return null;
169 | }
170 |
171 | // --- Resolve binding expression values
172 | // --- Resolve xmlui playground definitions
173 |
174 | let resolvedMd = children;
175 | while (true) {
176 | const nextPlayground = observePlaygroundPattern(resolvedMd);
177 | if (!nextPlayground) break;
178 |
179 | resolvedMd =
180 | resolvedMd.slice(0, nextPlayground[0]) +
181 | convertPlaygroundPatternToMarkdown(nextPlayground[2]) +
182 | resolvedMd.slice(nextPlayground[1]);
183 | }
184 |
185 | while (true) {
186 | const nextTreeDisplay = observeTreeDisplay(resolvedMd);
187 | if (!nextTreeDisplay) break;
188 | resolvedMd =
189 | resolvedMd.slice(0, nextTreeDisplay[0]) +
190 | convertTreeDisplayToMarkdown(nextTreeDisplay[2]) +
191 | resolvedMd.slice(nextTreeDisplay[1]);
192 | }
193 |
194 | resolvedMd = parseBindingExpression(resolvedMd, extractValue);
195 | return resolvedMd;
196 | }, [children, extractValue]);
197 |
198 | return (
199 | <Markdown
200 | ref={ref}
201 | removeIndents={removeIndents}
202 | codeHighlighter={codeHighlighter}
203 | className={className}
204 | showHeadingAnchors={showHeadingAnchors}
205 | >
206 | {markdownContent}
207 | </Markdown>
208 | );
209 | },
210 | );
211 |
212 | export { Markdown } from "./MarkdownNative";
213 |
```
--------------------------------------------------------------------------------
/xmlui/tests/parsers/xmlui/transform.errors.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { describe, expect, it, assert } from "vitest";
2 | import { transformSource } from "./xmlui";
3 |
4 | describe("Xmlui transform - errors", () => {
5 | it("Missing name in compound component", () => {
6 | try {
7 | transformSource("<Component />");
8 | assert.fail("Exception expected");
9 | } catch (err) {
10 | expect(err.toString()).includes("T003");
11 | }
12 | });
13 |
14 | it("Invalid name in compound component #1", () => {
15 | try {
16 | transformSource("<Component name=''/>");
17 | assert.fail("Exception expected");
18 | } catch (err) {
19 | expect(err.toString()).includes("T004");
20 | }
21 | });
22 |
23 | it("Invalid name in compound component #2", () => {
24 | try {
25 | transformSource("<Component name='alma'/>");
26 | assert.fail("Exception expected");
27 | } catch (err) {
28 | expect(err.toString()).includes("T004");
29 | }
30 | });
31 |
32 | it("Compound child in compound component", () => {
33 | try {
34 | transformSource("<Component name='MyComp'><Component /></Component>");
35 | assert.fail("Exception expected");
36 | } catch (err) {
37 | expect(err.toString()).includes("T006");
38 | }
39 | });
40 |
41 | it("Invalid attribute in compound component", () => {
42 | try {
43 | transformSource("<Component name='MyComp' blabla='123'><Stack/></Component>");
44 | assert.fail("Exception expected");
45 | } catch (err) {
46 | expect(err.toString()).includes("T021");
47 | }
48 | });
49 |
50 | it("Event attribute starts with 'on'", () => {
51 | try {
52 | transformSource("<Stack><event name='onClick' /></Stack>");
53 | assert.fail("Exception expected");
54 | } catch (err) {
55 | expect(err.toString()).includes("T008");
56 | }
57 | });
58 |
59 | it("'uses' is invalid in a compound component", () => {
60 | try {
61 | transformSource("<Component name='MyComp'><Stack/><uses /></Component>");
62 | assert.fail("Exception expected");
63 | } catch (err) {
64 | expect(err.toString()).includes("T009");
65 | }
66 | });
67 |
68 | it("'loaders' is invalid in a compound component", () => {
69 | try {
70 | transformSource("<Component name='MyComp'><Stack/><loaders /></Component>");
71 | assert.fail("Exception expected");
72 | } catch (err) {
73 | expect(err.toString()).includes("T009");
74 | }
75 | });
76 |
77 | it("Invalid attribute in prop", () => {
78 | try {
79 | transformSource("<Stack><property name='my' blabal='123'/></Stack>");
80 | assert.fail("Exception expected");
81 | } catch (err) {
82 | expect(err.toString()).includes("T011");
83 | }
84 | });
85 |
86 | it("Invalid attribute in event", () => {
87 | try {
88 | transformSource("<Stack><event name='my' blabal='123'/></Stack>");
89 | assert.fail("Exception expected");
90 | } catch (err) {
91 | expect(err.toString()).includes("T011");
92 | }
93 | });
94 |
95 | it("Invalid attribute in var", () => {
96 | try {
97 | transformSource("<Stack><variable name='my' blabal='123'/></Stack>");
98 | assert.fail("Exception expected");
99 | } catch (err) {
100 | expect(err.toString()).includes("T011");
101 | }
102 | });
103 |
104 | it("Invalid attribute in api", () => {
105 | try {
106 | transformSource("<Stack><property name='my' blabal='123'/></Stack>");
107 | assert.fail("Exception expected");
108 | } catch (err) {
109 | expect(err.toString()).includes("T011");
110 | }
111 | });
112 |
113 | it("Name required in prop #1", () => {
114 | try {
115 | transformSource("<Stack><property /></Stack>");
116 | assert.fail("Exception expected");
117 | } catch (err) {
118 | expect(err.toString()).includes("T012");
119 | }
120 | });
121 |
122 | it("Name required in prop #2", () => {
123 | try {
124 | transformSource("<Stack><property name='' /></Stack>");
125 | assert.fail("Exception expected");
126 | } catch (err) {
127 | expect(err.toString()).includes("T012");
128 | }
129 | });
130 |
131 | it("Name required in event #1", () => {
132 | try {
133 | transformSource("<Stack><event /></Stack>");
134 | assert.fail("Exception expected");
135 | } catch (err) {
136 | expect(err.toString()).includes("T012");
137 | }
138 | });
139 |
140 | it("Name required in event #2", () => {
141 | try {
142 | transformSource("<Stack><event name='' /></Stack>");
143 | assert.fail("Exception expected");
144 | } catch (err) {
145 | expect(err.toString()).includes("T012");
146 | }
147 | });
148 |
149 | it("Name required in var #1", () => {
150 | try {
151 | transformSource("<Stack><variable/></Stack>");
152 | assert.fail("Exception expected");
153 | } catch (err) {
154 | expect(err.toString()).includes("T012");
155 | }
156 | });
157 |
158 | it("Name required in var #2", () => {
159 | try {
160 | transformSource("<Stack><variable name='' /></Stack>");
161 | assert.fail("Exception expected");
162 | } catch (err) {
163 | expect(err.toString()).includes("T012");
164 | }
165 | });
166 |
167 | it("Name required in method #1", () => {
168 | try {
169 | transformSource("<Stack><method /></Stack>");
170 | assert.fail("Exception expected");
171 | } catch (err) {
172 | expect(err.toString()).includes("T012");
173 | }
174 | });
175 |
176 | it("Name required in api #2", () => {
177 | try {
178 | transformSource("<Stack><method name='' /></Stack>");
179 | assert.fail("Exception expected");
180 | } catch (err) {
181 | expect(err.toString()).includes("T012");
182 | }
183 | });
184 |
185 | it("A 'uses' must have value #1", () => {
186 | try {
187 | transformSource("<Stack><uses value=''/></Stack>");
188 | assert.fail("Exception expected");
189 | } catch (err) {
190 | expect(err.toString()).includes("T015");
191 | }
192 | });
193 |
194 | it("A 'uses' must have value #2", () => {
195 | try {
196 | transformSource("<Stack><uses/></Stack>");
197 | assert.fail("Exception expected");
198 | } catch (err) {
199 | expect(err.toString()).includes("T015");
200 | }
201 | });
202 |
203 | it("A 'value' can have 'field' and 'item' child", () => {
204 | try {
205 | transformSource("<Stack><property name='my'><dummy /></property></Stack>");
206 | assert.fail("Exception expected");
207 | } catch (err) {
208 | expect(err.toString()).includes("T016");
209 | }
210 | });
211 |
212 | it("Cannot mix field and item children #1", () => {
213 | try {
214 | transformSource(
215 | "<Stack><property name='my'><field name='my' /><item value='3'/></property></Stack>",
216 | );
217 | assert.fail("Exception expected");
218 | } catch (err) {
219 | expect(err.toString()).includes("T017");
220 | }
221 | });
222 |
223 | it("Cannot mix field and item children #2", () => {
224 | try {
225 | transformSource(
226 | `<Stack>
227 | <property name='my'>
228 | <item value='3'/>
229 | <field name='my' />
230 | </property>
231 | </Stack>`,
232 | );
233 | assert.fail("Exception expected");
234 | } catch (err) {
235 | expect(err.toString()).includes("T017");
236 | }
237 | });
238 |
239 | it("Item cannot have a 'name' attribute", () => {
240 | try {
241 | transformSource("<Stack><property name='my'><item name='my' value='3'/></property></Stack>");
242 | assert.fail("Exception expected");
243 | } catch (err) {
244 | expect(err.toString()).includes("T018");
245 | }
246 | });
247 |
248 | it("throws script errors in script tag", () => {
249 | try {
250 | transformSource(`
251 | <App>
252 | <script>
253 | var a =;
254 | </script>
255 | <Text>Hello World!</Text>
256 | </App>
257 | `);
258 | assert.fail("Exception expected");
259 | } catch (err) {
260 | expect(err.toString()).include("W001");
261 | }
262 | });
263 | });
264 |
```
--------------------------------------------------------------------------------
/xmlui/src/abstractions/RendererDefs.ts:
--------------------------------------------------------------------------------
```typescript
1 | import type { CSSProperties, ForwardedRef, ReactNode, RefObject } from "react";
2 |
3 | import type { AppContextObject } from "./AppContextDefs";
4 | import type {
5 | ComponentDef,
6 | ComponentMetadata,
7 | CompoundComponentDef,
8 | DynamicChildComponentDef, ParentRenderContext,
9 | } from "./ComponentDefs";
10 | import type { ContainerState } from "./ContainerDefs";
11 | import type { LookupActionOptions, LookupAsyncFn, LookupSyncFn } from "./ActionDefs";
12 | import type { AsyncFunction } from "./FunctionDefs";
13 | import type {ComponentApi} from "../components-core/rendering/ContainerWrapper";
14 |
15 | // This interface defines the renderer context for the exposed components of the
16 | // XMLUI framework.
17 | export interface RendererContext<TMd extends ComponentMetadata = ComponentMetadata>
18 | extends ComponentRendererContextBase<TMd> {
19 | uid: symbol; // The unique identifier of the component instance
20 |
21 | updateState: UpdateStateFn; // A component invokes this function to change its internal state
22 |
23 | // Context variables (all keys starting with "$") available in the current container
24 | contextVars: Record<string, any>;
25 |
26 | // When a component wants to access a property value (which may contain a binding
27 | // expression to evaluate), it must use this property to get the current value.
28 | extractValue: ValueExtractor;
29 |
30 | // This function gets a physical resource URL according to the provided logical URL.
31 | extractResourceUrl: (url?: string) => string | undefined;
32 |
33 | // This function gets an async executable function that handles an event.
34 | lookupEventHandler: LookupEventHandlerFn<TMd>;
35 |
36 | registerComponentApi: RegisterComponentApiFn; // A component can register its APIs with this function
37 |
38 | lookupAction: LookupAsyncFn; // This function obtains an action by its name with the specified options
39 |
40 | // This function retrieves a sync function the component can use as a callback
41 | lookupSyncCallback: LookupSyncFn;
42 |
43 | className?: string;
44 | }
45 |
46 | export type UpdateStateFn = (componentState: any, options?: any) => void; // This function updates the state of a component.
47 |
48 | // This type represent the function that extracts the value from a component property
49 | export type ValueExtractor = {
50 | (expression?: any, strict?: boolean): any; // Get a value (any) from a component property
51 |
52 | asString(expression?: any): string; // Get a string value from an expression
53 |
54 | // Get an optional string value from an expression
55 | asOptionalString<T extends string>(expression?: any, defValue?: string): T | undefined;
56 |
57 | // Get an optional string value from an expression
58 | asOptionalStringArray(expression?: any): (string | undefined)[];
59 |
60 | asDisplayText(expression?: any): string; // Get a display string value from an expression
61 |
62 | asNumber(expression?: any): number; // Get a number value from an expression
63 |
64 | // Get an optional number value from an expression
65 | asOptionalNumber(expression?: any, defValue?: number): number | undefined;
66 |
67 | // Get a boolean value (JavaScript semantics) from an expression
68 | asBoolean(expression?: any): boolean;
69 |
70 | // Get an optional Boolean value from an expression
71 | asOptionalBoolean(expression?: any, defValue?: boolean): boolean | undefined;
72 |
73 | // Get a CSS size value from an expression
74 | asSize(expression?: any): string;
75 | };
76 |
77 | // This function retrieves an async function for a particular component's specified
78 | // event to be invoked as an event handler (`undefined` if the particular event
79 | // handler is not defined).
80 | export type LookupEventHandlerFn<TMd extends ComponentMetadata = ComponentMetadata> = (
81 | eventName: keyof NonNullable<TMd["events"]>,
82 | actionOptions?: LookupActionOptions,
83 | ) => AsyncFunction | undefined;
84 |
85 | // This type represents a function that registers all API endpoints of a particular component.
86 | export type RegisterComponentApiFn = (componentApi: ComponentApi) => void;
87 |
88 | // Function signature to render a particular child component (or set of child components)
89 | export type RenderChildFn<L extends ComponentDef = ComponentDef> = (
90 | children?:
91 | | ComponentDef
92 | | ComponentDef[]
93 | | DynamicChildComponentDef
94 | | DynamicChildComponentDef[]
95 | | string,
96 | layoutContext?: LayoutContext<L>,
97 | parentRenderContext?: ParentRenderContext,
98 | uidInfoRef?: RefObject<Record<string, any>>,
99 | ref?: ForwardedRef<any>,
100 | rest?: Record<string, any>
101 | ) => ReactNode | ReactNode[];
102 |
103 | // Each component is rendered in a particular layout context (for example, within a
104 | // stack). This type provides information about that context and the operations that
105 | // render children in it.
106 | export type LayoutContext<T extends ComponentDef = ComponentDef> = {
107 | type?: string; // The type of the layout context
108 |
109 | // This function allows the React representation of a particular child node to be
110 | // wrapped in whatever React components to accommodate the current layout context.
111 | // When the engine is about to render children in a particular layout context, it
112 | // checks the existence of this function. If declared, the engine invokes it.
113 | wrapChild?: (
114 | context: RendererContext<T>,
115 | renderedChild: ReactNode,
116 | metadata?: ComponentMetadata,
117 | ) => ReactNode;
118 |
119 | // Arbitrary props extending the layout context
120 | [key: string]: any;
121 | };
122 |
123 | export type NonCssLayoutProps = {
124 | horizontalAlignment?: string;
125 | verticalAlignment?: string;
126 | orientation?: string;
127 | };
128 |
129 | // This function renders a component definition into a React component
130 | export type ComponentRendererFn<T extends ComponentDef> = (
131 | context: RendererContext<T>,
132 | ) => ReactNode;
133 |
134 | // This function renders a component definition into a React component
135 | export type CompoundComponentRendererInfo = {
136 | compoundComponentDef: CompoundComponentDef;
137 | metadata?: ComponentMetadata;
138 | };
139 |
140 | // Components must be registered with a component registry so the engine can use them.
141 | // This type collects the information held by the registry.
142 | export type ComponentRendererDef<T extends ComponentDef = any> = {
143 | // The component's type identifier. In the markup, the component must use this name
144 | // to be recognized.
145 | type: string;
146 |
147 | // This function renders the component from its definition to its React representation.
148 | renderer: ComponentRendererFn<T>;
149 |
150 | // The metadata to use when rendering the component
151 | metadata?: ComponentMetadata;
152 | };
153 |
154 | // Rendering components (turning component definitions into their React node
155 | // representation) is a complicated process that requires information describing the
156 | // actual context. This interface defines the common properties of that context.
157 | export interface ComponentRendererContextBase<TMd extends ComponentMetadata = ComponentMetadata> {
158 | // The definition of the component to render
159 | node: ComponentDef<TMd>;
160 |
161 | // The state of the container in which the component is rendered
162 | state: ContainerState;
163 |
164 | // The application context the component (and its binding expressions) can use
165 | appContext?: AppContextObject;
166 |
167 | // The component can use this function to render its child components
168 | renderChild: RenderChildFn;
169 |
170 | // Information about the layout context in which the component is rendered
171 | layoutContext?: LayoutContext;
172 | }
173 |
```
--------------------------------------------------------------------------------
/xmlui/src/components-core/TableOfContentsContext.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import {
2 | createContext,
3 | useCallback,
4 | useContext,
5 | useEffect,
6 | useMemo,
7 | useRef,
8 | useState,
9 | } from "react";
10 | import { useIsomorphicLayoutEffect, useScrollEventHandler, useScrollParent } from "./utils/hooks";
11 | import { useNavigate } from "@remix-run/react";
12 | import { EMPTY_ARRAY, EMPTY_OBJECT } from "./constants";
13 | import { useAppContext } from "./AppContext";
14 |
15 |
16 | // --- Stores the information about a particular heading to be displayed in the TOC.
17 | type HeadingItem = {
18 | // --- The id of the heading.
19 | id: string;
20 |
21 | // --- Heading level
22 | level: number;
23 |
24 | // --- Heading thext to display in the TOC.
25 | text: string;
26 |
27 | // --- Reference to the anchor element.
28 | anchor: HTMLAnchorElement | null;
29 | };
30 |
31 | type ActiveAnchorChangedCallback = (id: string) => void;
32 |
33 | // --- The context object that is used to store the hierarchy of headings.
34 | interface ITableOfContentsContext {
35 | // --- The list of headings in the TOC
36 | headings: HeadingItem[];
37 |
38 | // --- This method allows adding a new heading to the TOC.
39 | registerHeading: (headingItem: HeadingItem) => void;
40 |
41 | // --- This flag indicates whether the intersection observer is enabled.
42 | hasTableOfContents: boolean;
43 |
44 | // --- This method allows setting the id of the active anchor.
45 | scrollToAnchor: (id: string, smoothScrolling: boolean) => void;
46 |
47 | subscribeToActiveAnchorChange: (callback: ActiveAnchorChangedCallback) => () => void;
48 | activeAnchorId: string;
49 | }
50 |
51 | /**
52 | * Several components work together to represent the hierarchy of a particular
53 | * app page as a TOC. This React component provides a context for storing this
54 | * hierarchy information.
55 | */
56 | export const TableOfContentsContext = createContext<ITableOfContentsContext | null>(null);
57 |
58 | /**
59 | * This provider component injects the specified children into the TOC context.
60 | */
61 | export function TableOfContentsProvider({ children }: { children: React.ReactNode }) {
62 | const [headings, setHeadings] = useState<Record<string, HeadingItem>>(EMPTY_OBJECT);
63 | const [callbacks, setCallbacks] = useState<Array<ActiveAnchorChangedCallback>>(EMPTY_ARRAY);
64 | const observer = useRef<IntersectionObserver | null>(null);
65 | const {forceRefreshAnchorScroll} = useAppContext();
66 | const thisRef = useRef({
67 | suspendPositionBasedSetActiveId: false,
68 | });
69 | const scrollParent = useScrollParent(Object.values(headings)?.[0]?.anchor);
70 | useScrollEventHandler(scrollParent, {
71 | onScrollEnd: useCallback(() => {
72 | thisRef.current.suspendPositionBasedSetActiveId = false;
73 | }, []),
74 | });
75 | const [activeAnchorId, setActiveAnchorId] = useState(null);
76 |
77 | const notify = useCallback(
78 | (id) => {
79 | callbacks.forEach((cb) => cb(id));
80 | setActiveAnchorId(id);
81 | },
82 | [callbacks],
83 | );
84 |
85 | useEffect(() => {
86 | if (callbacks.length) {
87 | const handleObserver = (entries: any) => {
88 | entries.forEach((entry: any) => {
89 | if (entry?.isIntersecting) {
90 | if (!thisRef.current.suspendPositionBasedSetActiveId) {
91 | notify(entry.target.id);
92 | }
93 | }
94 | });
95 | };
96 |
97 | // stolen from nextra: https://github.com/shuding/nextra/blob/3729f67059f1fbdd3f98125bebabbe568c918694/packages/nextra-theme-docs/src/mdx-components/heading-anchor.client.tsx
98 | // let headerHeight = getComputedStyle(scrollParent || document.body).getPropertyValue(
99 | // '--header-abs-height'
100 | // );
101 | observer.current = new IntersectionObserver(handleObserver, {
102 | rootMargin: `0% 0% -80%`,
103 | // root: scrollParent,
104 | // threshold: [0, 1],
105 | });
106 |
107 | Object.values(headings).forEach((elem) => observer?.current?.observe?.(elem.anchor!));
108 | return () => observer.current?.disconnect();
109 | }
110 | }, [callbacks.length, headings, notify, scrollParent]);
111 |
112 | const registerHeading = useCallback((headingItem: HeadingItem) => {
113 | setHeadings((prevHeadings) => {
114 | return {
115 | ...prevHeadings,
116 | [headingItem.id]: headingItem,
117 | };
118 | });
119 |
120 | return () => {
121 | setHeadings((prevHeadings) => {
122 | const newHeadings = { ...prevHeadings };
123 | delete newHeadings[headingItem.id];
124 | return newHeadings;
125 | });
126 | };
127 | }, []);
128 |
129 | const navigate = useNavigate();
130 |
131 | const scrollToAnchor = useCallback(
132 | (id: string, smoothScrolling: boolean) => {
133 | const value = headings[id];
134 | if (value) {
135 | thisRef.current.suspendPositionBasedSetActiveId = true;
136 | value.anchor.scrollIntoView({
137 | block: "start",
138 | inline: "start",
139 | behavior: smoothScrolling ? "smooth" : "auto",
140 | });
141 | notify(id);
142 | requestAnimationFrame(() => {
143 | navigate(
144 | {
145 | hash: `#${value.id}`,
146 | },
147 | {
148 | state: {
149 | preventHashScroll: true,
150 | },
151 | },
152 | );
153 | //we clear the preventHashScroll route state: https://stackoverflow.com/questions/72121228/how-to-update-location-state-in-react-router-v6
154 | requestAnimationFrame(()=>{
155 | navigate({
156 | hash: `#${value.id}`,
157 | }, { replace: true });
158 | })
159 | });
160 | }
161 | },
162 | [headings, navigate, notify],
163 | );
164 |
165 | const sortedHeadings = useMemo(() => {
166 | return Object.values(headings).sort(function (a, b) {
167 | if (a.anchor === b.anchor) return 0;
168 | if (a.anchor.compareDocumentPosition(b.anchor) & Node.DOCUMENT_POSITION_PRECEDING) {
169 | // b comes before a
170 | return 1;
171 | }
172 | return -1;
173 | });
174 | }, [headings]);
175 |
176 |
177 | //the content could take time to load, this way we try to force the scroll to anchor mechanism to kick in
178 | const hasHeadings = sortedHeadings.length > 0;
179 | useIsomorphicLayoutEffect(()=>{
180 | if(hasHeadings){
181 | forceRefreshAnchorScroll();
182 | }
183 | }, [forceRefreshAnchorScroll, hasHeadings]);
184 |
185 | const subscribeToActiveAnchorChange = useCallback((cb: ActiveAnchorChangedCallback) => {
186 | setCallbacks((prev) => {
187 | return [...prev, cb];
188 | });
189 | return () => {
190 | setCallbacks((prev) => {
191 | return prev.filter((item) => item !== cb);
192 | });
193 | };
194 | }, []);
195 |
196 | const contextValue: ITableOfContentsContext = useMemo(() => {
197 | return {
198 | registerHeading,
199 | headings: sortedHeadings,
200 | scrollToAnchor,
201 | subscribeToActiveAnchorChange,
202 | hasTableOfContents: callbacks.length > 0,
203 | activeAnchorId
204 | };
205 | }, [
206 | registerHeading,
207 | sortedHeadings,
208 | scrollToAnchor,
209 | subscribeToActiveAnchorChange,
210 | callbacks.length,
211 | activeAnchorId,
212 | ]);
213 |
214 | return (
215 | <TableOfContentsContext.Provider value={contextValue}>
216 | {children}
217 | </TableOfContentsContext.Provider>
218 | );
219 | }
220 |
221 | export function useTableOfContents() {
222 | const context = useContext(TableOfContentsContext);
223 |
224 | if (!context) {
225 | throw new Error(`The TableOfContents component can only be used inside a Page component.
226 | <App>
227 | <Pages>
228 | <Page url="/">
229 | <Heading>Harry Potter and the Sorcerer's Stone</Heading>
230 | <TableOfContents />
231 | </Page>
232 | </Pages>
233 | </App>
234 |
235 | `);
236 | }
237 |
238 | return context;
239 | }
240 |
```
--------------------------------------------------------------------------------
/xmlui/src/components/NumberBox/NumberBox.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import styles from "./NumberBox.module.scss";
2 |
3 | import { createComponentRenderer } from "../../components-core/renderers";
4 | import { parseScssVar } from "../../components-core/theming/themeVars";
5 | import {
6 | createMetadata,
7 | d,
8 | dAutoFocus,
9 | dDidChange,
10 | dEnabled,
11 | dEndIcon,
12 | dEndText,
13 | dGotFocus,
14 | dInitialValue,
15 | dLostFocus,
16 | dMaxLength,
17 | dPlaceholder,
18 | dReadonly,
19 | dRequired,
20 | dStartIcon,
21 | dStartText,
22 | dValidationStatus,
23 | } from "../metadata-helpers";
24 | import { defaultProps, NumberBox } from "./NumberBoxNative";
25 |
26 | const COMP = "NumberBox";
27 |
28 | export const NumberBoxMd = createMetadata({
29 | status: "stable",
30 | description:
31 | "`NumberBox` provides a specialized input field for numeric values with built-in " +
32 | "validation, spinner buttons, and flexible formatting options. It supports both " +
33 | "integer and floating-point numbers, handles empty states as null values, and " +
34 | "integrates seamlessly with form validation.",
35 | parts: {
36 | label: {
37 | description: "The label displayed for the text box.",
38 | },
39 | startAdornment: {
40 | description: "The adornment displayed at the start of the text box.",
41 | },
42 | endAdornment: {
43 | description: "The adornment displayed at the end of the text box.",
44 | },
45 | input: {
46 | description: "The text box input area.",
47 | },
48 | },
49 | props: {
50 | placeholder: dPlaceholder(),
51 | initialValue: dInitialValue(),
52 | maxLength: dMaxLength(),
53 | autoFocus: dAutoFocus(),
54 | required: dRequired(),
55 | readOnly: dReadonly(),
56 | enabled: dEnabled(),
57 | validationStatus: dValidationStatus(),
58 | startText: dStartText(),
59 | startIcon: dStartIcon(),
60 | endText: dEndText(),
61 | endIcon: dEndIcon(),
62 | gap: {
63 | description: "This property defines the gap between the adornments and the input area.",
64 | },
65 | hasSpinBox: {
66 | description: `This boolean prop shows (\`true\`) or hides (\`false\`) the spinner buttons for the input field.`,
67 | valueType: "boolean",
68 | defaultValue: defaultProps.hasSpinBox,
69 | },
70 | spinnerUpIcon: d(
71 | `Allows setting an alternate icon displayed in the ${COMP} spinner for incrementing values. You can change ` +
72 | `the default icon for all ${COMP} instances with the "icon.spinnerUp:NumberBox" declaration in the ` +
73 | `app configuration file.`,
74 | ),
75 | spinnerDownIcon: d(
76 | `Allows setting an alternate icon displayed in the ${COMP} spinner for decrementing values. You can change ` +
77 | `the default icon for all ${COMP} instances with the "icon.spinnerDown:NumberBox" declaration in the ` +
78 | `app configuration file.`,
79 | ),
80 | step: {
81 | description: `This prop governs how big the step when clicking on the spinner of the field.`,
82 | valueType: "number",
83 | defaultValue: defaultProps.step,
84 | },
85 | integersOnly: {
86 | description:
87 | `This boolean property signs whether the input field accepts integers only (\`true\`) ` +
88 | `or not (\`false\`).`,
89 | valueType: "boolean",
90 | defaultValue: defaultProps.integersOnly,
91 | },
92 | zeroOrPositive: {
93 | description:
94 | `This boolean property determines whether the input value can only be 0 or positive numbers ` +
95 | `(\`true\`) or also negative (\`false\`).`,
96 | valueType: "boolean",
97 | defaultValue: defaultProps.zeroOrPositive,
98 | },
99 | minValue: {
100 | description:
101 | "The minimum value the input field allows. Can be a float or an integer if " +
102 | "[\`integersOnly\`](#integersonly) is set to \`false\`, otherwise it can only be an integer." +
103 | "If not set, no minimum value check is done.",
104 | defaultValue: defaultProps.min,
105 | },
106 | maxValue: {
107 | description:
108 | "The maximum value the input field allows. Can be a float or an integer if " +
109 | "[\`integersOnly\`](#integersonly) is set to \`false\`, otherwise it can only be an integer." +
110 | "If not set, no maximum value check is done.",
111 | defaultValue: defaultProps.max,
112 | },
113 | },
114 | events: {
115 | gotFocus: dGotFocus(COMP),
116 | lostFocus: dLostFocus(COMP),
117 | didChange: dDidChange(COMP),
118 | },
119 | apis: {
120 | focus: {
121 | description: `This API focuses the input field of the \`${COMP}\`. You can use it to programmatically focus the field.`,
122 | signature: "focus(): void",
123 | },
124 | value: {
125 | description: `This API retrieves the current value of the \`${COMP}\`. You can use it to get the value programmatically.`,
126 | signature: "get value(): number | undefined",
127 | },
128 | setValue: {
129 | description: `This API sets the value of the \`${COMP}\`. You can use it to programmatically change the value.`,
130 | signature: "setValue(value: number | undefined): void",
131 | },
132 | },
133 | themeVars: parseScssVar(styles.themeVars),
134 | defaultThemeVars: {
135 | [`paddingVertical-${COMP}`]: "$space-2",
136 | [`paddingHorizontal-${COMP}`]: "$space-2",
137 | },
138 | });
139 |
140 | export const numberBoxComponentRenderer = createComponentRenderer(
141 | COMP,
142 | NumberBoxMd,
143 | ({
144 | node,
145 | state,
146 | updateState,
147 | lookupEventHandler,
148 | extractValue,
149 | className,
150 | registerComponentApi,
151 | }) => {
152 | let extractedInitialValue;
153 | try {
154 | extractedInitialValue = extractValue.asOptionalNumber(node.props.initialValue);
155 | } catch {}
156 | return (
157 | <NumberBox
158 | className={className}
159 | value={state?.value}
160 | initialValue={extractedInitialValue}
161 | step={extractValue(node.props.step)}
162 | enabled={extractValue.asOptionalBoolean(node.props.enabled)}
163 | placeholder={extractValue.asOptionalString(node.props.placeholder)}
164 | validationStatus={extractValue(node.props.validationStatus)}
165 | updateState={updateState}
166 | onDidChange={lookupEventHandler("didChange")}
167 | onFocus={lookupEventHandler("gotFocus")}
168 | onBlur={lookupEventHandler("lostFocus")}
169 | registerComponentApi={registerComponentApi}
170 | hasSpinBox={extractValue.asOptionalBoolean(node.props.hasSpinBox)}
171 | integersOnly={extractValue.asOptionalBoolean(node.props.integersOnly)}
172 | zeroOrPositive={extractValue.asOptionalBoolean(node.props.zeroOrPositive)}
173 | min={extractValue.asOptionalNumber(node.props.minValue)}
174 | max={extractValue.asOptionalNumber(node.props.maxValue)}
175 | startText={extractValue.asOptionalString(node.props.startText)}
176 | startIcon={extractValue.asOptionalString(node.props.startIcon)}
177 | endText={extractValue.asOptionalString(node.props.endText)}
178 | gap={extractValue.asOptionalString(node.props.gap)}
179 | endIcon={extractValue.asOptionalString(node.props.endIcon)}
180 | spinnerUpIcon={extractValue.asOptionalString(node.props.spinnerUpIcon)}
181 | spinnerDownIcon={extractValue.asOptionalString(node.props.spinnerDownIcon)}
182 | autoFocus={extractValue.asOptionalBoolean(node.props.autoFocus)}
183 | readOnly={extractValue.asOptionalBoolean(node.props.readOnly)}
184 | maxLength={extractValue(node.props.maxLength)}
185 | required={extractValue.asOptionalBoolean(node.props.required)}
186 | direction={extractValue(node.props.direction)}
187 | />
188 | );
189 | },
190 | );
191 |
```
--------------------------------------------------------------------------------
/xmlui/src/components/TextBox/TextBoxNative.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import { type CSSProperties, type ForwardedRef, forwardRef, useId, useState } from "react";
2 | import React, { useCallback, useEffect, useRef } from "react";
3 | import classnames from "classnames";
4 |
5 | import styles from "./TextBox.module.scss";
6 |
7 | import type { RegisterComponentApiFn, UpdateStateFn } from "../../abstractions/RendererDefs";
8 | import { noop } from "../../components-core/constants";
9 | import { useEvent } from "../../components-core/utils/misc";
10 | import { Adornment } from "../Input/InputAdornment";
11 | import type { ValidationStatus } from "../abstractions";
12 | import { PART_START_ADORNMENT, PART_INPUT, PART_END_ADORNMENT } from "../../components-core/parts";
13 |
14 | /**
15 | * TextBox component that supports text input with various configurations.
16 | * Features:
17 | * - Standard text, password, and search input types
18 | * - Input validation states
19 | * - Start/end adornments (icons and text)
20 | * - Password visibility toggle option
21 | */
22 |
23 | type Props = {
24 | id?: string;
25 | type?: "text" | "password" | "search";
26 | value?: string;
27 | updateState?: UpdateStateFn;
28 | initialValue?: string;
29 | style?: CSSProperties;
30 | className?: string;
31 | maxLength?: number;
32 | enabled?: boolean;
33 | placeholder?: string;
34 | validationStatus?: ValidationStatus;
35 | onDidChange?: (newValue: string) => void;
36 | onFocus?: () => void;
37 | onBlur?: () => void;
38 | onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => void;
39 | registerComponentApi?: RegisterComponentApiFn;
40 | startText?: string;
41 | startIcon?: string;
42 | endText?: string;
43 | endIcon?: string;
44 | gap?: string;
45 | autoFocus?: boolean;
46 | readOnly?: boolean;
47 | tabIndex?: number;
48 | required?: boolean;
49 | /**
50 | * When true and type is "password", displays a toggle icon to show/hide password text
51 | * Default: false
52 | */
53 | showPasswordToggle?: boolean;
54 | /**
55 | * The icon to show when the password is visible
56 | * Default: "eye"
57 | */
58 | passwordVisibleIcon?: string;
59 | /**
60 | * The icon to show when the password is hidden
61 | * Default: "eye-off"
62 | */
63 | passwordHiddenIcon?: string;
64 | };
65 |
66 | export const defaultProps: Pick<
67 | Props,
68 | | "type"
69 | | "value"
70 | | "initialValue"
71 | | "enabled"
72 | | "validationStatus"
73 | | "onDidChange"
74 | | "onFocus"
75 | | "onBlur"
76 | | "onKeyDown"
77 | | "updateState"
78 | | "passwordVisibleIcon"
79 | | "passwordHiddenIcon"
80 | > = {
81 | type: "text",
82 | value: "",
83 | initialValue: "",
84 | enabled: true,
85 | validationStatus: "none",
86 | onDidChange: noop,
87 | onFocus: noop,
88 | onBlur: noop,
89 | onKeyDown: noop,
90 | updateState: noop,
91 | passwordVisibleIcon: "eye",
92 | passwordHiddenIcon: "eye-off",
93 | };
94 |
95 | export const TextBox = forwardRef(function TextBox(
96 | {
97 | id,
98 | type = defaultProps.type,
99 | value = defaultProps.value,
100 | updateState = defaultProps.updateState,
101 | initialValue = defaultProps.initialValue,
102 | style,
103 | className,
104 | maxLength,
105 | enabled = defaultProps.enabled,
106 | placeholder,
107 | validationStatus = defaultProps.validationStatus,
108 | onDidChange = defaultProps.onDidChange,
109 | onFocus = defaultProps.onFocus,
110 | onBlur = defaultProps.onBlur,
111 | onKeyDown = defaultProps.onKeyDown,
112 | registerComponentApi,
113 | startText,
114 | startIcon,
115 | endText,
116 | endIcon,
117 | gap,
118 | autoFocus,
119 | readOnly,
120 | tabIndex,
121 | required,
122 | showPasswordToggle,
123 | passwordVisibleIcon = defaultProps.passwordVisibleIcon,
124 | passwordHiddenIcon = defaultProps.passwordHiddenIcon,
125 | ...rest
126 | }: Props,
127 | ref: ForwardedRef<HTMLDivElement>,
128 | ) {
129 | const inputRef = useRef<HTMLInputElement>(null);
130 |
131 | // State to control password visibility
132 | const [showPassword, setShowPassword] = useState(false);
133 |
134 | // Determine the actual input type based on the password visibility toggle
135 | const actualType = type === "password" && showPassword ? "text" : type;
136 |
137 | // Toggle password visibility
138 | const togglePasswordVisibility = useCallback(() => {
139 | setShowPassword((prev) => !prev);
140 | }, []);
141 |
142 | useEffect(() => {
143 | if (autoFocus) {
144 | setTimeout(() => {
145 | inputRef.current?.focus();
146 | }, 0);
147 | }
148 | }, [autoFocus, inputRef]);
149 |
150 | // --- NOTE: This is a workaround for the jumping caret issue.
151 | // --- Local state can sync up values that can get set asynchronously outside the component.
152 | const [localValue, setLocalValue] = React.useState(value);
153 | useEffect(() => {
154 | setLocalValue(value);
155 | }, [value]);
156 | // --- End NOTE
157 |
158 | // --- Initialize the related field with the input's initial value
159 | useEffect(() => {
160 | updateState({ value: initialValue }, { initial: true });
161 | }, [initialValue, updateState]);
162 |
163 | const updateValue = useCallback(
164 | (value: string) => {
165 | setLocalValue(value);
166 | updateState({ value });
167 | onDidChange(value);
168 | },
169 | [onDidChange, updateState],
170 | );
171 |
172 | // --- Handle the value change events for this input
173 | const onInputChange = useCallback(
174 | (event: React.ChangeEvent<HTMLInputElement>) => {
175 | updateValue(event.target.value);
176 | },
177 | [updateValue],
178 | );
179 |
180 | // --- Manage obtaining and losing the focus
181 | const handleOnFocus = useCallback(() => {
182 | onFocus?.();
183 | }, [onFocus]);
184 |
185 | const handleOnBlur = useCallback(() => {
186 | onBlur?.();
187 | }, [onBlur]);
188 |
189 | const focus = useCallback(() => {
190 | inputRef.current?.focus();
191 | }, []);
192 |
193 | const setValue = useEvent((newValue) => {
194 | updateValue(newValue);
195 | });
196 |
197 | useEffect(() => {
198 | registerComponentApi?.({
199 | focus,
200 | setValue,
201 | });
202 | }, [focus, registerComponentApi, setValue]);
203 |
204 | return (
205 | <div
206 | {...rest}
207 | ref={ref}
208 | className={classnames(className, styles.inputRoot, {
209 | [styles.disabled]: !enabled,
210 | [styles.readOnly]: readOnly,
211 | [styles.error]: validationStatus === "error",
212 | [styles.warning]: validationStatus === "warning",
213 | [styles.valid]: validationStatus === "valid",
214 | })}
215 | tabIndex={-1}
216 | onFocus={focus}
217 | style={{ ...style, gap }}
218 | >
219 | <Adornment
220 | data-part-id={PART_START_ADORNMENT}
221 | text={startText}
222 | iconName={startIcon}
223 | className={classnames(styles.adornment)}
224 | />
225 | <input
226 | id={id}
227 | ref={inputRef}
228 | data-part-id={PART_INPUT}
229 | type={actualType}
230 | className={classnames(styles.input, {
231 | [styles.readOnly]: readOnly,
232 | })}
233 | disabled={!enabled}
234 | value={localValue}
235 | maxLength={maxLength}
236 | placeholder={placeholder}
237 | onChange={onInputChange}
238 | onFocus={handleOnFocus}
239 | onBlur={handleOnBlur}
240 | onKeyDown={onKeyDown}
241 | readOnly={readOnly}
242 | autoFocus={autoFocus}
243 | tabIndex={enabled ? tabIndex : -1}
244 | required={required}
245 | />
246 | {type === "password" && showPasswordToggle ? (
247 | <Adornment
248 | data-part-id={PART_END_ADORNMENT}
249 | iconName={showPassword ? passwordVisibleIcon : passwordHiddenIcon}
250 | className={classnames(styles.adornment, styles.passwordToggle)}
251 | onClick={togglePasswordVisibility}
252 | />
253 | ) : (
254 | <Adornment
255 | data-part-id={PART_END_ADORNMENT}
256 | text={endText}
257 | iconName={endIcon}
258 | className={styles.adornment}
259 | />
260 | )}
261 | </div>
262 | );
263 | });
264 |
```