This is page 43 of 143. Use http://codebase.md/xmlui-org/xmlui/xmlui/mockApiDef.js?lines=false&page={x} to view the full context.
# Directory Structure
```
├── .changeset
│ ├── config.json
│ └── cyan-tools-design.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.module.scss
│ │ │ ├── CodeSelector.tsx
│ │ │ ├── ConfirmationDialog.module.scss
│ │ │ ├── ConfirmationDialog.tsx
│ │ │ ├── Editor.tsx
│ │ │ ├── Header.module.scss
│ │ │ ├── Header.tsx
│ │ │ ├── Playground.tsx
│ │ │ ├── PlaygroundContent.module.scss
│ │ │ ├── PlaygroundContent.tsx
│ │ │ ├── PlaygroundNative.module.scss
│ │ │ ├── PlaygroundNative.tsx
│ │ │ ├── Preview.tsx
│ │ │ ├── StandalonePlayground.tsx
│ │ │ ├── StandalonePlaygroundNative.module.scss
│ │ │ ├── StandalonePlaygroundNative.tsx
│ │ │ ├── ThemeSwitcher.module.scss
│ │ │ ├── ThemeSwitcher.tsx
│ │ │ └── utils.ts
│ │ ├── providers
│ │ │ ├── Toast.module.scss
│ │ │ └── ToastProvider.tsx
│ │ ├── state
│ │ │ └── store.ts
│ │ ├── themes
│ │ │ └── theme.ts
│ │ └── utils
│ │ └── helpers.ts
│ ├── xmlui-search
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── index.tsx
│ │ ├── Search.module.scss
│ │ └── Search.tsx
│ ├── xmlui-spreadsheet
│ │ ├── .gitignore
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── index.tsx
│ │ ├── Spreadsheet.tsx
│ │ └── SpreadsheetNative.tsx
│ └── xmlui-website-blocks
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── demo
│ │ ├── components
│ │ │ ├── HeroBackgroundBreakoutPage.xmlui
│ │ │ ├── HeroBackgroundsPage.xmlui
│ │ │ ├── HeroContentsPage.xmlui
│ │ │ ├── HeroTextAlignPage.xmlui
│ │ │ ├── HeroTextPage.xmlui
│ │ │ └── HeroTonesPage.xmlui
│ │ ├── Main.xmlui
│ │ └── themes
│ │ └── default.ts
│ ├── index.html
│ ├── index.ts
│ ├── meta
│ │ └── componentsMetadata.ts
│ ├── package.json
│ ├── public
│ │ └── resources
│ │ ├── building.jpg
│ │ └── xmlui-logo.svg
│ └── src
│ ├── Carousel
│ │ ├── Carousel.module.scss
│ │ ├── Carousel.tsx
│ │ ├── CarouselContext.tsx
│ │ └── CarouselNative.tsx
│ ├── FancyButton
│ │ ├── FancyButton.module.scss
│ │ ├── FancyButton.tsx
│ │ └── FancyButton.xmlui
│ ├── Hello
│ │ ├── Hello.tsx
│ │ ├── Hello.xmlui
│ │ └── Hello.xmlui.xs
│ ├── HeroSection
│ │ ├── HeroSection.module.scss
│ │ ├── HeroSection.spec.ts
│ │ ├── HeroSection.tsx
│ │ └── HeroSectionNative.tsx
│ ├── index.tsx
│ ├── ScrollToTop
│ │ ├── ScrollToTop.module.scss
│ │ ├── ScrollToTop.tsx
│ │ └── ScrollToTopNative.tsx
│ └── vite-env.d.ts
├── 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/NavGroup/NavGroup.spec.ts:
--------------------------------------------------------------------------------
```typescript
import { expect, test } from "../../testing/fixtures";
test.describe("smoke tests", { tag: "@smoke" }, () => {
test("displays menuitems after click", async ({ initTestBed, page }) => {
await initTestBed(`
<NavGroup label="Pages">
<NavLink label="Page 1" />
<NavGroup label="subpages">
<NavLink label="inner page 2" />
<NavLink label="inner page 3" />
</NavGroup>
<NavLink label="Page 4" />
</NavGroup>
`);
await page.getByRole("button", { name: "Pages", exact: true }).click();
await expect(page.getByRole("menuitem")).toHaveCount(3);
await expect(page.getByRole("menuitem", { name: "Page 1" })).toBeVisible();
await expect(page.getByRole("menuitem", { name: "subpages" })).toBeVisible();
await expect(page.getByRole("menuitem", { name: "Page 4" })).toBeVisible();
await expect(page.getByRole("menuitem", { name: "inner page 2" })).not.toBeVisible();
await expect(page.getByRole("menuitem", { name: "inner page 3" })).not.toBeVisible();
});
test("disabled navgroup can't open", async ({ initTestBed, page }) => {
await initTestBed(`
<NavGroup label="Pages" enabled="false">
<NavLink label="Page 1" />
<NavGroup label="subpages">
<NavLink label="inner page 2" />
<NavLink label="inner page 3" />
</NavGroup>
<NavLink label="Page 4" />
</NavGroup>
`);
const pagesBtn = page.getByRole("button", { name: "Pages", exact: true });
await expect(pagesBtn).toBeDisabled();
await pagesBtn.click({ force: true });
await expect(page.getByRole("menuitem", { name: "Page 1" })).not.toBeVisible();
await expect(page.getByRole("menuitem", { name: "inner page 2" })).not.toBeVisible();
});
test("component trigger has correct aria labels", async ({ initTestBed, page }) => {
await initTestBed(`<NavGroup testId="navGroup" label="NavGroup"/>`);
const button = page.getByTestId("navGroup");
await expect(button).toBeVisible();
await expect(button).toHaveAttribute("aria-expanded", "false");
await button.click();
await expect(button).toHaveAttribute("aria-expanded", "true");
});
test("expanded in vertical layout to show link of current page", async ({
initTestBed,
page,
}) => {
await initTestBed(`
<App layout="vertical">
<NavPanel>
<NavGroup label="Current-upper">
<NavGroup label="Current">
<NavLink label="link-to-current-page" to="/" />
</NavGroup>
</NavGroup>
</NavPanel>
</App>`);
await expect(page.getByText("link-to-current-page")).toBeVisible();
});
});
test("nested disabled navgroup can't open", async ({ initTestBed, page }) => {
await initTestBed(`
<NavGroup label="Pages" >
<NavLink label="Page 1" />
<NavGroup label="subpages" enabled="false">
<NavLink label="inner page 2" />
<NavLink label="inner page 3" />
</NavGroup>
<NavLink label="Page 4" />
</NavGroup>
`);
const pagesBtn = page.getByRole("button", { name: "Pages", exact: true });
await pagesBtn.click();
await expect(page.getByRole("menuitem", { name: "Page 1" })).toBeVisible();
const subpagesBtn = page.getByRole("menuitem", { name: "subpages" });
await expect(subpagesBtn).toBeDisabled();
await subpagesBtn.click({ force: true });
await expect(page.getByRole("menuitem", { name: "inner page 2" })).not.toBeVisible();
});
test("initiallyExpanded works", async ({ initTestBed, page }) => {
await initTestBed(`
<NavGroup label="Pages" initiallyExpanded="true">
<NavLink label="Page 1" />
<NavGroup label="subpages">
<NavLink label="inner page 2" />
<NavLink label="inner page 3" />
</NavGroup>
<NavLink label="Page 4" />
</NavGroup>
`);
await expect(page.getByRole("menuitem", { name: "Page 1" })).toBeVisible();
await expect(page.getByRole("menuitem", { name: "inner page 2" })).not.toBeVisible();
});
test("nested initiallyExpanded works", async ({ initTestBed, page }) => {
await initTestBed(`
<Stack testId="stack">
<NavGroup label="Pages" initiallyExpanded="true">
<NavLink label="Page 1" />
<NavGroup label="subpages" initiallyExpanded="true">
<NavLink label="inner page 2" />
<NavLink label="inner page 3" />
</NavGroup>
<NavLink label="Page 4" />
</NavGroup>
</Stack>
`);
const stack = page.getByTestId("stack");
await expect(stack).toBeVisible();
const items = page.getByRole("menuitem");
await expect(items).toHaveCount(3);
expect(items.nth(0)).toHaveText("Page 1");
expect(items.nth(1)).toHaveText("subpages");
expect(items.nth(2)).toHaveText("Page 4");
});
test("expands even without label", async ({ initTestBed, page }) => {
await initTestBed(`
<NavGroup >
<NavLink label="Page 1" />
<NavGroup label="subpages">
<NavLink label="inner page 2" />
<NavLink label="inner page 3" />
</NavGroup>
<NavLink label="Page 4" />
</NavGroup>
`);
await expect(page.getByRole("menuitem", { name: "Page 1" })).not.toBeVisible();
await page.getByRole("button").click();
await expect(page.getByRole("menuitem", { name: "Page 1" })).toBeVisible();
});
test.describe("icon props", () => {
test("icon appears", async ({ initTestBed, page }) => {
const { testIcons } = await initTestBed(
`<App layout="vertical">
<NavPanel>
<NavGroup icon="bell" label="NavGroup">
<NavLink label="link" to="/" />
</NavGroup>
</NavPanel>
</App>`,
);
await expect(testIcons.bellIcon).toBeVisible();
});
test("iconHorizontal shows in horizontal layout submenu", async ({ initTestBed, page }) => {
const { testIcons } = await initTestBed(
`
<App layout="horizontal">
<NavPanel>
<NavGroup label="Send To">
<NavGroup icon="users" label="Team"
iconHorizontalExpanded="bell" iconHorizontalCollapsed="eye">
<NavLink label="Jane" />
</NavGroup>
</NavGroup>
</NavPanel>
</App>
`,
);
const bell = testIcons.bellIcon;
const eye = testIcons.eyeIcon;
await expect(bell).not.toBeVisible();
await expect(eye).not.toBeVisible();
await page.getByRole("button", { name: "Send to" }).click();
await expect(bell).not.toBeVisible();
await expect(eye).toBeVisible();
await page.getByRole("menuitem", { name: "Team" }).hover();
await expect(bell).toBeVisible();
await expect(eye).not.toBeVisible();
});
test("iconVertical shows in horizontal layout top lvl navgroup", async ({
initTestBed,
page,
}) => {
const { testIcons } = await initTestBed(
`
<App layout="horizontal">
<NavPanel>
<NavGroup icon="users" label="Team"
iconVerticalExpanded="bell" iconVerticalCollapsed="eye">
<NavLink label="Jane" />
</NavGroup>
</NavPanel>
</App>
`,
);
const bell = testIcons.bellIcon;
const eye = testIcons.eyeIcon;
await expect(bell).not.toBeVisible();
await expect(eye).toBeVisible();
await page.getByText("Team").click();
await expect(bell).toBeVisible();
await expect(eye).not.toBeVisible();
});
test("iconVertical shows in vertical layout submenu", async ({ initTestBed, page }) => {
const { testIcons } = await initTestBed(
`
<App layout="vertical">
<NavPanel>
<NavGroup label="Send To">
<NavGroup icon="users" label="Team"
iconVerticalExpanded="bell" iconVerticalCollapsed="eye">
<NavLink label="Jane" />
</NavGroup>
</NavGroup>
</NavPanel>
</App>
`,
);
const bell = testIcons.bellIcon;
const eye = testIcons.eyeIcon;
await expect(bell).not.toBeVisible();
await page.getByText("Send to").click();
await expect(bell).not.toBeVisible();
await expect(eye).toBeVisible();
await page.getByText("Team").click();
await expect(bell).toBeVisible();
await expect(eye).not.toBeVisible();
});
});
// =============================================================================
// DRAWER INTERACTION TESTS
// =============================================================================
test.describe("Drawer Interaction", () => {
test("clicking NavGroup toggle in drawer does not close drawer", async ({
initTestBed,
page,
}) => {
// Set small viewport to trigger drawer mode
await page.setViewportSize({ width: 400, height: 600 });
await initTestBed(`
<App layout="condensed">
<AppHeader testId="appHeader"/>
<NavPanel>
<NavGroup label="Pages">
<NavLink label="Page 1" to="/page1"/>
<NavLink label="Page 2" to="/page2"/>
</NavGroup>
</NavPanel>
<Pages fallbackPath="/">
<Page url="/">
<Text value="Home" />
</Page>
<Page url="/page1">
<Text value="Page 1" />
</Page>
<Page url="/page2">
<Text value="Page 2" />
</Page>
</Pages>
</App>
`);
// Open drawer by clicking hamburger button
const appHeader = page.getByTestId("appHeader");
const hamburgerButton = appHeader.locator('[data-part-id="hamburger"]').first();
await hamburgerButton.click();
const dialog = page.getByRole("dialog");
await expect(dialog).toBeVisible();
// finst the first element in the dialog with a text of "Pages"
const navGroupToggle = dialog.getByRole("button", { name: "Pages" });
await navGroupToggle.click();
await page.waitForTimeout(200);
await expect(dialog).toBeVisible();
// There must be a text "Page1"
await expect(dialog).toContainText("Page 1");
await expect(dialog).toContainText("Page 2");
});
test("clicking NavLink in drawer closes drawer", async ({ initTestBed, page }) => {
// Set small viewport to trigger drawer mode
await page.setViewportSize({ width: 400, height: 600 });
await initTestBed(`
<App layout="condensed">
<AppHeader />
<NavPanel>
<NavGroup label="Pages">
<NavLink label="Page 1" to="/page1"/>
<NavLink label="Page 2" to="/page2"/>
</NavGroup>
</NavPanel>
<Pages fallbackPath="/">
<Page url="/">
<Text value="Home" />
</Page>
<Page url="/page1">
<Text value="Page 1 Content" />
</Page>
<Page url="/page2">
<Text value="Page 2" />
</Page>
</Pages>
</App>
`);
// Open drawer
const hamburgerButton = page.locator('[data-part-id="hamburger"]');
await hamburgerButton.click();
const dialog = page.getByRole("dialog");
await expect(dialog).toBeVisible();
// Expand NavGroup
const navGroupToggle = dialog.getByRole("button", { name: "Pages" });
await navGroupToggle.click();
await page.waitForTimeout(200);
// Click a NavLink to navigate
await dialog.getByRole("link", { name: "Page 1" }).click();
// Verify navigation occurred
await expect(page.getByText("Page 1 Content")).toBeVisible();
// Verify drawer is closed
await expect(dialog).not.toBeVisible();
});
});
```
--------------------------------------------------------------------------------
/tools/vscode/syntaxes/xmlui.tmLanguage.json:
--------------------------------------------------------------------------------
```json
{
"name": "xmlui",
"scopeName": "source.xmlui",
"patterns": [{ "include": "#root" }],
"repository": {
"root": {
"patterns": [
{ "include": "#comments" },
{ "include": "#helperTag" },
{ "include": "#componentTag" },
{ "include": "#entity" },
{ "include": "#textWithBindingExpr" },
{
"begin": "(<!\\[)(CDATA)(\\[)",
"beginCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "storage.xmlui" },
"3": { "name": "punctuation.definition.tag.xmlui" }
},
"end": "]]>",
"endCaptures": {
"0": { "name": "punctuation.definition.tag.xmlui" }
},
"contentName": "string.unquoted.cdata.xmlui"
}
]
},
"methodTag": {
"begin": "(<)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)(method)",
"beginCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.tag.localname.xmlui" }
},
"endCaptures": {
"0": { "name": "punctuation.definition.tag.xmlui" }
},
"applyEndPatternLast": "1",
"patterns": [
{ "include": "#comments" },
{ "include": "#valueAttributeScriptInside" },
{ "include": "#attribute" },
{
"begin": "(?<!/|(?:/\\s*(?:method))\\s*)>",
"beginCaptures": {
"0": {
"name": "punctuation.definition.tag.xmlui"
}
},
"endCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.tag.localname.xmlui" }
},
"contentName": "meta.embedded.block.javascript",
"patterns": [{ "include": "source.js" }],
"end": "(</)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)(method)(?=\\s*>)"
}
],
"end": "/?>"
},
"eventTag": {
"begin": "(<)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)(event)",
"beginCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.tag.localname.xmlui" }
},
"endCaptures": {
"0": { "name": "punctuation.definition.tag.xmlui" }
},
"applyEndPatternLast": "1",
"patterns": [
{ "include": "#comments" },
{ "include": "#valueAttributeScriptInside" },
{ "include": "#attribute" },
{
"begin": "(?<!/|(?:/\\s*event)\\s*)(>)",
"beginCaptures": {
"0": {
"name": "punctuation.definition.tag.xmlui"
}
},
"endCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.tag.localname.xmlui" }
},
"patterns": [
{ "include": "#comments" },
{ "include": "#componentTag" },
{ "include": "source.js" }
],
"end": "(</)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)(event)(?=\\s*>)"
}
],
"end": ">"
},
"fieldTag": {
"begin": "(<)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)(field)",
"beginCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.tag.localname.xmlui" }
},
"endCaptures": {
"0": { "name": "punctuation.definition.tag.xmlui" }
},
"applyEndPatternLast": "1",
"patterns": [
{ "include": "#comments" },
{ "include": "#valueAttributeScriptInside" },
{ "include": "#attribute" },
{
"begin": "(?<!/|(?:/\\s*field)\\s*)(>)",
"beginCaptures": {
"0": {
"name": "punctuation.definition.tag.xmlui"
}
},
"endCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.tag.localname.xmlui" }
},
"patterns": [
{ "include": "#comments" },
{ "include": "#componentTag" },
{ "include": "source.js" }
],
"end": "(</)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)(field)(?=\\s*>)"
}
],
"end": ">"
},
"itemTag": {
"begin": "(<)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)(item)",
"beginCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.tag.localname.xmlui" }
},
"endCaptures": {
"0": { "name": "punctuation.definition.tag.xmlui" }
},
"applyEndPatternLast": "1",
"patterns": [
{ "include": "#comments" },
{ "include": "#valueAttributeScriptInside" },
{ "include": "#attribute" },
{
"begin": "(?<!/|(?:/\\s*item)\\s*)(>)",
"beginCaptures": {
"0": {
"name": "punctuation.definition.tag.xmlui"
}
},
"endCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.tag.localname.xmlui" }
},
"patterns": [
{ "include": "#comments" },
{ "include": "#componentTag" },
{ "include": "source.js" }
],
"end": "(</)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)(item)(?=\\s*>)"
}
],
"end": ">"
},
"bindingExpr": {
"contentName": "meta.embedded.block.javascript",
"begin": "\\{",
"end": "\\}",
"beginCaptures": {
"0": { "name": "entity.name.function.xmlui punctuation.definition.block.xmlui" }
},
"endCaptures": {
"0": { "name": "entity.name.function.xmlui punctuation.definition.block.xmlui" }
},
"patterns": [{ "include": "source.js" }]
},
"helperTag": {
"patterns": [
{ "include": "#scriptTag" },
{ "include": "#eventTag" },
{ "include": "#fieldTag" },
{ "include": "#itemTag" },
{ "include": "#methodTag" },
{ "include": "#propOrVarTag" }
]
},
"valueAttributeScriptInside": {
"patterns": [
{
"captures": {
"1": { "name": "entity.other.attribute-name.localname.xmlui" },
"2": { "name": "keyword.operator.xmlui" }
},
"match": "(?:^|\\s+)(value)(\\s*=)"
},
{ "include": "#quotedStringJsInside" }
]
},
"scriptTag": {
"begin": "(\\s*<)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)(script)(\\s*>)",
"end": "(\\s*</)(\\2)(\\3)(\\s*>)",
"contentName": "meta.embedded.block.javascript",
"beginCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.function.xmlui" },
"4": { "name": "punctuation.definition.tag.xmlui" }
},
"endCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.function.xmlui" },
"4": { "name": "punctuation.definition.tag.xmlui" }
},
"patterns": [{ "include": "source.js" }]
},
"textWithBindingExpr": {
"patterns": [{ "include": "#entity" }, { "match": "\\\\{" }, { "include": "#bindingExpr" }]
},
"propOrVarTag": {
"begin": "(</?)([a-zA-Z_][\\w\\.\\-]*?:)?((?:variable)|(?:property)|(?:prop))",
"beginCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.tag.localname.xmlui" }
},
"end": "(/?>)",
"endCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" }
},
"patterns": [{ "include": "#attribute" }, { "include": "#comments" }]
},
"componentTag": {
"begin": "(\\s*</?)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)([a-zA-Z][\\w\\.\\-]*)",
"beginCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "support.class.tag.component.xmlui" }
},
"end": "(/?>)",
"endCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" }
},
"patterns": [
{ "include": "#comments" },
{ "include": "#eventHandler" },
{ "include": "#attribute" }
]
},
"quotedStringJsInside": {
"begin": "\"|'|`",
"beginCaptures": {
"0": { "name": "string.xmlui" }
},
"end": "\\0",
"endCaptures": {
"0": { "name": "string.xmlui" }
},
"contentName": "meta.embedded.block.javascript",
"patterns": [{ "include": "source.js" }]
},
"entity": {
"captures": {
"1": { "name": "punctuation.definition.constant.xmlui" },
"2": { "name": "punctuation.definition.constant.xmlui" },
"3": { "name": "punctuation.definition.constant.xmlui" }
},
"match": "(&)((?:amp)|(?:lt)|(?:gt)|(?:quot)|(?:apos))(;)"
},
"eventHandler": {
"patterns": [
{
"captures": {
"1": { "name": "entity.other.attribute-name.localname.xmlui" },
"2": { "name": "keyword.operator.xmlui" }
},
"match": "(?:^|\\s+)(on[A-Z][-\\w.:$]*)(\\s*=)"
},
{ "include": "#quotedStringJsInside" }
]
},
"attribute": {
"patterns": [
{
"begin": "(?:^|\\s+)((?:[a-zA-Z$_][-\\w.$]*:)?)([a-zA-Z$_][-\\w.$]*)(\\s*=\\s*)(['\"`])",
"end": "\\4",
"beginCaptures": {
"1": { "name": "keyword.operator.namespace.xmlui" },
"2": { "name": "entity.other.attribute-name.localname.xmlui" },
"3": { "name": "keyword.operator.xmlui" },
"4": { "name": "string.xmlui" }
},
"endCaptures": {
"0": { "name": "string.xmlui" }
},
"contentName": "string.xmlui",
"patterns": [{ "include": "#textWithBindingExpr" }]
},
{
"match": "(?:^|\\s+)((?:[a-zA-Z$_][-\\w.$]*:)?)([a-zA-Z$_][-\\w.$]*)(\\s*=\\s*)([a-zA-Z$_][-\\w.$]*)",
"captures": {
"1": { "name": "keyword.operator.namespace.xmlui" },
"2": { "name": "entity.other.attribute-name.localname.xmlui" },
"3": { "name": "keyword.operator.xmlui" },
"4": { "name": "string.xmlui" }
}
},
{
"match": "(?:^|\\s+)((?:[a-zA-Z$_][-\\w.$]*:)?)([a-zA-Z$_][-\\w.$]*)",
"name": "entity.other.attribute-name.localname.xmlui",
"captures": {
"1": { "name": "keyword.operator.namespace.xmlui" },
"2": { "name": "entity.other.attribute-name.localname.xmlui" }
}
}
]
},
"comments": {
"patterns": [
{
"begin": "<!--",
"captures": {
"0": {
"name": "punctuation.definition.comment.xmlui"
}
},
"end": "-->",
"name": "comment.block.xmlui"
}
]
}
}
}
```
--------------------------------------------------------------------------------
/xmlui/src/syntax/textMate/xmlui.tmLanguage.json:
--------------------------------------------------------------------------------
```json
{
"name": "xmlui",
"scopeName": "source.xmlui",
"patterns": [{ "include": "#root" }],
"repository": {
"root": {
"patterns": [
{ "include": "#comments" },
{ "include": "#helperTag" },
{ "include": "#componentTag" },
{ "include": "#entity" },
{ "include": "#textWithBindingExpr" },
{
"begin": "(<!\\[)(CDATA)(\\[)",
"beginCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "storage.xmlui" },
"3": { "name": "punctuation.definition.tag.xmlui" }
},
"end": "]]>",
"endCaptures": {
"0": { "name": "punctuation.definition.tag.xmlui" }
},
"contentName": "string.unquoted.cdata.xmlui"
}
]
},
"methodTag": {
"begin": "(<)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)(method)",
"beginCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.tag.localname.xmlui" }
},
"endCaptures": {
"0": { "name": "punctuation.definition.tag.xmlui" }
},
"applyEndPatternLast": "1",
"patterns": [
{ "include": "#comments" },
{ "include": "#valueAttributeScriptInside" },
{ "include": "#attribute" },
{
"begin": "(?<!/|(?:/\\s*(?:method))\\s*)>",
"beginCaptures": {
"0": {
"name": "punctuation.definition.tag.xmlui"
}
},
"endCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.tag.localname.xmlui" }
},
"contentName": "meta.embedded.block.javascript",
"patterns": [{ "include": "source.js" }],
"end": "(</)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)(method)(?=\\s*>)"
}
],
"end": "/?>"
},
"eventTag": {
"begin": "(<)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)(event)",
"beginCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.tag.localname.xmlui" }
},
"endCaptures": {
"0": { "name": "punctuation.definition.tag.xmlui" }
},
"applyEndPatternLast": "1",
"patterns": [
{ "include": "#comments" },
{ "include": "#valueAttributeScriptInside" },
{ "include": "#attribute" },
{
"begin": "(?<!/|(?:/\\s*event)\\s*)(>)",
"beginCaptures": {
"0": {
"name": "punctuation.definition.tag.xmlui"
}
},
"endCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.tag.localname.xmlui" }
},
"patterns": [
{ "include": "#comments" },
{ "include": "#componentTag" },
{ "include": "source.js" }
],
"end": "(</)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)(event)(?=\\s*>)"
}
],
"end": ">"
},
"fieldTag": {
"begin": "(<)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)(field)",
"beginCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.tag.localname.xmlui" }
},
"endCaptures": {
"0": { "name": "punctuation.definition.tag.xmlui" }
},
"applyEndPatternLast": "1",
"patterns": [
{ "include": "#comments" },
{ "include": "#valueAttributeScriptInside" },
{ "include": "#attribute" },
{
"begin": "(?<!/|(?:/\\s*field)\\s*)(>)",
"beginCaptures": {
"0": {
"name": "punctuation.definition.tag.xmlui"
}
},
"endCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.tag.localname.xmlui" }
},
"patterns": [
{ "include": "#comments" },
{ "include": "#componentTag" },
{ "include": "source.js" }
],
"end": "(</)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)(field)(?=\\s*>)"
}
],
"end": ">"
},
"itemTag": {
"begin": "(<)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)(item)",
"beginCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.tag.localname.xmlui" }
},
"endCaptures": {
"0": { "name": "punctuation.definition.tag.xmlui" }
},
"applyEndPatternLast": "1",
"patterns": [
{ "include": "#comments" },
{ "include": "#valueAttributeScriptInside" },
{ "include": "#attribute" },
{
"begin": "(?<!/|(?:/\\s*item)\\s*)(>)",
"beginCaptures": {
"0": {
"name": "punctuation.definition.tag.xmlui"
}
},
"endCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.tag.localname.xmlui" }
},
"patterns": [
{ "include": "#comments" },
{ "include": "#componentTag" },
{ "include": "source.js" }
],
"end": "(</)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)(item)(?=\\s*>)"
}
],
"end": ">"
},
"bindingExpr": {
"contentName": "meta.embedded.block.javascript",
"begin": "\\{",
"end": "\\}",
"beginCaptures": {
"0": { "name": "entity.name.function.xmlui punctuation.definition.block.xmlui" }
},
"endCaptures": {
"0": { "name": "entity.name.function.xmlui punctuation.definition.block.xmlui" }
},
"patterns": [{ "include": "source.js" }]
},
"helperTag": {
"patterns": [
{ "include": "#scriptTag" },
{ "include": "#eventTag" },
{ "include": "#fieldTag" },
{ "include": "#itemTag" },
{ "include": "#methodTag" },
{ "include": "#propOrVarTag" }
]
},
"valueAttributeScriptInside": {
"patterns": [
{
"captures": {
"1": { "name": "entity.other.attribute-name.localname.xmlui" },
"2": { "name": "keyword.operator.xmlui" }
},
"match": "(?:^|\\s+)(value)(\\s*=)"
},
{ "include": "#quotedStringJsInside" }
]
},
"scriptTag": {
"begin": "(\\s*<)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)(script)(\\s*>)",
"end": "(\\s*</)(\\2)(\\3)(\\s*>)",
"contentName": "meta.embedded.block.javascript",
"beginCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.function.xmlui" },
"4": { "name": "punctuation.definition.tag.xmlui" }
},
"endCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.function.xmlui" },
"4": { "name": "punctuation.definition.tag.xmlui" }
},
"patterns": [{ "include": "source.js" }]
},
"textWithBindingExpr": {
"patterns": [{ "include": "#entity" }, { "match": "\\\\{" }, { "include": "#bindingExpr" }]
},
"propOrVarTag": {
"begin": "(</?)([a-zA-Z_][\\w\\.\\-]*?:)?((?:variable)|(?:property)|(?:prop))",
"beginCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "entity.name.tag.localname.xmlui" }
},
"end": "(/?>)",
"endCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" }
},
"patterns": [{ "include": "#attribute" }, { "include": "#comments" }]
},
"componentTag": {
"begin": "(\\s*</?)((?:[a-zA-Z_][\\w\\.\\-]*?:)?)([a-zA-Z][\\w\\.\\-]*)",
"beginCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" },
"2": { "name": "keyword.operator.namespace.xmlui" },
"3": { "name": "support.class.tag.component.xmlui" }
},
"end": "(/?>)",
"endCaptures": {
"1": { "name": "punctuation.definition.tag.xmlui" }
},
"patterns": [
{ "include": "#comments" },
{ "include": "#eventHandler" },
{ "include": "#attribute" }
]
},
"quotedStringJsInside": {
"begin": "\"|'|`",
"beginCaptures": {
"0": { "name": "string.xmlui" }
},
"end": "\\0",
"endCaptures": {
"0": { "name": "string.xmlui" }
},
"contentName": "meta.embedded.block.javascript",
"patterns": [{ "include": "source.js" }]
},
"entity": {
"captures": {
"1": { "name": "punctuation.definition.constant.xmlui" },
"2": { "name": "punctuation.definition.constant.xmlui" },
"3": { "name": "punctuation.definition.constant.xmlui" }
},
"match": "(&)((?:amp)|(?:lt)|(?:gt)|(?:quot)|(?:apos))(;)"
},
"eventHandler": {
"patterns": [
{
"captures": {
"1": { "name": "entity.other.attribute-name.localname.xmlui" },
"2": { "name": "keyword.operator.xmlui" }
},
"match": "(?:^|\\s+)(on[A-Z][-\\w.:$]*)(\\s*=)"
},
{ "include": "#quotedStringJsInside" }
]
},
"attribute": {
"patterns": [
{
"begin": "(?:^|\\s+)((?:[a-zA-Z$_][-\\w.$]*:)?)([a-zA-Z$_][-\\w.$]*)(\\s*=\\s*)(['\"`])",
"end": "\\4",
"beginCaptures": {
"1": { "name": "keyword.operator.namespace.xmlui" },
"2": { "name": "entity.other.attribute-name.localname.xmlui" },
"3": { "name": "keyword.operator.xmlui" },
"4": { "name": "string.xmlui" }
},
"endCaptures": {
"0": { "name": "string.xmlui" }
},
"contentName": "string.xmlui",
"patterns": [{ "include": "#textWithBindingExpr" }]
},
{
"match": "(?:^|\\s+)((?:[a-zA-Z$_][-\\w.$]*:)?)([a-zA-Z$_][-\\w.$]*)(\\s*=\\s*)([a-zA-Z$_][-\\w.$]*)",
"captures": {
"1": { "name": "keyword.operator.namespace.xmlui" },
"2": { "name": "entity.other.attribute-name.localname.xmlui" },
"3": { "name": "keyword.operator.xmlui" },
"4": { "name": "string.xmlui" }
}
},
{
"match": "(?:^|\\s+)((?:[a-zA-Z$_][-\\w.$]*:)?)([a-zA-Z$_][-\\w.$]*)",
"name": "entity.other.attribute-name.localname.xmlui",
"captures": {
"1": { "name": "keyword.operator.namespace.xmlui" },
"2": { "name": "entity.other.attribute-name.localname.xmlui" }
}
}
]
},
"comments": {
"patterns": [
{
"begin": "<!--",
"captures": {
"0": {
"name": "punctuation.definition.comment.xmlui"
}
},
"end": "-->",
"name": "comment.block.xmlui"
}
]
}
}
}
```
--------------------------------------------------------------------------------
/docs/content/components/Form.md:
--------------------------------------------------------------------------------
```markdown
# Form [#form]
`Form` provides a structured container for collecting and validating user input, with built-in data binding, validation, and submission handling. It automatically manages form state and provides context for nested form controls to work together.
**Key features:**
- **Automatic data binding**: Form controls automatically sync with form data using `bindTo` properties
- **Built-in validation**: Validates individual fields and overall form state before submission
- **Context sharing**: Provides `$data` and other context values accessible to all nested components
- **Submission handling**: Manages form submission workflow and prevents invalid submissions
See [this guide](/forms) for details.
**Context variables available during execution:**
- `$data`: This property represents the value of the form data. You can access the fields of the form using the IDs in the `bindTo` property of nested `FormItem` instances. `$data` also provides an `update` method as a shortcut to the Form's exposed `update` method.
## Properties [#properties]
### `buttonRowTemplate` [#buttonrowtemplate]
This property allows defining a custom component to display the buttons at the bottom of the form.
The following example demonstrates using it:
```xmlui-pg copy display name="Example: buttonRowTemplate"
---app copy display {10-19}
<App>
<Form id="searchForm" padding="0.5rem"
data="{{ search: 'Seattle', caseSensitive: false }}"
onSubmit="() => {isSearching = true; delay(1000); isSearching = false; }"
saveLabel="Search"
var.isSearching="{false}">
<Text>Please specify the name to include in the search:</Text>
<FormItem bindTo="search" width="280px" />
<FormItem type="checkbox" label="Case sensitive?" bindTo="caseSensitive" />
<property name="buttonRowTemplate">
<HStack gap="0.5rem" borderTop="1px solid #ddd" paddingVertical="1rem">
<Button label="Test Search Server" type="button"
themeColor="secondary" variant="outlined"
onClick="toast('Search server is ok.')"/>
<SpaceFiller/>
<Button type="submit" enabled="{!isSearching}" icon="search"
label="{isSearching ? 'Searching...' : 'Search'}"/>
</HStack>
</property>
</Form>
</App>
---desc
This example mimics a one-second search and turns off the submit button during the operation. Also, it adds a Test Search Server button:
```
### `cancelLabel` (default: "Cancel") [#cancellabel-default-cancel]
This property defines the label of the Cancel button.
### `completedNotificationMessage` [#completednotificationmessage]
This property sets the message to display when the form is submitted successfully.
### `data` [#data]
This property sets the initial value of the form's data structure. The form infrastructure uses this value to set the initial state of form items within the form. If this property isnot set, the form does not have an initial value.
### `enabled` (default: true) [#enabled-default-true]
This boolean property value indicates whether the component responds to user events (`true`) or not (`false`).
### `enableSubmit` (default: true) [#enablesubmit-default-true]
This property controls whether the submit button is enabled. When set to false, the submit button is disabled and the form cannot be submitted.
### `errorNotificationMessage` [#errornotificationmessage]
This property sets the message to display when the form submission fails.
### `hideButtonRow` (default: false) [#hidebuttonrow-default-false]
This property hides the button row entirely when set to true.
### `hideButtonRowUntilDirty` (default: false) [#hidebuttonrowuntildirty-default-false]
This property hides the button row until the form data is modified (dirty).
### `inProgressNotificationMessage` [#inprogressnotificationmessage]
This property sets the message to display when the form is being submitted.
### `itemLabelBreak` (default: true) [#itemlabelbreak-default-true]
This boolean value indicates if form item labels can be split into multiple lines if it would overflow the available label width. Individual `FormItem` instances can override this property.
### `itemLabelPosition` (default: "top") [#itemlabelposition-default-top]
This property sets the position of the item labels within the form.Individual `FormItem` instances can override this property.
Available values:
| Value | Description |
| --- | --- |
| `start` | The left side of the input (left-to-right) or the right side of the input (right-to-left) |
| `end` | The right side of the input (left-to-right) or the left side of the input (right-to-left) |
| `top` | The top of the input **(default)** |
| `bottom` | The bottom of the input |
### `itemLabelWidth` [#itemlabelwidth]
This property sets the width of the item labels within the form. Individual `FormItem` instances can override this property. If this property is not set, each form item nested in the form uses its calculated label width. These widths may be different for each item.
### `keepModalOpenOnSubmit` (default: false) [#keepmodalopenonsubmit-default-false]
This property prevents the modal from closing when the form is submitted.
### `saveInProgressLabel` (default: "Saving...") [#saveinprogresslabel-default-saving-]
This property defines the label of the Save button to display during the form data submit (save) operation.
### `saveLabel` (default: "Save") [#savelabel-default-save]
This property defines the label of the Save button.
### `submitMethod` [#submitmethod]
This property sets the HTTP method to use when submitting the form data. If not defined, `put` is used when the form has initial data; otherwise, `post`.
### `submitUrl` [#submiturl]
URL to submit the form data.
### `swapCancelAndSave` (default: false) [#swapcancelandsave-default-false]
By default, the Cancel button is to the left of the Save button. Set this property to `true` to swap them or `false` to keep their original location.
## Events [#events]
### `cancel` [#cancel]
The form infrastructure fires this event when the form is canceled.
### `reset` [#reset]
The form infrastructure fires this event when the form is reset.
### `submit` [#submit]
The form infrastructure fires this event when the form is submitted. The event argument is the current `data` value to save.
```xmlui-pg copy {4} display name="Example: submit"
<App>
<Form padding="0.5rem"
data="{{ name: 'Joe', age: 43 }}"
onSubmit="(toSave) => toast(JSON.stringify(toSave))">
<FlowLayout columnGap="12px" paddingBottom="6px">
<FormItem bindTo="name" label="Customer name" width="50%" />
<FormItem bindTo="age" label="Age" type="integer" width="50%"
zeroOrPositive="true" />
</FlowLayout>
</Form>
</App>
```
### `success` [#success]
The form infrastructure fires this event when the form is submitted successfully.
### `willSubmit` [#willsubmit]
The form infrastructure fires this event just before the form is submitted. The event argument is the current `data` value to be submitted. You can cancel the submission by returning `false` from the event handler.
The following example allows saving customer data only when the age is an even number. The `willSubmit` event handler returns `false` if this condition is not met.
```xmlui-pg display copy {4-9} name="Example: willSubmit"
<App>
<Form padding="0.5rem"
data="{{ name: 'Joe', age: 43 }}"
onWillSubmit="(toSubmit) => {
if (toSubmit.age % 2) {
toast.error('Age must be an even number');
return false;
}
}"
onSubmit="(toSave) => toast(JSON.stringify(toSave))">
<FlowLayout columnGap="12px" paddingBottom="6px">
<FormItem bindTo="name" label="Customer name" width="50%" />
<FormItem bindTo="age" label="Age" type="integer" width="50%"
zeroOrPositive="true" />
</FlowLayout>
</Form>
</App>
```
## Exposed Methods [#exposed-methods]
### `reset` [#reset]
This method resets the form to its initial state, clearing all user input.
**Signature**: `reset(): void`
### `update` [#update]
You can pass a data object to update the form data. The properties in the passed data object are updated to their values accordingly. Other form properties remain intact.
**Signature**: `update(data: Record<string, any>): void`
- `data`: An object containing the form data to update.
This method updates the form data with the change passed in its parameter. The parameter is a hash object, and this method updates the Form's properties accordingly.
```xmlui-pg copy display name="Example: update"
<App>
<Form id="myForm" padding="0.5rem"
data="{{ name: 'Joe', age: 43, $update: 123 }}"
onSubmit="(toSave) => toast(JSON.stringify(toSave))">
<FlowLayout columnGap="12px" paddingBottom="6px">
<FormItem bindTo="name" label="Customer name" width="50%" />
<FormItem bindTo="age" label="Age" type="integer" width="50%"
zeroOrPositive="true" />
</FlowLayout>
<Button onClick="() => $data.update({age: $data.age + 1})" >
Increment age (1)
</Button>
<Button onClick="() => myForm.update({age: $data.age + 1})" >
Increment age (2)
</Button>
<Button onClick="() => myForm.update({name: $data.name + '!', age: $data.age + 1})" >
Update name and age
</Button>
</Form>
</App>
```
### `validate` [#validate]
This method triggers validation on all form fields without submitting the form. It displays validation errors and returns the validation result along with the cleaned form data. This is useful for implementing custom submit buttons or performing operations that require validated data without actually submitting the form.
**Signature**: `validate(): Promise<{ isValid: boolean, data: Record<string, any>, errors: ValidationResult[], warnings: ValidationResult[], validationResults: Record<string, ValidationResult> }>`
## Styling [#styling]
### Theme Variables [#theme-variables]
| Variable | Default Value (Light) | Default Value (Dark) |
| --- | --- | --- |
| [backgroundColor](../styles-and-themes/common-units/#color)-ValidationDisplay-error | $color-danger-100 | $color-danger-100 |
| [backgroundColor](../styles-and-themes/common-units/#color)-ValidationDisplay-info | $color-primary-100 | $color-primary-100 |
| [backgroundColor](../styles-and-themes/common-units/#color)-ValidationDisplay-valid | $color-success-100 | $color-success-100 |
| [backgroundColor](../styles-and-themes/common-units/#color)-ValidationDisplay-warning | $color-warn-100 | $color-warn-100 |
| [color](../styles-and-themes/common-units/#color)-accent-ValidationDisplay-error | $color-error | $color-error |
| [color](../styles-and-themes/common-units/#color)-accent-ValidationDisplay-info | $color-info | $color-info |
| [color](../styles-and-themes/common-units/#color)-accent-ValidationDisplay-valid | $color-valid | $color-valid |
| [color](../styles-and-themes/common-units/#color)-accent-ValidationDisplay-warning | $color-warning | $color-warning |
| [gap](../styles-and-themes/common-units/#size)-buttonRow-Form | $space-4 | $space-4 |
| [gap](../styles-and-themes/common-units/#size)-Form | $space-4 | $space-4 |
| [marginTop](../styles-and-themes/common-units/#size)-buttonRow-Form | $space-4 | $space-4 |
| [textColor](../styles-and-themes/common-units/#color)-ValidationDisplay-error | $color-error | $color-error |
| [textColor](../styles-and-themes/common-units/#color)-ValidationDisplay-info | $color-info | $color-info |
| [textColor](../styles-and-themes/common-units/#color)-ValidationDisplay-valid | $color-valid | $color-valid |
| [textColor](../styles-and-themes/common-units/#color)-ValidationDisplay-warning | $color-warning | $color-warning |
```
--------------------------------------------------------------------------------
/xmlui/src/components/collectedComponentMetadata.ts:
--------------------------------------------------------------------------------
```typescript
import { ButtonMd } from "./Button/Button";
import { CHStackMd, CVStackMd, HStackMd, StackMd, VStackMd } from "./Stack/Stack";
import { PasswordMd, TextBoxMd } from "./TextBox/TextBox";
import { ThemeMd } from "./Theme/Theme";
import { AppMd } from "./App/App";
import { AppHeaderMd } from "./AppHeader/AppHeader";
import { AppStateMd } from "./AppState/AppState";
import { AvatarMd } from "./Avatar/Avatar";
import { BadgeMd } from "./Badge/Badge";
import { BookmarkMd } from "./Bookmark/Bookmark";
import { CardMd } from "./Card/Card";
import { ChangeListenerMd } from "./ChangeListener/ChangeListener";
import { CheckboxMd } from "./Checkbox/Checkbox";
import { ContentSeparatorMd } from "./ContentSeparator/ContentSeparator";
import { DatePickerMd } from "./DatePicker/DatePicker";
import {
DropdownMenuMd,
MenuItemMd,
MenuSeparatorMd,
SubMenuItemMd,
} from "./DropdownMenu/DropdownMenu";
import { EmojiSelectorMd } from "./EmojiSelector/EmojiSelector";
import { FileInputMd } from "./FileInput/FileInput";
import { FileUploadDropZoneMd } from "./FileUploadDropZone/FileUploadDropZone";
import { FlowLayoutMd } from "./FlowLayout/FlowLayout";
import { FooterMd } from "./Footer/Footer";
import { FormMd } from "./Form/Form";
import { FormItemMd } from "./FormItem/FormItem";
import { H1Md, H2Md, H3Md, H4Md, H5Md, H6Md, HeadingMd } from "./Heading/Heading";
import { HoverCardMd } from "./HoverCard/HoverCard";
import { IconMd } from "./Icon/Icon";
import { IFrameMd } from "./IFrame/IFrame";
import { ImageMd } from "./Image/Image";
import { ItemsMd } from "./Items/Items";
import { LinkMd } from "./Link/Link";
import { ListMd } from "./List/List";
import { LogoMd } from "./Logo/Logo";
import { MarkdownMd } from "./Markdown/Markdown";
import { ModalDialogMd } from "./ModalDialog/ModalDialog";
import { NavGroupMd } from "./NavGroup/NavGroup";
import { NavLinkMd } from "./NavLink/NavLink";
import { NavPanelMd } from "./NavPanel/NavPanel";
import { NoResultMd } from "./NoResult/NoResult";
import { NumberBoxMd } from "./NumberBox/NumberBox";
import { PageMetaTitleMd } from "./PageMetaTitle/PageMetaTitle";
import { PageMd, PagesMd } from "./Pages/Pages";
import { PositionedContainerMd } from "./PositionedContainer/PositionedContainer";
import { ProgressBarMd } from "./ProgressBar/ProgressBar";
import { QueueMd } from "./Queue/Queue";
import { RadioGroupMd } from "./RadioGroup/RadioGroup";
import { RealTimeAdapterMd } from "./RealTimeAdapter/RealTimeAdapter";
import { RedirectMd } from "./Redirect/Redirect";
import { SelectMd } from "./Select/Select";
import { SelectionStoreMd } from "./SelectionStore/SelectionStore";
import { SpaceFillerMd } from "./SpaceFiller/SpaceFiller";
import { SpinnerMd } from "./Spinner/Spinner";
import { HSplitterMd, SplitterMd, VSplitterMd } from "./Splitter/Splitter";
import { StickyBoxMd } from "./StickyBox/StickyBox";
import { SwitchMd } from "./Switch/Switch";
import { TableMd } from "./Table/Table";
import { ColumnMd } from "./Column/Column";
import { TableOfContentsMd } from "./TableOfContents/TableOfContents";
import { TabsMd } from "./Tabs/Tabs";
import { TextMd } from "./Text/Text";
import { TextAreaMd } from "./TextArea/TextArea";
import { AccordionMd } from "./Accordion/Accordion";
import { TabItemMd } from "./Tabs/TabItem";
import { FragmentMd } from "./Fragment/Fragment";
import { TreeMd } from "./Tree/TreeComponent";
import { APICallMd } from "./APICall/APICall";
import { DataSourceMd } from "./DataSource/DataSource";
import { FormSectionMd } from "./FormSection/FormSection";
import { BreakoutMd } from "./Breakout/Breakout";
import { CarouselMd } from "./Carousel/Carousel";
import { ToneChangerButtonMd } from "./ToneChangerButton/ToneChangerButton";
import { ToneSwitchMd } from "./ToneSwitch/ToneSwitch";
import { OptionMd } from "./Option/Option";
import { AutoCompleteMd } from "./AutoComplete/AutoComplete";
import { BackdropMd } from "./Backdrop/Backdrop";
import {
HtmlAddressMd,
HtmlAMd,
HtmlAreaMd,
HtmlArticleMd,
HtmlAsideMd,
HtmlAudioMd,
HtmlBdiMd,
HtmlBdoMd,
HtmlBlockquoteMd,
HtmlBMd,
HtmlBrMd,
HtmlButtonMd,
HtmlCanvasMd,
HtmlCaptionMd,
HtmlCiteMd,
HtmlCodeMd,
HtmlColgroupMd,
HtmlColMd,
HtmlDatalistMd,
HtmlDataMd,
HtmlDdMd,
HtmlDelMd,
HtmlDetailsMd,
HtmlDfnMd,
HtmlDialogMd,
HtmlDivMd,
HtmlDlMd,
HtmlDtMd,
HtmlEmbedMd,
HtmlEMMd,
HtmlFieldsetMd,
HtmlFigcaptionMd,
HtmlFigureMd,
HtmlFooterMd,
HtmlFormMd,
HtmlH1Md,
HtmlH2Md,
HtmlH3Md,
HtmlH4Md,
HtmlH5Md,
HtmlH6Md,
HtmlHeaderMd,
HtmlHrMd,
HtmlIframeMd,
HtmlIMd,
HtmlImgMd,
HtmlInputMd,
HtmlInsMd,
HtmlKbdMd,
HtmlLabelMd,
HtmlLegendMd,
HtmlLiMd,
HtmlMainMd,
HtmlMapMd,
HtmlMarkMd,
HtmlMenuMd,
HtmlMeterMd,
HtmlNavMd,
HtmlObjectMd,
HtmlOlMd,
HtmlOptgroupMd,
HtmlOptionMd,
HtmlOutputMd,
HtmlParamMd,
HtmlPictureMd,
HtmlPMd,
HtmlPreMd,
HtmlProgressMd,
HtmlQMd,
HtmlRpMd,
HtmlRtMd,
HtmlRubyMd,
HtmlSampMd,
HtmlSectionMd,
HtmlSelectMd,
HtmlSmallMd,
HtmlSMd,
HtmlSourceMd,
HtmlSpanMd,
HtmlStrongMd,
HtmlSubMd,
HtmlSummaryMd,
HtmlSupMd,
HtmlTableMd,
HtmlTbodyMd,
HtmlTdMd,
HtmlTemplateMd,
HtmlTextareaMd,
HtmlTfootMd,
HtmlTheadMd,
HtmlThMd,
HtmlTimeMd,
HtmlTrackMd,
HtmlTrMd,
HtmlUlMd,
HtmlUMd,
HtmlVideoMd,
HtmlVarMd,
HtmlWbrMd,
} from "./HtmlTags/HtmlTags";
import { SliderMd } from "./Slider/Slider";
import { ColorPickerMd } from "./ColorPicker/ColorPicker";
import type { ThemeDefinition } from "../abstractions/ThemingDefs";
import { RootThemeDefinition } from "../components-core/theming/themes/root";
import {
XmlUiCyanThemeDefinition,
XmlUiGrayThemeDefinition,
XmlUiGreenThemeDefinition,
XmlUiOrangeThemeDefinition,
XmlUiPurpleThemeDefinition,
XmlUiRedThemeDefinition,
XmlUiThemeDefinition,
} from "../components-core/theming/themes/xmlui";
import { BarChartMd } from "./Charts/BarChart/BarChart";
import { DonutChartMd } from "./Charts/DonutChart/DonutChart";
import { LabelListMd } from "./Charts/LabelList/LabelList";
import { LegendMd } from "./Charts/Legend/Legend";
import { LineChartMd } from "./Charts/LineChart/LineChart";
import { PieChartMd } from "./Charts/PieChart/PieChart";
import { ExpandableItemMd } from "./ExpandableItem/ExpandableItem";
import { SlotMd } from "./Slot/Slot";
import { TooltipMd } from "./Tooltip/Tooltip";
import { TimeInputMd } from "./TimeInput/TimeInput";
import { TimerMd } from "./Timer/Timer";
import { DateInput } from "./DateInput/DateInputNative";
import { DateInputMd } from "./DateInput/DateInput";
import { PaginationMd } from "./Pagination/Pagination";
export const collectedComponentMetadata = {
// --- HTML tags
a: HtmlAMd,
address: HtmlAddressMd,
area: HtmlAreaMd,
article: HtmlArticleMd,
aside: HtmlAsideMd,
audio: HtmlAudioMd,
b: HtmlBMd,
bdi: HtmlBdiMd,
bdo: HtmlBdoMd,
blockquote: HtmlBlockquoteMd,
br: HtmlBrMd,
button: HtmlButtonMd,
canvas: HtmlCanvasMd,
caption: HtmlCaptionMd,
cite: HtmlCiteMd,
code: HtmlCodeMd,
col: HtmlColMd,
colgroup: HtmlColgroupMd,
data: HtmlDataMd,
datalist: HtmlDatalistMd,
dd: HtmlDdMd,
del: HtmlDelMd,
details: HtmlDetailsMd,
dfn: HtmlDfnMd,
dialog: HtmlDialogMd,
div: HtmlDivMd,
dl: HtmlDlMd,
dt: HtmlDtMd,
em: HtmlEMMd,
embed: HtmlEmbedMd,
fieldset: HtmlFieldsetMd,
figcaption: HtmlFigcaptionMd,
figure: HtmlFigureMd,
footer: HtmlFooterMd,
form: HtmlFormMd,
h1: HtmlH1Md,
h2: HtmlH2Md,
h3: HtmlH3Md,
h4: HtmlH4Md,
h5: HtmlH5Md,
h6: HtmlH6Md,
header: HtmlHeaderMd,
hr: HtmlHrMd,
i: HtmlIMd,
iframe: HtmlIframeMd,
img: HtmlImgMd,
input: HtmlInputMd,
ins: HtmlInsMd,
kbd: HtmlKbdMd,
label: HtmlLabelMd,
legend: HtmlLegendMd,
li: HtmlLiMd,
main: HtmlMainMd,
map: HtmlMapMd,
mark: HtmlMarkMd,
menu: HtmlMenuMd,
meter: HtmlMeterMd,
nav: HtmlNavMd,
object: HtmlObjectMd,
ol: HtmlOlMd,
optgroup: HtmlOptgroupMd,
option: HtmlOptionMd,
output: HtmlOutputMd,
p: HtmlPMd,
param: HtmlParamMd,
picture: HtmlPictureMd,
pre: HtmlPreMd,
progress: HtmlProgressMd,
q: HtmlQMd,
rp: HtmlRpMd,
rt: HtmlRtMd,
ruby: HtmlRubyMd,
s: HtmlSMd,
samp: HtmlSampMd,
section: HtmlSectionMd,
select: HtmlSelectMd,
small: HtmlSmallMd,
source: HtmlSourceMd,
span: HtmlSpanMd,
strong: HtmlStrongMd,
sub: HtmlSubMd,
summary: HtmlSummaryMd,
sup: HtmlSupMd,
table: HtmlTableMd,
tbody: HtmlTbodyMd,
td: HtmlTdMd,
template: HtmlTemplateMd,
textarea: HtmlTextareaMd,
tfoot: HtmlTfootMd,
th: HtmlThMd,
thead: HtmlTheadMd,
time: HtmlTimeMd,
tr: HtmlTrMd,
track: HtmlTrackMd,
u: HtmlUMd,
ul: HtmlUlMd,
var: HtmlVarMd,
video: HtmlVideoMd,
wbr: HtmlWbrMd,
// --- Heavy xmlui components
Accordion: AccordionMd,
APICall: APICallMd,
App: AppMd,
AppHeader: AppHeaderMd,
AppState: AppStateMd,
AutoComplete: AutoCompleteMd,
Avatar: AvatarMd,
Backdrop: BackdropMd,
Badge: BadgeMd,
Bookmark: BookmarkMd,
Breakout: BreakoutMd,
Button: ButtonMd,
Card: CardMd,
Carousel: CarouselMd,
ChangeListener: ChangeListenerMd,
Checkbox: CheckboxMd,
CODE: HtmlCodeMd,
ColorPicker: ColorPickerMd,
Column: ColumnMd,
ContentSeparator: ContentSeparatorMd,
DataSource: DataSourceMd,
DatePicker: DatePickerMd,
DateInput: DateInputMd,
DropdownMenu: DropdownMenuMd,
EM: HtmlEMMd,
Fragment: FragmentMd,
MenuItem: MenuItemMd,
SubMenuItem: SubMenuItemMd,
EmojiSelector: EmojiSelectorMd,
ExpandableItem: ExpandableItemMd,
FileInput: FileInputMd,
FileUploadDropZone: FileUploadDropZoneMd,
FlowLayout: FlowLayoutMd,
Footer: FooterMd,
Form: FormMd,
FormItem: FormItemMd,
FormSection: FormSectionMd,
Heading: HeadingMd,
H1: H1Md,
H2: H2Md,
H3: H3Md,
H4: H4Md,
H5: H5Md,
H6: H6Md,
HoverCard: HoverCardMd,
Icon: IconMd,
IFrame: IFrameMd,
Image: ImageMd,
Items: ItemsMd,
Link: LinkMd,
List: ListMd,
Logo: LogoMd,
Markdown: MarkdownMd,
MenuSeparator: MenuSeparatorMd,
ModalDialog: ModalDialogMd,
NavGroup: NavGroupMd,
NavLink: NavLinkMd,
NavPanel: NavPanelMd,
NoResult: NoResultMd,
NumberBox: NumberBoxMd,
Option: OptionMd,
PageMetaTitle: PageMetaTitleMd,
Page: PageMd,
Pages: PagesMd,
Pagination: PaginationMd,
PositionedContainer: PositionedContainerMd,
ProgressBar: ProgressBarMd,
Queue: QueueMd,
RadioGroup: RadioGroupMd,
RealTimeAdapter: RealTimeAdapterMd,
Redirect: RedirectMd,
Select: SelectMd,
SelectionStore: SelectionStoreMd,
Slider: SliderMd,
Slot: SlotMd,
SpaceFiller: SpaceFillerMd,
Spinner: SpinnerMd,
Splitter: SplitterMd,
Tooltip: TooltipMd,
HSplitter: HSplitterMd,
VSplitter: VSplitterMd,
Stack: StackMd,
CHStack: CHStackMd,
CVStack: CVStackMd,
HStack: HStackMd,
VStack: VStackMd,
StickyBox: StickyBoxMd,
Switch: SwitchMd,
Table: TableMd,
TableOfContents: TableOfContentsMd,
TabItem: TabItemMd,
Tabs: TabsMd,
Text: TextMd,
TextArea: TextAreaMd,
TextBox: TextBoxMd,
PasswordInput: PasswordMd,
Theme: ThemeMd,
TimeInput: TimeInputMd,
Timer: TimerMd,
ToneChangerButton: ToneChangerButtonMd,
ToneSwitch: ToneSwitchMd,
Tree: TreeMd,
BarChart: BarChartMd,
DonutChart: DonutChartMd,
LabelList: LabelListMd,
Legend: LegendMd,
LineChart: LineChartMd,
PieChart: PieChartMd,
};
export const collectedThemes: Record<string, ThemeDefinition> = {
root: RootThemeDefinition,
xmlui: XmlUiThemeDefinition,
xmluiGreen: XmlUiGreenThemeDefinition,
xmluiGray: XmlUiGrayThemeDefinition,
xmluiOrange: XmlUiOrangeThemeDefinition,
xmluiPurple: XmlUiPurpleThemeDefinition,
xmluiCyan: XmlUiCyanThemeDefinition,
xmluiRed: XmlUiRedThemeDefinition,
};
```
--------------------------------------------------------------------------------
/xmlui/src/components-core/utils/extractParam.ts:
--------------------------------------------------------------------------------
```typescript
import React, { type CSSProperties } from "react";
import { isPlainObject } from "lodash-es";
import type { ContainerState } from "../rendering/ContainerWrapper";
import type { AppContextObject } from "../../abstractions/AppContextDefs";
import { parseParameterString } from "../script-runner/ParameterParser";
import { evalBinding } from "../script-runner/eval-tree-sync";
import { LRUCache } from "../utils/LruCache";
import type { ValueExtractor } from "../../abstractions/RendererDefs";
import { layoutOptionKeys } from "../descriptorHelper";
import { asOptionalBoolean } from "../rendering/valueExtractor";
/**
* Extract the value of the specified parameter from the given view container state
* @param state The state of the view container
* @param param Parameter to extract
* @param appContext Application context to use
* @param strict Strict evaluation?
* @param extractContext
* @returns
*/
export function extractParam(
state: ContainerState,
param: any,
appContext: AppContextObject | undefined = undefined,
strict: boolean = false, // --- In this case we only allow string binding expression
extractContext: { didResolve: boolean } = { didResolve: false },
): any {
if (typeof param === "string") {
const paramSegments = parseParameterString(param);
if (paramSegments.length === 0) {
// --- The param is an empty string, retrieve it
return param;
}
// --- Cut the first segment, if it is whitespace-only
if (paramSegments[0].type === "literal" && paramSegments[0].value.trim() === "") {
paramSegments.shift();
}
if (paramSegments.length === 0) {
// --- The param is an empty string, retrieve it
return param;
}
// --- Cut the last segment, if it is whitespace-only
const lastSegment = paramSegments[paramSegments.length - 1];
if (lastSegment.type === "literal" && lastSegment.value.trim() === "") {
paramSegments.pop();
}
if (paramSegments.length === 0) {
// --- The param is an empty string, retrieve it
return param;
}
if (paramSegments.length === 1) {
// --- We have a single string literal or expression
if (paramSegments[0].type === "literal") {
// --- No expression to evaluate
return paramSegments[0].value;
} else {
// --- We have a single expression to evaluate
extractContext.didResolve = true;
return evalBinding(paramSegments[0].value, {
localContext: state,
appContext,
options: {
defaultToOptionalMemberAccess: true,
},
});
}
}
// --- At this point, we have multiple segments. Evaluate all expressions and convert them to strings
let result = "";
paramSegments.forEach((ps) => {
if (ps.type === "literal") {
result += ps.value;
} else {
extractContext.didResolve = true;
const exprValue = evalBinding(ps.value, {
localContext: state,
appContext,
options: {
defaultToOptionalMemberAccess: true,
},
});
if (exprValue?.toString) {
result += exprValue.toString();
}
}
});
return result;
}
if (strict) {
// --- As we allow only string parameters as binding expressions, we return with the provided
// --- *not string* parameter without transforming it
return param;
}
// --- Resolve each array item
if (Array.isArray(param)) {
const arrayExtractContext = { didResolve: false };
let resolvedChildren = param.map((childParam) =>
extractParam(state, childParam, appContext, false, arrayExtractContext),
);
if (arrayExtractContext.didResolve) {
extractContext.didResolve = true;
return resolvedChildren;
}
return param;
}
// --- Resolve each object property
if (isPlainObject(param)) {
const objectExtractContext = { didResolve: false };
const substitutedObject: Record<string, any> = {};
Object.entries(param).forEach(([key, value]) => {
substitutedObject[key] = extractParam(state, value, appContext, false, objectExtractContext);
});
if (objectExtractContext.didResolve) {
extractContext.didResolve = true;
return substitutedObject;
}
return param;
}
// --- The param itself is the extracted value
return param;
}
// --- Store stable object references for extracted parameter values
const extractedObjectCache = new LRUCache(1024 * 10);
/**
* Get a stable object reference from an LRU cache
* @param object Object to get the stable reference for
*
* We are doing this to prevent creating new objects with new references when the data hasn't changed this way we
* can use these as dependencies for useEffect
*/
export function withStableObjectReference(object: any) {
if (typeof object === "function") {
return object;
}
if (React.isValidElement(object) || (Array.isArray(object) && React.isValidElement(object[0]))) {
//here could be some gnarly circular object references, JSON.stringify would blow up
return object;
}
if (object?._ARROW_EXPR_) {
//here could be some gnarly circular object references, JSON.stringify would blow up
return object;
}
try {
const stringObject = JSON.stringify(object);
const cachedObject = extractedObjectCache.get(stringObject);
if (cachedObject) {
return cachedObject;
}
extractedObjectCache.set(stringObject, object);
} catch (e) {
console.log(object);
console.warn("couldn't cache result", e);
}
return object;
}
export function shouldKeep(
when: string | boolean | undefined,
componentState: ContainerState,
appContext?: AppContextObject,
) {
if (when === undefined) {
return true;
}
return asOptionalBoolean(extractParam(componentState, when, appContext, true));
}
/**
* Resolves props that can either be regular properties or URL resources.
* It also removes layoutCss props from regular properties.
* @param props Component (rest) props
* @param extractValue Value extractor function
* @param layoutCss Component styles
* @param resourceExtraction URL resource extractor function and array that specifies which props are URL resources
* @returns properties that are resolved and cleaned of CSS styles
*/
export function resolveAndCleanProps<T extends Record<string, any>>(
props: Record<string, any>,
extractValue: ValueExtractor,
resourceExtraction?: {
extractResourceUrl: (url?: string) => string | undefined;
resourceProps?: string[];
},
): T {
const { extractResourceUrl, resourceProps } = resourceExtraction ?? {};
const cleanedProps = removeStylesFromProps(props);
const resultProps: Record<string, any> = {} as any;
const result = Object.keys(cleanedProps).reduce((acc, propName) => {
if (resourceProps && extractResourceUrl && resourceProps.includes(propName)) {
acc[propName] = extractResourceUrl(cleanedProps[propName]);
} else {
acc[propName] = extractValue(cleanedProps[propName]);
}
return acc;
}, resultProps);
// --- Remove aliased CSS properties
delete resultProps.canShrink;
delete resultProps.radiusTopLeft;
delete resultProps.radiusTopRight;
delete resultProps.radiusBottomLeft;
delete resultProps.radiusBottomRight;
// --- Delete pseudo CSS properties
delete resultProps.paddingHorizontal;
delete resultProps.paddingVertical;
delete resultProps.marginHorizontal;
delete resultProps.marginVertical;
delete resultProps.borderHorizontal;
delete resultProps.borderVertical;
return result as T;
}
/**
* Removes unnecessary style related properties so only layoutCss contains them.
* @param nodeProps properties to clean
* @returns only component-specific properties
*/
export function removeStylesFromProps(
nodeProps: Record<string, any>,
) {
if (nodeProps.hasOwnProperty("style")) {
delete nodeProps["style"];
}
if (nodeProps.hasOwnProperty("class")) {
delete nodeProps["class"];
}
const filterKeys = layoutOptionKeys;
return Object.fromEntries(
Object.entries(nodeProps).filter(([key]) => !filterKeys.includes(key)),
);
}
type NodeProps = Record<string, any>;
type ResourceUrlExtractor = (url?: string) => string | undefined;
/**
* Extracts props that can either be regular properties or URL resources.
* It also removes layoutCss props from regular properties fed into it.
* @param extractValue Value extractor function
* @param extractResourceUrl URL resource extractor function that specifies which props are URL resources
* @param layoutCss Component styles
* @param props Component props
* @returns properties that are resolved and cleaned of CSS styles
*/
export class PropsTrasform<T extends NodeProps> {
private nodeProps: T;
private extractValue: ValueExtractor;
private extractResourceUrl: ResourceUrlExtractor;
private usedKeys: (keyof T)[] = [];
constructor(
extractValue: ValueExtractor,
extractResourceUrl: ResourceUrlExtractor,
props: T,
) {
this.extractValue = extractValue;
this.extractResourceUrl = extractResourceUrl;
this.nodeProps = removeStylesFromProps(props) as T;
}
private mapValues(keys: (keyof T)[], fn: (value: any) => any) {
this.usedKeys = Array.from(new Set(this.usedKeys.concat(keys)));
return Object.fromEntries(keys.map((key) => [key, fn(this.nodeProps[key])]));
}
asValue<K extends keyof T>(...key: K[]) {
return this.mapValues(key, this.extractValue) as T;
}
asUrlResource<K extends keyof T>(...key: K[]) {
return this.mapValues(key, this.extractResourceUrl) as Record<K, string | undefined>;
}
asBoolean<K extends keyof T>(...key: K[]) {
return this.mapValues(key, this.extractValue.asBoolean) as Record<
string,
ReturnType<ValueExtractor["asBoolean"]>
>;
}
asOptionalBoolean<K extends keyof T>(...key: K[]) {
return this.mapValues(key, this.extractValue.asOptionalBoolean) as Record<
K,
ReturnType<ValueExtractor["asOptionalBoolean"]>
>;
}
asString<K extends keyof T>(...key: K[]) {
return this.mapValues(key, this.extractValue.asString) as Record<
string,
ReturnType<ValueExtractor["asString"]>
>;
}
asOptionalString<K extends keyof T>(...key: K[]) {
return this.mapValues(key, this.extractValue.asOptionalString) as Record<
string,
ReturnType<ValueExtractor["asOptionalString"]>
>;
}
asOptionalStringArray<K extends keyof T>(...key: K[]) {
return this.mapValues(key, this.extractValue.asOptionalString) as Record<
string,
ReturnType<ValueExtractor["asOptionalStringArray"]>
>;
}
asDisplayText<K extends keyof T>(...key: K[]) {
return this.mapValues(key, this.extractValue.asDisplayText) as Record<
string,
ReturnType<ValueExtractor["asDisplayText"]>
>;
}
asNumber<K extends keyof T>(...key: K[]) {
return this.mapValues(key, this.extractValue.asNumber) as Record<
string,
ReturnType<ValueExtractor["asNumber"]>
>;
}
asOptionalNumber<K extends keyof T>(...key: K[]) {
return this.mapValues(key, this.extractValue.asOptionalNumber) as Record<
K,
ReturnType<ValueExtractor["asOptionalNumber"]>
>;
}
asSize<K extends keyof T>(...key: K[]) {
return this.mapValues(key, this.extractValue.asSize) as Record<
K,
ReturnType<ValueExtractor["asSize"]>
>;
}
/**
* Resolves props which have not been used yet.
* If all keys have been referenced before this function is called, an empty object is returned.
*
* @returns props that have not been used yet
*/
asRest(): T {
const filteredKeys = Object.keys(this.nodeProps).filter(
(propKey) => !this.usedKeys.includes(propKey as keyof T),
);
return this.mapValues(filteredKeys, this.extractValue) as T;
}
}
```
--------------------------------------------------------------------------------
/xmlui/dev-docs/next/configuration-management-enhancement-summary.md:
--------------------------------------------------------------------------------
```markdown
# Configuration Management Enhancement - Implementation Summary
## Overview
This document summarizes the implementation of enhanced configuration management for the XMLUI documentation generation scripts. This refactoring addressed scattered configuration loading patterns, added schema validation, standardized path resolution, and created a comprehensive configuration management system.
## Problems Addressed
### 1. Inconsistent Configuration Loading
**Before:** Each script manually loaded configuration files with inconsistent error handling and path resolution.
- Basic JSON parsing without validation
- Hard-coded path construction using `join()`
- Inconsistent error messages and handling
- No schema validation or type checking
**After:** Centralized configuration management with comprehensive features.
### 2. Hard-coded Path Construction
**Before:** Path construction scattered throughout scripts using manual `join()` calls.
- Magic string paths embedded in scripts
- No standardized base path resolution
- Project structure dependencies hard-coded
**After:** Intelligent path resolution with multiple strategies and standardized output locations.
### 3. No Configuration Validation
**Before:** No validation of configuration file contents.
- Runtime errors on invalid configurations
- No type checking or schema validation
- Unclear error messages for configuration issues
**After:** Comprehensive schema validation with clear error messages.
## Enhanced Configuration Management System
### Core Components
#### 1. **ConfigurationManager Class**
Centralized configuration loading with advanced features:
- **Schema validation** against predefined schemas
- **Search path resolution** - automatic file discovery
- **Error handling** with clear, actionable messages
- **Transformation support** for data preprocessing
- **Environment variable integration** for overrides
#### 2. **PathResolver Class**
Intelligent path resolution system:
- **Project root detection** - automatically finds project base
- **Multiple resolution strategies** - script, project, cwd relative
- **Standard output paths** - predefined locations for generated content
- **Cross-platform compatibility** - handles Windows/Unix path differences
#### 3. **ConfigValidator Class**
Comprehensive validation system:
- **Schema-based validation** with detailed error messages
- **Type checking** for properties and nested values
- **Allowed values validation** for enums and constrained fields
- **Default value application** for missing optional properties
### Configuration Schemas
#### **Components Configuration Schema:**
```javascript
{
required: ["excludeComponentStatuses"],
optional: ["includeInternalComponents", "sortOrder", "customTemplates"],
properties: {
excludeComponentStatuses: {
type: "array",
itemType: "string",
allowedValues: ["internal", "experimental", "deprecated", "stable"]
},
includeInternalComponents: { type: "boolean", default: false },
sortOrder: {
type: "string",
allowedValues: ["alphabetical", "status", "category"],
default: "alphabetical"
}
}
}
```
#### **Extensions Configuration Schema:**
```javascript
{
required: ["packageNames"],
optional: ["excludePackages", "includeDevPackages"],
properties: {
packageNames: { type: "array", itemType: "string" },
excludePackages: { type: "array", itemType: "string", default: [] },
includeDevPackages: { type: "boolean", default: false }
}
}
```
#### **Documentation Generator Schema:**
```javascript
{
required: ["outputFormat"],
optional: ["includeExamples", "generateMetadata", "verboseLogging"],
properties: {
outputFormat: {
type: "string",
allowedValues: ["markdown", "html", "json"],
default: "markdown"
},
includeExamples: { type: "boolean", default: true },
generateMetadata: { type: "boolean", default: true },
verboseLogging: { type: "boolean", default: false }
}
}
```
### Path Resolution Features
#### **Smart Search Paths:**
The system automatically searches for configuration files in:
1. Current working directory (`./`)
2. Script directory (`./scripts/generate-docs/`)
3. Docs config directory (`./docs/config/`)
4. Project root config (`./config/`)
#### **Standard Output Paths:**
Centralized definition of output locations:
```javascript
{
themes: "dist/themes",
components: "docs/content/components",
extensions: "docs/content/extensions",
pages: "docs/pages",
metadata: "dist/metadata",
downloads: "docs/public/downloads"
}
```
#### **Flexible Resolution Strategies:**
- `script` - Relative to script directory
- `project` - Relative to project root
- `cwd` - Relative to current working directory
## Scripts Updated
### 1. **create-theme-files.mjs**
**Changes:**
- Uses `pathResolver.getOutputPaths().themes` instead of manual path construction
- Leverages enhanced configuration management for future theme configuration needs
**Benefits:**
- Automatic output directory resolution
- Consistent with other scripts' path handling
- Prepared for future theme configuration enhancements
### 2. **get-docs.mjs**
**Changes:**
- Uses `configManager.loadComponentsConfig()` and `configManager.loadExtensionsConfig()`
- Uses `pathResolver.resolvePath()` for component and example folder paths
- Consistent configuration loading with validation
**Benefits:**
- Automatic schema validation for configuration files
- Standardized error handling for configuration issues
- Simplified path construction logic
### 3. **input-handler.mjs**
**Changes:**
- Refactored to use enhanced configuration manager as backend
- Maintains backward compatibility with existing API
- Enhanced error handling and validation
**Benefits:**
- Improved error messages and validation
- Consistent behavior with other configuration loading
- Future-proof for additional configuration features
## Features and Benefits
### **Enhanced Error Handling**
- **Clear error messages** with specific information about what went wrong
- **File not found handling** with search path information
- **JSON syntax error detection** with line number information when possible
- **Permission error handling** for configuration files
### **Configuration Validation**
- **Schema-based validation** ensures configuration files are correct
- **Type checking** prevents runtime errors from wrong data types
- **Allowed values validation** for enumerated fields
- **Required property checking** with clear error messages
### **Path Resolution Intelligence**
- **Automatic project root detection** by looking for package.json
- **Multiple search strategies** for finding configuration files
- **Cross-platform path handling** for Windows/Unix compatibility
- **Standardized output directories** across all scripts
### **Developer Experience Improvements**
- **Detailed logging** for configuration loading process
- **Environment variable overrides** for CI/CD and development
- **Default value application** for missing optional settings
- **Backward compatibility** with existing configuration files
## Usage Examples
### **Basic Configuration Loading:**
```javascript
import { configManager } from "./configuration-management.mjs";
// Load and validate components configuration
const config = await configManager.loadComponentsConfig();
// Load with custom path and validation
const customConfig = await configManager.loadConfig("./custom-config.json", "COMPONENTS");
```
### **Path Resolution:**
```javascript
import { pathResolver } from "./configuration-management.mjs";
// Get standard output paths
const outputs = pathResolver.getOutputPaths();
const themeDir = outputs.themes;
// Resolve custom paths
const componentDir = pathResolver.resolvePath("src/components", "project");
const scriptFile = pathResolver.resolvePath("./script.mjs", "script");
```
### **Environment Integration:**
```javascript
// Override configuration with environment variables
const config = configManager.mergeWithEnvironment(baseConfig, "XMLUI_DOCS_");
// XMLUI_DOCS_VERBOSE_LOGGING=true becomes config.verboseLogging = true
```
## Validation Results
All enhanced configuration management features have been thoroughly tested:
### **Functionality Testing**
- ✅ `npm run export-themes` - Works with new path resolution
- ✅ Configuration loading - Handles missing files gracefully
- ✅ Schema validation - Rejects invalid configurations with clear errors
- ✅ Path resolution - Correctly resolves all path types
### **Error Handling Testing**
- ✅ **Missing files** - Clear error messages with search paths
- ✅ **Invalid JSON** - Syntax error reporting with file information
- ✅ **Schema violations** - Detailed validation error messages
- ✅ **Permission errors** - Appropriate error handling
### **Backward Compatibility**
- ✅ **Existing configurations** - All current config files work unchanged
- ✅ **API compatibility** - `loadConfig()` function maintains same interface
- ✅ **Output consistency** - Generated files identical to pre-refactoring
## Performance and Resource Impact
### **Performance Characteristics**
- **No performance degradation** - Configuration loading is still fast
- **Caching opportunities** - Path resolution results can be cached
- **Memory efficiency** - No significant memory overhead
- **Startup time** - Minimal impact on script startup time
### **Resource Usage**
- **File system** - More intelligent file searching reduces redundant operations
- **Error recovery** - Better error handling reduces failed script runs
- **Development time** - Significantly reduced debugging time for configuration issues
## Future Enhancement Opportunities
### **Phase 1 Extensions (Immediate)**
- Add configuration file hot-reloading for development
- Implement configuration merging from multiple sources
- Add JSON Schema file generation for IDE support
### **Phase 2 Extensions (Short-term)**
- YAML configuration file support
- Configuration templates and generators
- Advanced path aliasing and variable substitution
### **Phase 3 Extensions (Long-term)**
- Configuration management UI/CLI tools
- Integration with external configuration stores
- Advanced validation rules and custom validators
## Risk Assessment
### **Risk Mitigation Achieved**
- ✅ **Zero breaking changes** - Full backward compatibility maintained
- ✅ **Graceful degradation** - Missing configurations handled appropriately
- ✅ **Clear error messages** - Debugging and troubleshooting significantly improved
- ✅ **Comprehensive testing** - All functionality validated
### **Benefits vs. Risks**
- **High benefits** - Significantly improved maintainability and developer experience
- **Low risk** - No breaking changes, comprehensive testing
- **Future-proof** - Foundation for additional configuration enhancements
- **Immediate value** - Better error handling and validation right away
## Conclusion
The configuration management enhancement has successfully:
1. **Centralized configuration loading** with comprehensive error handling
2. **Added schema validation** for all configuration types
3. **Implemented intelligent path resolution** with multiple strategies
4. **Maintained full backward compatibility** while adding powerful new features
5. **Significantly improved developer experience** with better error messages and validation
The enhanced configuration management system provides a robust foundation for future configuration needs while immediately improving the reliability and maintainability of the documentation generation scripts. All changes are low-risk, well-tested, and provide immediate value to developers working with the system.
---
*Refactoring completed: 2024*
*Impact: High - significantly improved configuration reliability and developer experience*
*Risk: Low - no breaking changes, comprehensive validation*
*Status: Complete and validated* ✅
```