This is page 87 of 186. Use http://codebase.md/xmlui-org/xmlui/tools/vscode/resources/%7Bsrc%7D?lines=true&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/Charts/BarChart/BarChart.spec.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { expect, test } from "../../../testing/fixtures";
2 |
3 | // Test data helpers - using proper XMLUI data format
4 | const sampleData = `[
5 | { name: 'Jan', sales: 100, profit: 20 },
6 | { name: 'Feb', sales: 150, profit: 30 },
7 | { name: 'Mar', sales: 120, profit: 25 },
8 | { name: 'Apr', sales: 180, profit: 40 }
9 | ]`;
10 |
11 | const emptyData = `[]`;
12 |
13 | const singlePointData = `[
14 | { name: 'Jan', sales: 100, profit: 20 }
15 | ]`;
16 |
17 | // Chart selectors - updated for actual Recharts BarChart output
18 | const chartRoot = ".recharts-responsive-container";
19 | const chartSvg = ".recharts-surface";
20 | const barsSelector = ".recharts-bar";
21 | const legendSelector = ".recharts-legend-wrapper";
22 | const tooltipSelector = ".recharts-tooltip-wrapper";
23 | const xAxisSelector = ".recharts-xAxis";
24 | const yAxisSelector = ".recharts-yAxis";
25 | const xTicksSelector = ".recharts-xAxis .recharts-cartesian-axis-tick-value";
26 | const yTicksSelector = ".recharts-yAxis .recharts-cartesian-axis-tick-value";
27 |
28 | // --- Smoke Tests
29 |
30 | test.describe("smoke tests", { tag: "@smoke" }, () => {
31 | test("component renders with basic props", async ({ initTestBed, page }) => {
32 | await initTestBed(`
33 | <BarChart
34 | xKey="name"
35 | data="{${sampleData}}"
36 | yKeys="{['sales', 'profit']}"
37 | width="600px"
38 | height="400px"
39 | />
40 | `);
41 |
42 | await page.waitForSelector(chartRoot, { timeout: 10000 });
43 | await expect(page.locator(chartRoot)).toBeVisible();
44 | });
45 |
46 | test("renders correct number of bar series", async ({ initTestBed, page }) => {
47 | await initTestBed(`
48 | <BarChart
49 | xKey="name"
50 | data="{${sampleData}}"
51 | yKeys="{['sales', 'profit']}"
52 | width="600px"
53 | height="400px"
54 | />
55 | `);
56 |
57 | await page.waitForSelector(chartRoot, { timeout: 10000 });
58 | // Should have 2 bar series (sales and profit)
59 | await expect(page.locator(barsSelector)).toHaveCount(2);
60 | });
61 | });
62 |
63 | // --- Data Handling Tests
64 |
65 | test.describe("data handling", () => {
66 | test("renders with empty data array", async ({ initTestBed, page }) => {
67 | await initTestBed(`
68 | <BarChart
69 | xKey="name"
70 | data="{${emptyData}}"
71 | yKeys="{['sales', 'profit']}"
72 | width="600px"
73 | height="400px"
74 | />
75 | `);
76 |
77 | await page.waitForSelector(chartRoot, { timeout: 10000 });
78 | await expect(page.locator(barsSelector)).toHaveCount(0);
79 | });
80 |
81 | test("renders with single data point", async ({ initTestBed, page }) => {
82 | await initTestBed(`
83 | <BarChart
84 | xKey="name"
85 | data="{${singlePointData}}"
86 | yKeys="{['sales', 'profit']}"
87 | width="600px"
88 | height="400px"
89 | />
90 | `);
91 |
92 | await page.waitForSelector(chartRoot, { timeout: 10000 });
93 | await expect(page.locator(barsSelector)).toHaveCount(2);
94 | });
95 |
96 | test("handles non-array data gracefully", async ({ initTestBed, page }) => {
97 | await initTestBed(`
98 | <BarChart
99 | xKey="name"
100 | data="{null}"
101 | yKeys="{['sales', 'profit']}"
102 | width="600px"
103 | height="400px"
104 | />
105 | `);
106 |
107 | await page.waitForSelector(chartRoot, { timeout: 10000 });
108 | await expect(page.locator(barsSelector)).toHaveCount(0);
109 | });
110 | });
111 |
112 | // --- Layout Tests (BarChart specific)
113 |
114 | test.describe("layout", () => {
115 | test("renders vertical layout by default", async ({ initTestBed, page }) => {
116 | await initTestBed(`
117 | <BarChart
118 | xKey="name"
119 | data="{${sampleData}}"
120 | yKeys="{['sales', 'profit']}"
121 | width="600px"
122 | height="400px"
123 | />
124 | `);
125 |
126 | await page.waitForSelector(chartRoot, { timeout: 10000 });
127 | await expect(page.locator(chartRoot)).toBeVisible();
128 | // In vertical layout, bars should be oriented vertically
129 | await expect(page.locator(barsSelector)).toHaveCount(2);
130 | });
131 |
132 | test("renders horizontal layout when specified", async ({ initTestBed, page }) => {
133 | await initTestBed(`
134 | <BarChart
135 | xKey="name"
136 | data="{${sampleData}}"
137 | yKeys="{['sales', 'profit']}"
138 | orientation="horizontal"
139 | width="600px"
140 | height="400px"
141 | />
142 | `);
143 |
144 | await page.waitForSelector(chartRoot, { timeout: 10000 });
145 | await expect(page.locator(chartRoot)).toBeVisible();
146 | // In horizontal layout, bars should be oriented horizontally
147 | await expect(page.locator(barsSelector)).toHaveCount(2);
148 | });
149 | });
150 |
151 | // --- Stacked Tests (BarChart specific)
152 |
153 | test.describe("stacked", () => {
154 | test("renders grouped bars by default (stacked=false)", async ({ initTestBed, page }) => {
155 | await initTestBed(`
156 | <BarChart
157 | xKey="name"
158 | data="{${sampleData}}"
159 | yKeys="{['sales', 'profit']}"
160 | width="600px"
161 | height="400px"
162 | />
163 | `);
164 |
165 | await page.waitForSelector(chartRoot, { timeout: 10000 });
166 | await expect(page.locator(barsSelector)).toHaveCount(2);
167 | });
168 |
169 | test("renders stacked bars when stacked=true", async ({ initTestBed, page }) => {
170 | await initTestBed(`
171 | <BarChart
172 | xKey="name"
173 | data="{${sampleData}}"
174 | yKeys="{['sales', 'profit']}"
175 | stacked
176 | width="600px"
177 | height="400px"
178 | />
179 | `);
180 |
181 | await page.waitForSelector(chartRoot, { timeout: 10000 });
182 | await expect(page.locator(barsSelector)).toHaveCount(2);
183 | });
184 | });
185 |
186 | // --- Legend Tests
187 |
188 | test.describe("legend", () => {
189 | test("legend is hidden by default", async ({ initTestBed, page }) => {
190 | await initTestBed(`
191 | <BarChart
192 | xKey="name"
193 | data="{${sampleData}}"
194 | yKeys="{['sales', 'profit']}"
195 | width="600px"
196 | height="400px"
197 | />
198 | `);
199 |
200 | await page.waitForSelector(chartRoot, { timeout: 10000 });
201 | await expect(page.locator(legendSelector)).not.toBeVisible();
202 | });
203 |
204 | test("legend is shown when showLegend is true", async ({ initTestBed, page }) => {
205 | await initTestBed(`
206 | <BarChart
207 | xKey="name"
208 | data="{${sampleData}}"
209 | yKeys="{['sales', 'profit']}"
210 | showLegend
211 | width="600px"
212 | height="400px"
213 | />
214 | `);
215 |
216 | await page.waitForSelector(chartRoot, { timeout: 10000 });
217 | await expect(page.locator(legendSelector)).toBeVisible();
218 | });
219 | });
220 |
221 | // --- Tooltip Tests
222 |
223 | test.describe("tooltip", () => {
224 | test("tooltip appears on hover by default", async ({ initTestBed, page }) => {
225 | await initTestBed(`
226 | <BarChart
227 | xKey="name"
228 | data="{${sampleData}}"
229 | yKeys="{['sales', 'profit']}"
230 | width="600px"
231 | height="400px"
232 | />
233 | `);
234 |
235 | await page.waitForSelector(chartRoot, { timeout: 10000 });
236 | const chart = page.locator(chartSvg).first();
237 | await chart.hover({ position: { x: 200, y: 100 } });
238 |
239 | // Wait for tooltip to appear
240 | await page.waitForTimeout(500);
241 | await expect(page.locator(tooltipSelector)).toBeVisible();
242 | });
243 |
244 | test("tooltip is hidden when hideTooltip is true", async ({ initTestBed, page }) => {
245 | await initTestBed(`
246 | <BarChart
247 | xKey="name"
248 | data="{${sampleData}}"
249 | yKeys="{['sales', 'profit']}"
250 | hideTooltip
251 | width="600px"
252 | height="400px"
253 | />
254 | `);
255 |
256 | await page.waitForSelector(chartRoot, { timeout: 10000 });
257 | const chart = page.locator(chartSvg).first();
258 | await chart.hover({ position: { x: 200, y: 100 } });
259 |
260 | await page.waitForTimeout(500);
261 | await expect(page.locator(tooltipSelector)).not.toBeVisible();
262 | });
263 | });
264 |
265 | // --- Axis Tests
266 |
267 | test.describe("x-axis", () => {
268 | test("x-axis is shown by default", async ({ initTestBed, page }) => {
269 | await initTestBed(`
270 | <BarChart
271 | xKey="name"
272 | data="{${sampleData}}"
273 | yKeys="{['sales', 'profit']}"
274 | width="600px"
275 | height="400px"
276 | />
277 | `);
278 |
279 | await page.waitForSelector(chartRoot, { timeout: 10000 });
280 | await expect(page.locator(xAxisSelector)).toBeVisible();
281 | // Check that at least one tick is present
282 | const tickCount = await page.locator(xTicksSelector).count();
283 | expect(tickCount).toBeGreaterThan(0);
284 | });
285 |
286 | test("x-axis is hidden when hideX is true", async ({ initTestBed, page }) => {
287 | await initTestBed(`
288 | <BarChart
289 | xKey="name"
290 | data="{${sampleData}}"
291 | yKeys="{['sales', 'profit']}"
292 | hideX
293 | width="600px"
294 | height="400px"
295 | />
296 | `);
297 |
298 | await page.waitForSelector(chartRoot, { timeout: 10000 });
299 | await expect(page.locator(xTicksSelector)).toHaveCount(0);
300 | });
301 |
302 | test("x-axis ticks are hidden when hideTickX is true", async ({ initTestBed, page }) => {
303 | await initTestBed(`
304 | <BarChart
305 | xKey="name"
306 | data="{${sampleData}}"
307 | yKeys="{['sales', 'profit']}"
308 | hideTickX
309 | width="600px"
310 | height="400px"
311 | />
312 | `);
313 |
314 | await page.waitForSelector(chartRoot, { timeout: 10000 });
315 | // When hideTickX is true, ticks should not be rendered in DOM
316 | await expect(page.locator(xTicksSelector)).toHaveCount(0);
317 | // But the axis line itself should still be present (though not visible due to tickLine=false)
318 | await expect(page.locator(xAxisSelector)).toBeAttached();
319 | });
320 | });
321 |
322 | test.describe("y-axis", () => {
323 | test("y-axis is shown by default", async ({ initTestBed, page }) => {
324 | await initTestBed(`
325 | <BarChart
326 | xKey="name"
327 | data="{${sampleData}}"
328 | yKeys="{['sales', 'profit']}"
329 | width="600px"
330 | height="400px"
331 | />
332 | `);
333 |
334 | await page.waitForSelector(chartRoot, { timeout: 10000 });
335 | await expect(page.locator(yAxisSelector)).toBeVisible();
336 | // Check that at least one tick is present
337 | const tickCount = await page.locator(yTicksSelector).count();
338 | expect(tickCount).toBeGreaterThan(0);
339 | });
340 |
341 | test("y-axis is hidden when hideY is true", async ({ initTestBed, page }) => {
342 | await initTestBed(`
343 | <BarChart
344 | xKey="name"
345 | data="{${sampleData}}"
346 | yKeys="{['sales', 'profit']}"
347 | hideY
348 | width="600px"
349 | height="400px"
350 | />
351 | `);
352 |
353 | await page.waitForSelector(chartRoot, { timeout: 10000 });
354 | await expect(page.locator(yTicksSelector)).toHaveCount(0);
355 | });
356 |
357 | test("y-axis ticks are hidden when hideTickY is true", async ({ initTestBed, page }) => {
358 | await initTestBed(`
359 | <BarChart
360 | xKey="name"
361 | data="{${sampleData}}"
362 | yKeys="{['sales', 'profit']}"
363 | hideTickY
364 | width="600px"
365 | height="400px"
366 | />
367 | `);
368 |
369 | await page.waitForSelector(chartRoot, { timeout: 10000 });
370 | // When hideTickY is true, ticks should not be rendered in DOM
371 | // But the axis line itself should still be present (though not visible due to tickLine=false)
372 | await expect(page.locator(yAxisSelector)).toBeAttached();
373 | await expect(page.locator(yTicksSelector)).toHaveCount(0);
374 | });
375 | });
376 |
377 | // --- Formatter Tests
378 |
379 | test.describe("formatters", () => {
380 | test("applies tickFormatterX when provided", async ({ initTestBed, page }) => {
381 | await initTestBed(`
382 | <BarChart
383 | xKey="name"
384 | data="{${sampleData}}"
385 | yKeys="{['sales', 'profit']}"
386 | tickFormatterX="{(value) => value + ' (X)'}"
387 | width="600px"
388 | height="400px"
389 | />
390 | `);
391 |
392 | await page.waitForSelector(chartRoot, { timeout: 10000 });
393 | // Check that formatted text appears in x-axis ticks
394 | await expect(page.locator(xTicksSelector).first()).toContainText("(X)");
395 | });
396 |
397 | test("applies tickFormatterY when provided", async ({ initTestBed, page }) => {
398 | await initTestBed(`
399 | <BarChart
400 | xKey="name"
401 | data="{${sampleData}}"
402 | yKeys="{['sales', 'profit']}"
403 | tickFormatterY="{(value) => '$' + value}"
404 | width="600px"
405 | height="400px"
406 | />
407 | `);
408 |
409 | await page.waitForSelector(chartRoot, { timeout: 10000 });
410 | // Check that formatted text appears in y-axis ticks
411 | await expect(page.locator(yTicksSelector).first()).toContainText("$");
412 | });
413 | });
414 |
415 | // --- Margin Tests
416 |
417 | test.describe("margins", () => {
418 | test("applies custom margins", async ({ initTestBed, page }) => {
419 | await initTestBed(`
420 | <BarChart
421 | xKey="name"
422 | data="{${sampleData}}"
423 | yKeys="{['sales', 'profit']}"
424 | marginTop="{20}"
425 | marginRight="{30}"
426 | marginBottom="{40}"
427 | marginLeft="{50}"
428 | width="600px"
429 | height="400px"
430 | />
431 | `);
432 |
433 | await page.waitForSelector(chartRoot, { timeout: 10000 });
434 | // Chart should render with custom margins applied
435 | await expect(page.locator(chartRoot)).toBeVisible();
436 | });
437 | });
438 |
439 | // --- Responsive Behavior Tests
440 |
441 | test.describe("responsive behavior", () => {
442 | test("enters mini mode with very small container height", async ({ initTestBed, page }) => {
443 | await initTestBed(`
444 | <BarChart
445 | xKey="name"
446 | data="{${sampleData}}"
447 | yKeys="{['sales', 'profit']}"
448 | width="600px"
449 | height="50px"
450 | />
451 | `);
452 |
453 | await page.waitForSelector(chartRoot, { timeout: 10000 });
454 | // In mini mode, x-axis ticks should be hidden (tick=false)
455 | await expect(page.locator(xTicksSelector)).toHaveCount(0);
456 | // Y-axis ticks should also be hidden in mini mode
457 | await expect(page.locator(yTicksSelector)).toHaveCount(0);
458 | // Chart should still render the bars
459 | await expect(page.locator(barsSelector)).toHaveCount(2);
460 | });
461 |
462 | test("renders normally with adequate height", async ({ initTestBed, page }) => {
463 | await initTestBed(`
464 | <BarChart
465 | xKey="name"
466 | data="{${sampleData}}"
467 | yKeys="{['sales', 'profit']}"
468 | width="600px"
469 | height="200px"
470 | />
471 | `);
472 |
473 | await page.waitForSelector(chartRoot, { timeout: 10000 });
474 | // With adequate height, should have ticks
475 | const tickCount = await page.locator(xTicksSelector).count();
476 | expect(tickCount).toBeGreaterThan(0);
477 | });
478 |
479 | test("handles narrow containers gracefully", async ({ initTestBed, page }) => {
480 | await initTestBed(`
481 | <BarChart
482 | xKey="name"
483 | data="{${sampleData}}"
484 | yKeys="{['sales', 'profit']}"
485 | width="100px"
486 | height="400px"
487 | />
488 | `);
489 |
490 | await page.waitForSelector(chartRoot, { timeout: 10000 });
491 | // Chart should still render even in narrow container
492 | await expect(page.locator(chartRoot)).toBeVisible();
493 | await expect(page.locator(barsSelector)).toHaveCount(2);
494 | });
495 | });
496 |
497 | // --- Layout-specific Tests (BarChart specific)
498 |
499 | test.describe("layout-specific behavior", () => {
500 | test("vertical layout has correct axis orientation", async ({ initTestBed, page }) => {
501 | await initTestBed(`
502 | <BarChart
503 | xKey="name"
504 | data="{${sampleData}}"
505 | yKeys="{['sales', 'profit']}"
506 | orientation="vertical"
507 | width="600px"
508 | height="400px"
509 | />
510 | `);
511 |
512 | await page.waitForSelector(chartRoot, { timeout: 10000 });
513 | // In vertical layout, category names should be on X-axis, values on Y-axis
514 | await expect(page.locator(xAxisSelector)).toBeVisible();
515 | await expect(page.locator(yAxisSelector)).toBeVisible();
516 | });
517 |
518 | test("horizontal layout has correct axis orientation", async ({ initTestBed, page }) => {
519 | await initTestBed(`
520 | <BarChart
521 | xKey="name"
522 | data="{${sampleData}}"
523 | yKeys="{['sales', 'profit']}"
524 | orientation="horizontal"
525 | width="600px"
526 | height="400px"
527 | />
528 | `);
529 |
530 | await page.waitForSelector(chartRoot, { timeout: 10000 });
531 | // In horizontal layout, category names should be on Y-axis, values on X-axis
532 | await expect(page.locator(xAxisSelector)).toBeVisible();
533 | await expect(page.locator(yAxisSelector)).toBeVisible();
534 | });
535 | });
536 |
537 | // --- Edge Cases
538 |
539 | test.describe("edge cases", () => {
540 | test("handles missing yKeys gracefully", async ({ initTestBed, page }) => {
541 | await initTestBed(`
542 | <BarChart
543 | xKey="name"
544 | data="{${sampleData}}"
545 | yKeys="{[]}"
546 | width="600px"
547 | height="400px"
548 | />
549 | `);
550 |
551 | await page.waitForSelector(chartRoot, { timeout: 10000 });
552 | await expect(page.locator(barsSelector)).toHaveCount(0);
553 | });
554 |
555 | test("handles missing xKey gracefully", async ({ initTestBed, page }) => {
556 | await initTestBed(`
557 | <BarChart
558 | data="{${sampleData}}"
559 | yKeys="{['sales', 'profit']}"
560 | width="600px"
561 | height="400px"
562 | />
563 | `);
564 |
565 | await page.waitForSelector(chartRoot, { timeout: 10000 });
566 | // Should still render but may not have proper category labels
567 | await expect(page.locator(chartRoot)).toBeVisible();
568 | });
569 |
570 | test("handles data with missing values", async ({ initTestBed, page }) => {
571 | const dataWithMissingValues = `[
572 | { name: 'Jan', sales: 100, profit: 20 },
573 | { name: 'Feb', sales: null, profit: 30 },
574 | { name: 'Mar', sales: 120 },
575 | { name: 'Apr', sales: 180, profit: 40 }
576 | ]`;
577 |
578 | await initTestBed(`
579 | <BarChart
580 | xKey="name"
581 | data="{${dataWithMissingValues}}"
582 | yKeys="{['sales', 'profit']}"
583 | width="600px"
584 | height="400px"
585 | />
586 | `);
587 |
588 | await page.waitForSelector(chartRoot, { timeout: 10000 });
589 | // Should render bars for available data points
590 | await expect(page.locator(barsSelector)).toHaveCount(2);
591 | });
592 | });
593 |
594 | // --- Tooltip Template Tests
595 |
596 | test.describe("tooltipTemplate", () => {
597 | test("renders custom tooltip template", async ({ initTestBed, page }) => {
598 | await initTestBed(`
599 | <BarChart
600 | xKey="name"
601 | data="{${sampleData}}"
602 | yKeys="{['sales', 'profit']}"
603 | width="600px"
604 | height="400px"
605 | >
606 | <property name="tooltipTemplate">
607 | <VStack>
608 | <Text>Custom Tooltip</Text>
609 | <Text>Label: {$tooltip.label}</Text>
610 | <Text>Active: {$tooltip.active}</Text>
611 | </VStack>
612 | </property>
613 | </BarChart>
614 | `);
615 |
616 | await page.waitForSelector(chartRoot, { timeout: 10000 });
617 | const chart = page.locator(chartSvg).first();
618 | await chart.hover({ position: { x: 200, y: 100 } });
619 |
620 | // Wait for tooltip to appear
621 | await page.waitForTimeout(500);
622 | await expect(page.locator(tooltipSelector)).toBeVisible();
623 |
624 | // Check for custom tooltip content
625 | await expect(page.getByText("Custom Tooltip")).toBeVisible();
626 | await expect(page.getByText(/Label:/)).toBeVisible();
627 | await expect(page.getByText(/Active:/)).toBeVisible();
628 | });
629 |
630 | test("tooltip template is not rendered when hideTooltip is true", async ({ initTestBed, page }) => {
631 | await initTestBed(`
632 | <BarChart
633 | xKey="name"
634 | data="{${sampleData}}"
635 | yKeys="{['sales', 'profit']}"
636 | hideTooltip
637 | width="600px"
638 | height="400px"
639 | >
640 | <property name="tooltipTemplate">
641 | <Text>This tooltip should not appear</Text>
642 | </property>
643 | </BarChart>
644 | `);
645 |
646 | await page.waitForSelector(chartRoot, { timeout: 10000 });
647 | const chart = page.locator(chartSvg).first();
648 | await chart.hover({ position: { x: 200, y: 100 } });
649 |
650 | await page.waitForTimeout(500);
651 | await expect(page.locator(tooltipSelector)).not.toBeVisible();
652 | await expect(page.getByText("This tooltip should not appear")).not.toBeVisible();
653 | });
654 |
655 | test("falls back to default tooltip when tooltipTemplate is not provided", async ({ initTestBed, page }) => {
656 | await initTestBed(`
657 | <BarChart
658 | xKey="name"
659 | data="{${sampleData}}"
660 | yKeys="{['sales', 'profit']}"
661 | width="600px"
662 | height="400px"
663 | />
664 | `);
665 |
666 | await page.waitForSelector(chartRoot, { timeout: 10000 });
667 | const chart = page.locator(chartSvg).first();
668 | await chart.hover({ position: { x: 200, y: 100 } });
669 |
670 | await page.waitForTimeout(500);
671 | await expect(page.locator(tooltipSelector)).toBeVisible();
672 |
673 | // Default tooltip should contain standard recharts tooltip content
674 | const tooltip = page.locator(tooltipSelector);
675 | await expect(tooltip).toBeVisible();
676 | });
677 | });
678 |
```
--------------------------------------------------------------------------------
/xmlui/src/language-server/services/format.ts:
--------------------------------------------------------------------------------
```typescript
1 | import type { FormattingOptions, TextEdit, Range, Position } from "vscode-languageserver";
2 | import { type GetText, type Node, SyntaxKind, toDbgString } from "../../parsers/xmlui-parser";
3 |
4 | type FormattingContex = {
5 | node: Node;
6 | getText: GetText;
7 | offsetToPosition: (offset: number) => Position;
8 | options: FormattingOptions;
9 | };
10 |
11 | export interface FormatOptions extends FormattingOptions {
12 | maxLineLength?: number;
13 | }
14 |
15 | export function handleDocumentFormatting({
16 | node,
17 | getText,
18 | options,
19 | offsetToPosition,
20 | }: FormattingContex): TextEdit[] | null {
21 | const formatted = format(node, getText, options);
22 |
23 | // If content is already formatted correctly, return empty array
24 | const unformattedContent = getText(node, false);
25 | if (formatted === unformattedContent) {
26 | return [];
27 | }
28 |
29 | const entireDocumentRange: Range = {
30 | start: { line: 0, character: 0 },
31 | end: offsetToPosition(unformattedContent.length),
32 | };
33 |
34 | return [
35 | {
36 | range: entireDocumentRange,
37 | newText: formatted,
38 | },
39 | ];
40 | }
41 |
42 | class XmluiFormatter {
43 | private readonly maxConsecutiveNewlines: number = 2;
44 | private readonly getText: GetText;
45 | private readonly startingNode: Node;
46 | private readonly maxLineLength: number = 80;
47 | private readonly tabSize: number;
48 | private readonly trimRespectingOptions: (str: string) => string;
49 | private indentationToken: string;
50 | private indentationLvl: number;
51 | private newlineToken: string = "\n";
52 |
53 | constructor(node: Node, getText: GetText, options: FormatOptions) {
54 | this.getText = getText;
55 | this.startingNode = node;
56 | this.indentationLvl = 0;
57 | this.tabSize = options.tabSize;
58 |
59 | this.indentationToken = options.insertSpaces ? " ".repeat(options.tabSize) : "\t";
60 | if (options.insertFinalNewline && !options.trimFinalNewlines) {
61 | this.trimRespectingOptions = (formatted: string) => {
62 | return formatted.trim() + this.newlineToken;
63 | };
64 | } else {
65 | this.trimRespectingOptions = (formatted: string) => {
66 | return formatted.trim();
67 | };
68 | }
69 | }
70 |
71 | format(): string {
72 | let formattedStr: string;
73 | if (this.startingNode.kind !== SyntaxKind.ContentListNode) {
74 | formattedStr = this.getText(this.startingNode);
75 | }
76 |
77 | formattedStr = this.printContentListNode(this.startingNode);
78 | formattedStr = this.trimRespectingOptions(formattedStr);
79 |
80 | return formattedStr;
81 | }
82 |
83 | private printContentListNode(node: Node): string {
84 | let acc: string = "";
85 | for (let i = 0; i < node.children.length; i++) {
86 | const c = node.children[i];
87 | const prevChild = i > 0 ? node.children[i - 1] : null;
88 |
89 | switch (c.kind) {
90 | case SyntaxKind.CData:
91 | case SyntaxKind.Script:
92 | case SyntaxKind.ElementNode: {
93 | acc += this.printTagLikeTriviaInContent(prevChild, c);
94 |
95 | acc += this.printTagLike(c);
96 | break;
97 | }
98 |
99 | case SyntaxKind.StringLiteral:
100 | case SyntaxKind.TextNode: {
101 | const formattedContent = this.printContentString(c);
102 | if (formattedContent !== "") {
103 | acc += this.newlineToken;
104 | acc += this.indent(this.indentationLvl);
105 | acc += formattedContent;
106 | } else {
107 | acc.trimEnd();
108 | }
109 | break;
110 | }
111 | case SyntaxKind.ErrorNode:
112 | acc += this.getText(c, false);
113 | break;
114 |
115 | case SyntaxKind.EndOfFileToken: {
116 | const collapsedTrivias = this.collapseBlankLines(c.triviaBefore ?? []);
117 | acc += this.addWsToCollapsedTriviaInContentList(collapsedTrivias, prevChild);
118 | break;
119 | }
120 | }
121 | }
122 | return acc;
123 | }
124 |
125 | printTagLikeTriviaInContent(prevSibling: Node | null, node: Node) {
126 | if (!prevSibling) {
127 | return this.printTriviaBeforeFirstTaglikeInContent(node);
128 | } else if (prevSibling.isTagLike()) {
129 | return this.printTriviaBetweenSiblingTaglikes(node, prevSibling);
130 | } else if (
131 | prevSibling.kind === SyntaxKind.TextNode ||
132 | prevSibling.kind === SyntaxKind.StringLiteral
133 | ) {
134 | return this.printTriviaBetweenTextAndTaglike(prevSibling, node);
135 | } else {
136 | // prevSibling is an error node, treat is as any taglike
137 | return this.printTriviaBetweenSiblingTaglikes(node, prevSibling);
138 | }
139 | }
140 |
141 | printTriviaBetweenTextAndTaglike(textNode: Node, node: Node) {
142 | let acc = "";
143 | const trivias = node.getTriviaNodes() ?? [];
144 | const collapsedTrivias = this.collapseBlankLines(trivias);
145 | acc += this.addWsToCollapsedTriviaInContentList(collapsedTrivias, textNode);
146 | return acc;
147 | }
148 |
149 | printTriviaBeforeFirstTaglikeInContent(node: Node): string {
150 | const triviaBetweenTaglikes = node.getTriviaNodes();
151 | if (!triviaBetweenTaglikes) {
152 | if (node.start === 0) return "";
153 | return this.newlineToken + this.indent(this.indentationLvl);
154 | }
155 |
156 | const collapsedTrivias = this.collapseBlankLines(triviaBetweenTaglikes);
157 | let acc = this.addWsToCollapsedTriviaInContentList(collapsedTrivias, null);
158 | return acc;
159 | }
160 |
161 | printTriviaBetweenSiblingTaglikes(node: Node, prevNode: Node): string {
162 | const triviaBetweenTaglikes = node.getTriviaNodes();
163 | if (!triviaBetweenTaglikes) {
164 | return this.newlineToken + this.indent(this.indentationLvl);
165 | }
166 |
167 | const collapsedTrivias = this.collapseBlankLines(triviaBetweenTaglikes);
168 | let acc = this.addWsToCollapsedTriviaInContentList(collapsedTrivias, prevNode);
169 | return acc;
170 | }
171 |
172 | /* Filters out trivias so that only comments and newline remain.
173 | Respects the maximum permitted consecitive blank lines and throws out any extra consecutive newlines. */
174 | collapseBlankLines(trivias: Node[]) {
175 | let consecutiveNewlines = 0;
176 | const accTrivias: Node[] = [];
177 |
178 | for (let t of trivias) {
179 | if (t.kind === SyntaxKind.NewLineTrivia) {
180 | consecutiveNewlines++;
181 | if (consecutiveNewlines <= this.maxConsecutiveNewlines) {
182 | accTrivias.push(t);
183 | }
184 | } else if (t.kind === SyntaxKind.CommentTrivia) {
185 | consecutiveNewlines = 0;
186 | accTrivias.push(t);
187 | }
188 | }
189 |
190 | return accTrivias;
191 | }
192 |
193 | addWsToCollapsedTriviaInContentList(trivias: Node[], prevContentNode: Node | null) {
194 | let newlinesAfterText: number | null = null;
195 | let newlinesFromText: string | null = null;
196 | let prevContentIsText = false;
197 | let newlinesBeforeFirstComment = " ";
198 | if (prevContentNode) {
199 | prevContentIsText =
200 | prevContentNode.kind === SyntaxKind.StringLiteral ||
201 | prevContentNode.kind === SyntaxKind.TextNode;
202 |
203 | if (prevContentIsText) {
204 | const trailingNewlinesInText = newlinesInTrailingWhitespace(this.getText(prevContentNode));
205 | newlinesAfterText = Math.min(trailingNewlinesInText, this.maxConsecutiveNewlines);
206 |
207 | if (newlinesAfterText > 0) {
208 | newlinesFromText = this.newlineToken.repeat(newlinesAfterText);
209 | newlinesBeforeFirstComment = newlinesFromText + this.indent(this.indentationLvl);
210 | }
211 | }
212 | }
213 |
214 | let acc = "";
215 | let lastKeptTriviaIsComment = false;
216 | for (let i = 0; i < trivias.length; i++) {
217 | const t = trivias[i];
218 | switch (t.kind) {
219 | case SyntaxKind.CommentTrivia: {
220 | if (i === 0) {
221 | acc += newlinesBeforeFirstComment;
222 | acc += this.getText(t);
223 | } else {
224 | acc += this.indent(this.indentationLvl) + this.getText(t);
225 | }
226 | if (trivias[i + 1]?.kind === SyntaxKind.CommentTrivia) {
227 | acc += this.newlineToken;
228 | }
229 | lastKeptTriviaIsComment = true;
230 | break;
231 | }
232 | case SyntaxKind.NewLineTrivia: {
233 | acc += this.newlineToken;
234 | lastKeptTriviaIsComment = false;
235 | break;
236 | }
237 | }
238 | }
239 |
240 | if (lastKeptTriviaIsComment) {
241 | acc += this.newlineToken;
242 | } else if (trivias.length === 0) {
243 | acc += newlinesFromText ?? this.newlineToken;
244 | }
245 | acc += this.indent(this.indentationLvl);
246 |
247 | return acc;
248 | }
249 |
250 | addWsToCollapsedCloseNodeStartTrivia(trivias: Node[], lastContentNode: Node | null) {
251 | let newlinesAfterText: number | null = null;
252 | let lastContentIsText = false;
253 | if (lastContentNode) {
254 | if (
255 | lastContentNode.kind === SyntaxKind.StringLiteral ||
256 | lastContentNode.kind === SyntaxKind.TextNode
257 | ) {
258 | lastContentIsText = true;
259 | const trailingNewlinesInText = newlinesInTrailingWhitespace(this.getText(lastContentNode));
260 | newlinesAfterText = Math.min(trailingNewlinesInText, this.maxConsecutiveNewlines);
261 | }
262 | }
263 | const contentEndsInNewlines = lastContentIsText && newlinesAfterText > 0;
264 |
265 | let acc = "";
266 | if (contentEndsInNewlines) {
267 | acc += this.newlineToken.repeat(newlinesAfterText);
268 | }
269 |
270 | for (let i = 0; i < trivias.length; ++i) {
271 | const t = trivias[i];
272 | switch (t.kind) {
273 | case SyntaxKind.CommentTrivia: {
274 | if (i === 0 && ((lastContentIsText && newlinesAfterText === 0) || !lastContentIsText)) {
275 | acc += " ";
276 | acc += this.getText(t);
277 | } else {
278 | acc += this.indent(this.indentationLvl + 1) + this.getText(t);
279 | }
280 | if (trivias[i + 1]?.kind === SyntaxKind.CommentTrivia) {
281 | acc += this.newlineToken;
282 | }
283 | break;
284 | }
285 | case SyntaxKind.NewLineTrivia: {
286 | acc += this.newlineToken;
287 | break;
288 | }
289 | }
290 | }
291 |
292 | if (!lastContentNode && trivias.length === 0) {
293 | // leave the closing tag on the same line
294 | // when there's nothing inside it
295 | } else if (contentEndsInNewlines || trivias.at(-1)?.kind === SyntaxKind.NewLineTrivia) {
296 | acc += this.indent(this.indentationLvl);
297 | } else {
298 | acc += this.newlineToken;
299 | acc += this.indent(this.indentationLvl);
300 | }
301 |
302 | return acc;
303 | }
304 |
305 | printTagLike(node: Node): string {
306 | switch (node.kind) {
307 | case SyntaxKind.Script:
308 | case SyntaxKind.CData:
309 | return this.getText(node);
310 | default:
311 | return this.printElementNode(node);
312 | }
313 | }
314 |
315 | printElementNode(node: Node): string {
316 | let acc = "";
317 | const contentListIdx = node.children.findIndex((c) => c.kind === SyntaxKind.ContentListNode);
318 | const hasContentList = contentListIdx !== -1;
319 | const closeNodeStartIdx = node.children.findIndex((c) => c.kind === SyntaxKind.CloseNodeStart);
320 | const hasCloseNodeStart = closeNodeStartIdx !== -1;
321 |
322 | let nodesOfOpenTagNodeEndIdx: number;
323 | if (hasContentList) {
324 | nodesOfOpenTagNodeEndIdx = contentListIdx;
325 | } else if (hasCloseNodeStart) {
326 | nodesOfOpenTagNodeEndIdx = closeNodeStartIdx;
327 | } else {
328 | nodesOfOpenTagNodeEndIdx = node.children.length;
329 | }
330 |
331 | const nodesOfOpenTagNode = node.children.slice(0, nodesOfOpenTagNodeEndIdx);
332 | acc += this.printOpenTag(nodesOfOpenTagNode);
333 |
334 | let lastContentNode: Node | null = null;
335 | if (hasContentList) {
336 | const contentListNode = node.children[contentListIdx];
337 | lastContentNode = contentListNode.children.at(-1);
338 |
339 | ++this.indentationLvl;
340 | acc += this.printContentListNode(contentListNode);
341 | --this.indentationLvl;
342 | }
343 |
344 | let nodesOfCloseTagNodeStartIdx: number;
345 | if (hasCloseNodeStart) {
346 | nodesOfCloseTagNodeStartIdx = closeNodeStartIdx;
347 | } else if (hasContentList) {
348 | nodesOfCloseTagNodeStartIdx = contentListIdx + 1;
349 | } else {
350 | nodesOfCloseTagNodeStartIdx = nodesOfOpenTagNodeEndIdx + 1;
351 | }
352 | const closeTagNodes = node.children.slice(nodesOfCloseTagNodeStartIdx);
353 | acc += this.printClosingTag(closeTagNodes, lastContentNode);
354 | return acc;
355 | }
356 |
357 | private printOpenTag(tagChildren: Node[]) {
358 | let acc = "";
359 | for (let i = 0; i < tagChildren.length; ++i) {
360 | const c = tagChildren[i];
361 | switch (c.kind) {
362 | case SyntaxKind.OpenNodeStart:
363 | acc += "<";
364 | break;
365 | case SyntaxKind.CloseNodeStart:
366 | acc += "</";
367 | break;
368 | case SyntaxKind.TagNameNode:
369 | acc += this.printTagName(c);
370 | break;
371 | case SyntaxKind.NodeClose:
372 | case SyntaxKind.NodeEnd:
373 | case SyntaxKind.AttributeListNode:
374 | acc += this.printTagAfterName(tagChildren.slice(i), acc);
375 | return acc;
376 | case SyntaxKind.ErrorNode:
377 | acc += this.getText(c, false);
378 | }
379 | }
380 | return acc;
381 | }
382 |
383 | private printClosingTag(tagChildren: Node[], lastContentNode: Node | null) {
384 | let acc = "";
385 | for (let i = 0; i < tagChildren.length; ++i) {
386 | const c = tagChildren[i];
387 | switch (c.kind) {
388 | case SyntaxKind.CloseNodeStart:
389 | const collapsedTrivias = this.collapseBlankLines(c.triviaBefore ?? []);
390 | acc += this.addWsToCollapsedCloseNodeStartTrivia(collapsedTrivias, lastContentNode);
391 | acc += "</";
392 | break;
393 | case SyntaxKind.TagNameNode:
394 | acc += this.printTagName(c);
395 | break;
396 | case SyntaxKind.NodeEnd:
397 | acc += this.printTagAfterName(tagChildren.slice(i), acc);
398 | return acc;
399 | case SyntaxKind.ErrorNode:
400 | acc += this.getText(c, false);
401 | }
402 | }
403 | return acc;
404 | }
405 |
406 | private printTagAfterName(nodes: Node[], tagAcc: string): string {
407 | let acc: string = "";
408 | let closingIsNodeClose = false;
409 | let attrSegmentsFormatted: string[] = [];
410 |
411 | let closingSegmentsFormatted: string[] = [];
412 |
413 | let attrsAlreadyMultiline = false;
414 |
415 | for (let i = 0; i < nodes.length; ++i) {
416 | const c = nodes[i];
417 | switch (c.kind) {
418 | case SyntaxKind.AttributeListNode:
419 | attrSegmentsFormatted = this.printAttrList(c);
420 |
421 | attrsAlreadyMultiline = c.children.some((attr) =>
422 | attr
423 | .getTriviaNodes()
424 | ?.some((attrTrivia) => attrTrivia.kind === SyntaxKind.NewLineTrivia),
425 | );
426 | break;
427 | case SyntaxKind.NodeClose: {
428 | closingIsNodeClose = true;
429 | const comments = this.getCommentsSpaceJoined(c);
430 | if (comments) {
431 | closingSegmentsFormatted = [comments, "/>"];
432 | } else {
433 | closingSegmentsFormatted = ["/>"];
434 | }
435 | break;
436 | }
437 | case SyntaxKind.NodeEnd: {
438 | closingIsNodeClose = false;
439 |
440 | const comments = this.getCommentsSpaceJoined(c);
441 | if (comments) {
442 | closingSegmentsFormatted = [comments, ">"];
443 | } else {
444 | closingSegmentsFormatted = [">"];
445 | }
446 | break;
447 | }
448 | case SyntaxKind.ErrorNode:
449 | acc += this.getText(c, true);
450 | break;
451 | }
452 | }
453 |
454 | const lineLenBeforeAttrs = this.indentationLvl * this.tabSize + tagAcc.length;
455 | const errorNodesLen = acc.length;
456 |
457 | const restOfTag = attrSegmentsFormatted.concat(closingSegmentsFormatted);
458 | const restOfTagNonWsLen = restOfTag.reduce((sum, attr) => attr.length + sum, 0);
459 | const spacesBetweenRestOfTagNodes = attrSegmentsFormatted.length - 1;
460 |
461 | const sameLineTagLen =
462 | errorNodesLen + lineLenBeforeAttrs + restOfTagNonWsLen + spacesBetweenRestOfTagNodes;
463 |
464 | const breakAttrsToMultipleLines = attrsAlreadyMultiline || sameLineTagLen > this.maxLineLength;
465 |
466 | const attrsAndTrailingComments = attrSegmentsFormatted.concat(
467 | closingSegmentsFormatted.slice(0, -1),
468 | );
469 | const hasClosing = closingSegmentsFormatted.length > 0;
470 | const closingFormatted = closingSegmentsFormatted.at(-1);
471 |
472 | if (breakAttrsToMultipleLines) {
473 | const wsBeforeAttr = this.newlineToken + this.indent(this.indentationLvl + 1);
474 | const attrsFormatted = attrsAndTrailingComments.join(wsBeforeAttr);
475 |
476 | if (attrsAndTrailingComments.length > 0) {
477 | acc += wsBeforeAttr + attrsFormatted;
478 | }
479 | if (hasClosing) {
480 | if (closingIsNodeClose) {
481 | acc += " ";
482 | }
483 | acc += closingFormatted;
484 | }
485 | } else {
486 | const wsBeforeAttr = " ";
487 | const attrsFormatted = attrsAndTrailingComments.join(wsBeforeAttr);
488 |
489 | if (attrsAndTrailingComments.length > 0) {
490 | acc += wsBeforeAttr + attrsFormatted;
491 | }
492 | if (hasClosing) {
493 | if (closingIsNodeClose) {
494 | acc += " ";
495 | }
496 | acc += closingFormatted;
497 | }
498 | }
499 | return acc;
500 | }
501 |
502 | private printAttrList(node: Node): string[] {
503 | const attrsFormatted: string[] = [];
504 | for (const c of node.children) {
505 | const comments = this.getCommentsSpaceJoined(c);
506 | if (comments) {
507 | attrsFormatted.push(comments);
508 | }
509 | attrsFormatted.push(this.printAttrNode(c));
510 | }
511 | return attrsFormatted;
512 | }
513 |
514 | /**
515 | *
516 | * @param node a potential ErrorNode
517 | * @returns the formatted string if the node was an ErrorNode
518 | * otherwise `null`.
519 | */
520 | private printIfErrNode(node: Node): string | null {
521 | if (node.kind === SyntaxKind.ErrorNode) {
522 | return this.getText(node, true);
523 | } else {
524 | return null;
525 | }
526 | }
527 |
528 | /**
529 | * trivia before the first child element is not handled, as that is
530 | * the job of the parent function, that joins attrNodes together
531 | *
532 | * @param node attrNode
533 | * @returns
534 | */
535 | printAttrNode(node: Node): string {
536 | const formattedErrNode = this.printIfErrNode(node);
537 | if (formattedErrNode !== null) {
538 | return formattedErrNode;
539 | }
540 | let acc = this.printAttrKeyNode(node.children[0]);
541 | const otherChildren = node.children.slice(1);
542 | acc += this.printNodesSpaceJoinedCommentsBefore(otherChildren);
543 | return acc;
544 | }
545 |
546 | printAttrKeyNode(node: Node): string {
547 | let acc = this.getText(node.children[0]);
548 |
549 | const otherChildren = node.children.slice(1);
550 | acc += this.printNodesSpaceJoinedCommentsBefore(otherChildren);
551 | return acc;
552 | }
553 |
554 | private printNodesSpaceJoinedCommentsBefore(nodes: Node[]) {
555 | let acc = "";
556 | for (let node of nodes) {
557 | if (node.kind === SyntaxKind.ErrorNode) {
558 | acc += this.getText(node, false);
559 | continue;
560 | }
561 | const comment = this.getCommentsSpaceJoined(node);
562 | if (comment) {
563 | acc += " " + comment + " ";
564 | }
565 | acc += this.getText(node);
566 | }
567 | return acc;
568 | }
569 |
570 | printTagName(node: Node): string {
571 | const commentBefName = this.getCommentsSpaceJoined(node.children[0]);
572 | const firstTokenPrint = this.getText(node.children[0]);
573 | let acc = commentBefName ? ` ${commentBefName} ${firstTokenPrint}` : firstTokenPrint;
574 |
575 | acc += this.printNodesSpaceJoinedCommentsBefore(node.children.slice(1));
576 | return acc;
577 | }
578 |
579 | printContentString(node: Node): string {
580 | return this.getText(node, false).trim();
581 | }
582 |
583 | private indent(lvl: number): string {
584 | return this.indentationToken.repeat(lvl);
585 | }
586 |
587 | getComments(node: Node): string[] {
588 | const triviaNodes = node.getTriviaNodes() ?? [];
589 | return triviaNodes
590 | .filter(({ kind }) => kind === SyntaxKind.CommentTrivia)
591 | .map((comment) => this.getText(comment));
592 | }
593 |
594 | /**
595 | *
596 | * @returns null if the node doesn't have comment trivia,
597 | * otherwise the comments, joined with a space char.
598 | */
599 | getCommentsSpaceJoined(node: Node): string | null {
600 | const comments = this.getComments(node);
601 | if (comments.length === 0) {
602 | return null;
603 | }
604 | return comments.join(" ");
605 | }
606 |
607 | /**
608 | * Check if comments have newlines before them
609 | */
610 | hasNewlineTriviaBeforeComment(node: Node): boolean {
611 | const triviaNodes = node.getTriviaNodes();
612 | if (!triviaNodes) {
613 | return false;
614 | }
615 | for (let c of triviaNodes) {
616 | if (c.kind === SyntaxKind.NewLineTrivia) {
617 | return true;
618 | } else if (c.kind === SyntaxKind.CommentTrivia) {
619 | return false;
620 | }
621 | }
622 | return false;
623 | }
624 | }
625 |
626 | export function format(node: Node, getText: GetText, options: FormatOptions): string | null {
627 | const formatter = new XmluiFormatter(node, getText, options);
628 | return formatter.format();
629 | }
630 |
631 | function newlinesInTrailingWhitespace(text: string): number {
632 | const trimmedPrevText = text.trimEnd();
633 | const trailingWhitespace = text.substring(trimmedPrevText.length);
634 | let newlineCount = 0;
635 | for (const c of trailingWhitespace) {
636 | if (c === "\n") {
637 | newlineCount++;
638 | }
639 | }
640 | return newlineCount;
641 | }
642 |
```
--------------------------------------------------------------------------------
/xmlui/src/parsers/style-parser/StyleLexer.ts:
--------------------------------------------------------------------------------
```typescript
1 | import type { StyleToken } from "./tokens";
2 | import type { StyleInputStream } from "./StyleInputStream";
3 | import { StyleTokenType } from "./tokens";
4 |
5 | // States of the lexer's state machine
6 | enum LexerPhase {
7 | Start = 0,
8 | InWhiteSpace,
9 | IdTail,
10 | Dot,
11 | IdOrNumber,
12 | IntPart,
13 | FractPart,
14 | ColorCode,
15 | String,
16 | }
17 |
18 | /**
19 | * This class implements the lexer of binding expressions
20 | */
21 | export class StyleLexer {
22 | // --- Already fetched tokens
23 | private _ahead: StyleToken[] = [];
24 |
25 | // --- Prefetched character (from the next token)
26 | private _prefetched: string | null = null;
27 |
28 | // --- Prefetched character position (from the next token)
29 | private _prefetchedPos: number | null = null;
30 |
31 | // --- input position at the beginning of last fetch
32 | private _lastFetchPosition = 0;
33 |
34 | /**
35 | * Initializes the tokenizer with the input stream
36 | * @param input Input source code stream
37 | */
38 | constructor(public readonly input: StyleInputStream) {}
39 |
40 | /**
41 | * Fetches the next token without advancing to its position
42 | * @param ws If true, retrieve whitespaces too
43 | */
44 | peek(ws = false): StyleToken {
45 | return this.ahead(0, ws);
46 | }
47 |
48 | /**
49 | * Reads tokens ahead
50 | * @param n Number of token positions to read ahead
51 | * @param ws If true, retrieve whitespaces too
52 | */
53 | ahead(n = 1, ws = false): StyleToken {
54 | if (n > 16) {
55 | throw new Error("Cannot look ahead more than 16 tokens");
56 | }
57 |
58 | // --- Prefetch missing tokens
59 | while (this._ahead.length <= n) {
60 | const token = this.fetch();
61 | if (isEof(token)) {
62 | return token;
63 | }
64 | if (ws || (!ws && !isWs(token))) {
65 | this._ahead.push(token);
66 | }
67 | }
68 | return this._ahead[n];
69 | }
70 |
71 | /**
72 | * Fetches the next token and advances the stream position
73 | * @param ws If true, retrieve whitespaces too
74 | */
75 | get(ws = false): StyleToken {
76 | if (this._ahead.length > 0) {
77 | const token = this._ahead.shift();
78 | if (!token) {
79 | throw new Error("Token expected");
80 | }
81 | return token;
82 | }
83 | while (true) {
84 | const token = this.fetch();
85 | if (isEof(token) || ws || (!ws && !isWs(token))) {
86 | return token;
87 | }
88 | }
89 | }
90 |
91 | /**
92 | * Gets the remaining characters after the parsing phase
93 | */
94 | getTail(): string {
95 | return this._ahead.length > 0
96 | ? this.input.getTail(this._ahead[0].start)
97 | : this.input.getTail(this._lastFetchPosition);
98 | }
99 |
100 | /**
101 | * Fetches the next token from the input stream
102 | */
103 | private fetch(): StyleToken {
104 | // --- Captured constants used in nested functions
105 | const input = this.input;
106 | const startPos = this._prefetchedPos || input.position;
107 | this._lastFetchPosition = this.input.position;
108 |
109 | // --- State variables
110 | let text = "";
111 | let tokenType = StyleTokenType.Eof;
112 | let lastEndPos = input.position;
113 | let ch: string | null = null;
114 | let useResolver = false;
115 | let stringWrapper = "";
116 |
117 | /**
118 | * Appends the last character to the token, and manages positions
119 | */
120 | const appendTokenChar = (): void => {
121 | text += ch;
122 | this._prefetched = null;
123 | this._prefetchedPos = null;
124 | lastEndPos = input.position;
125 | };
126 |
127 | /**
128 | * Fetches the next character from the input stream
129 | */
130 | const fetchNextChar = (): string | null => {
131 | if (!this._prefetched) {
132 | this._prefetchedPos = input.position;
133 | this._prefetched = input.get();
134 | }
135 | return this._prefetched;
136 | };
137 |
138 | // --- Start from the beginning
139 | let phase: LexerPhase = LexerPhase.Start;
140 |
141 | // --- Process all token characters
142 | while (true) {
143 | // --- Get the next character
144 | ch = fetchNextChar();
145 |
146 | // --- In case of EOF, return the current token data
147 | if (ch === null) {
148 | return makeToken();
149 | }
150 |
151 | // --- Set the initial token type to unknown for the other characters
152 | if (tokenType === StyleTokenType.Eof) {
153 | tokenType = StyleTokenType.Unknown;
154 | }
155 |
156 | // --- Follow the lexer state machine
157 | switch (phase) {
158 | // ====================================================================
159 | // Process the first character
160 | case LexerPhase.Start:
161 | switch (ch) {
162 | // --- Go on with whitespaces
163 | case " ":
164 | case "\t":
165 | case "\n":
166 | case "\r":
167 | phase = LexerPhase.InWhiteSpace;
168 | tokenType = StyleTokenType.Ws;
169 | break;
170 |
171 | case "-":
172 | phase = LexerPhase.IdOrNumber;
173 | break;
174 |
175 | case "*":
176 | return completeToken(StyleTokenType.Star);
177 |
178 | case "%":
179 | return completeToken(StyleTokenType.Percentage);
180 |
181 | case "(":
182 | return completeToken(StyleTokenType.LParent);
183 |
184 | case ")":
185 | return completeToken(StyleTokenType.RParent);
186 |
187 | case ",":
188 | return completeToken(StyleTokenType.Comma);
189 |
190 | case "/":
191 | return completeToken(StyleTokenType.Slash);
192 |
193 | // --- Start of color code
194 | case "#":
195 | phase = LexerPhase.ColorCode;
196 | break;
197 |
198 | // --- Number starting with dot
199 | case ".":
200 | phase = LexerPhase.FractPart;
201 | break;
202 |
203 | case "'":
204 | case '"':
205 | stringWrapper = ch;
206 | phase = LexerPhase.String;
207 | break;
208 |
209 | default:
210 | if (isIdStart(ch)) {
211 | useResolver = true;
212 | phase = LexerPhase.IdTail;
213 | tokenType = StyleTokenType.Identifier;
214 | } else if (isDecimalDigit(ch)) {
215 | phase = LexerPhase.IntPart;
216 | tokenType = StyleTokenType.Number;
217 | } else {
218 | completeToken(StyleTokenType.Unknown);
219 | }
220 | break;
221 | }
222 | break;
223 |
224 | // --- Looking for the end of whitespace
225 | case LexerPhase.InWhiteSpace:
226 | if (ch !== " " && ch !== "\t" && ch !== "\r" && ch !== "\n") {
227 | return makeToken();
228 | }
229 | break;
230 |
231 | case LexerPhase.IdTail:
232 | if (!isIdContinuation(ch)) {
233 | return makeToken();
234 | }
235 | break;
236 |
237 | case LexerPhase.IdOrNumber:
238 | if (ch === ".") {
239 | phase = LexerPhase.FractPart;
240 | } else if (isDecimalDigit(ch)) {
241 | phase = LexerPhase.IntPart;
242 | tokenType = StyleTokenType.Number;
243 | } else if (isIdContinuation(ch)) {
244 | phase = LexerPhase.IdTail;
245 | tokenType = StyleTokenType.Identifier;
246 | } else {
247 | return makeToken();
248 | }
249 | break;
250 |
251 | case LexerPhase.IntPart:
252 | if (isDecimalDigit(ch)) {
253 | break;
254 | } else if (ch === ".") {
255 | phase = LexerPhase.FractPart;
256 | tokenType = StyleTokenType.Unknown;
257 | } else {
258 | return makeToken();
259 | }
260 | break;
261 |
262 | case LexerPhase.FractPart:
263 | if (!isDecimalDigit(ch)) {
264 | return makeToken();
265 | }
266 | tokenType = StyleTokenType.Number;
267 | break;
268 |
269 | case LexerPhase.ColorCode:
270 | if (isHexadecimalDigit(ch)) {
271 | tokenType =
272 | text.length === 3 || text.length === 6 || text.length === 4 || text.length === 8
273 | ? StyleTokenType.HexaColor
274 | : StyleTokenType.Unknown;
275 | break;
276 | } else {
277 | return makeToken();
278 | }
279 |
280 | case LexerPhase.String:
281 | if (ch === stringWrapper) {
282 | stringWrapper = "";
283 | return completeToken(StyleTokenType.String);
284 | }
285 | break;
286 |
287 | default:
288 | return makeToken();
289 | }
290 |
291 | // --- Append the char to the current text
292 | appendTokenChar();
293 |
294 | // --- Go on with parsing the next character
295 | }
296 |
297 | /**
298 | * Packs the specified type of token to send back
299 | */
300 | function makeToken(): StyleToken {
301 | if (useResolver) {
302 | tokenType =
303 | styleKeywords[text] ??
304 | (isIdStart(text[0]) ? StyleTokenType.Identifier : StyleTokenType.Unknown);
305 | }
306 | return {
307 | text,
308 | type: tokenType,
309 | start: startPos,
310 | end: lastEndPos,
311 | };
312 | }
313 |
314 | /**
315 | * Add the last character to the token and return it
316 | */
317 | function completeToken(suggestedType?: StyleTokenType): StyleToken {
318 | appendTokenChar();
319 |
320 | // --- Send back the token
321 | if (suggestedType !== undefined) {
322 | tokenType = suggestedType;
323 | }
324 | return makeToken();
325 | }
326 | }
327 | }
328 |
329 | /**
330 | * Tests if a token id EOF
331 | * @param t Token instance
332 | */
333 | function isEof(t: StyleToken): boolean {
334 | return t.type === StyleTokenType.Eof;
335 | }
336 |
337 | /**
338 | * Tests if a token is whitespace
339 | * @param t Token instance
340 | */
341 | function isWs(t: StyleToken): boolean {
342 | return t.type <= StyleTokenType.Ws;
343 | }
344 |
345 | /**
346 | * Tests if a character is an identifier start character
347 | * @param ch Character to test
348 | */
349 | function isIdStart(ch: string): boolean {
350 | return (
351 | (ch >= "a" && ch <= "z") || (ch >= "A" && ch <= "Z") || ch === "-" || ch === "_" || ch === "$"
352 | );
353 | }
354 |
355 | /**
356 | * Tests if a character is an identifier continuation character
357 | * @param ch Character to test
358 | */
359 | function isIdContinuation(ch: string): boolean {
360 | return (
361 | (ch >= "a" && ch <= "z") ||
362 | (ch >= "A" && ch <= "Z") ||
363 | (ch >= "0" && ch <= "9") ||
364 | ch === "-" ||
365 | ch === "_" ||
366 | ch === "$"
367 | );
368 | }
369 |
370 | /**
371 | * Tests if a character is a decimal digit
372 | * @param ch Character to test
373 | */
374 | function isDecimalDigit(ch: string): boolean {
375 | return ch >= "0" && ch <= "9";
376 | }
377 |
378 | /**
379 | * Tests if a character is a hexadecimal digit
380 | * @param ch Character to test
381 | */
382 | function isHexadecimalDigit(ch: string): boolean {
383 | return (ch >= "0" && ch <= "9") || (ch >= "A" && ch <= "F") || (ch >= "a" && ch <= "f");
384 | }
385 |
386 | /**
387 | * Reserved ID-like tokens
388 | */
389 | export const styleKeywords: Record<string, StyleTokenType> = {
390 | // --- "none" keyword
391 | none: StyleTokenType.None,
392 |
393 | // --- Size unit tokens
394 | auto: StyleTokenType.Auto,
395 | px: StyleTokenType.SizeUnit,
396 | cm: StyleTokenType.SizeUnit,
397 | mm: StyleTokenType.SizeUnit,
398 | in: StyleTokenType.SizeUnit,
399 | pt: StyleTokenType.SizeUnit,
400 | pc: StyleTokenType.SizeUnit,
401 | em: StyleTokenType.SizeUnit,
402 | rem: StyleTokenType.SizeUnit,
403 | vw: StyleTokenType.SizeUnit,
404 | vh: StyleTokenType.SizeUnit,
405 | ex: StyleTokenType.SizeUnit,
406 | ch: StyleTokenType.SizeUnit,
407 | vmin: StyleTokenType.SizeUnit,
408 | vmax: StyleTokenType.SizeUnit,
409 |
410 | // --- Alignment tokens
411 | start: StyleTokenType.Alignment,
412 | center: StyleTokenType.Alignment,
413 | end: StyleTokenType.Alignment,
414 |
415 | // --- Text alignment tokens
416 | left: StyleTokenType.TextAlignment,
417 | right: StyleTokenType.TextAlignment,
418 | justify: StyleTokenType.TextAlignment,
419 |
420 | // --- Orientation tokens
421 | vertical: StyleTokenType.Orientation,
422 | horizontal: StyleTokenType.Orientation,
423 |
424 | // --- Direction tokens
425 | ltr: StyleTokenType.Direction,
426 | rtl: StyleTokenType.Direction,
427 |
428 | // --- FontFamily tokens
429 | serif: StyleTokenType.FontFamily,
430 | sansSerif: StyleTokenType.FontFamily,
431 | mono: StyleTokenType.FontFamily,
432 |
433 | // --- Scrolling tokens
434 | visible: StyleTokenType.Scrolling,
435 | hidden: StyleTokenType.Scrolling,
436 | scroll: StyleTokenType.Scrolling,
437 |
438 | // --- Text transform tokens
439 | capitalize: StyleTokenType.TextTransform,
440 | uppercase: StyleTokenType.TextTransform,
441 | lowercase: StyleTokenType.TextTransform,
442 | "full-width": StyleTokenType.TextTransform,
443 | "full-size-kana": StyleTokenType.TextTransform,
444 |
445 | // --- Border style values
446 | dotted: StyleTokenType.BorderStyle,
447 | dashed: StyleTokenType.BorderStyle,
448 | solid: StyleTokenType.BorderStyle,
449 | double: StyleTokenType.BorderStyle,
450 | groove: StyleTokenType.BorderStyle,
451 | ridge: StyleTokenType.BorderStyle,
452 | inset: StyleTokenType.BorderStyle,
453 | outset: StyleTokenType.BorderStyle,
454 |
455 | // --- Angle tokens
456 | deg: StyleTokenType.Angle,
457 | rad: StyleTokenType.Angle,
458 | grad: StyleTokenType.Angle,
459 | turn: StyleTokenType.Angle,
460 |
461 | // --- Weight tokens
462 | lighter: StyleTokenType.FontWeight,
463 | normal: StyleTokenType.FontWeight,
464 | bold: StyleTokenType.FontWeight,
465 | bolder: StyleTokenType.FontWeight,
466 |
467 | // --- Boolean tokens
468 | true: StyleTokenType.Boolean,
469 | false: StyleTokenType.Boolean,
470 | yes: StyleTokenType.Boolean,
471 | no: StyleTokenType.Boolean,
472 | on: StyleTokenType.Boolean,
473 | off: StyleTokenType.Boolean,
474 |
475 | // --- Color function tokens
476 | rgb: StyleTokenType.ColorFunc,
477 | rgba: StyleTokenType.ColorFunc,
478 | hsl: StyleTokenType.ColorFunc,
479 | hsla: StyleTokenType.ColorFunc,
480 |
481 | // --- Text decoration line
482 | underline: StyleTokenType.DecorationLine,
483 | overline: StyleTokenType.DecorationLine,
484 | "line-through": StyleTokenType.DecorationLine,
485 |
486 | // --- user-select tokens
487 | all: StyleTokenType.UserSelect,
488 | text: StyleTokenType.UserSelect,
489 | contain: StyleTokenType.UserSelect,
490 |
491 | // --- Other tokens
492 | reset: StyleTokenType.Reset,
493 |
494 | // --- Color codes
495 | transparent: StyleTokenType.ColorName,
496 |
497 | // CSS Level 1
498 | black: StyleTokenType.ColorName,
499 | silver: StyleTokenType.ColorName,
500 | gray: StyleTokenType.ColorName,
501 | white: StyleTokenType.ColorName,
502 | maroon: StyleTokenType.ColorName,
503 | red: StyleTokenType.ColorName,
504 | purple: StyleTokenType.ColorName,
505 | fuchsia: StyleTokenType.ColorName,
506 | green: StyleTokenType.ColorName,
507 | lime: StyleTokenType.ColorName,
508 | olive: StyleTokenType.ColorName,
509 | yellow: StyleTokenType.ColorName,
510 | navy: StyleTokenType.ColorName,
511 | blue: StyleTokenType.ColorName,
512 | teal: StyleTokenType.ColorName,
513 | aqua: StyleTokenType.ColorName,
514 |
515 | // CSS Level 2
516 | orange: StyleTokenType.ColorName,
517 |
518 | // CSS Level 3
519 | aliceblue: StyleTokenType.ColorName,
520 | antiquewhite: StyleTokenType.ColorName,
521 | aquamarine: StyleTokenType.ColorName,
522 | azure: StyleTokenType.ColorName,
523 | beige: StyleTokenType.ColorName,
524 | bisque: StyleTokenType.ColorName,
525 | blanchedalmond: StyleTokenType.ColorName,
526 | blueviolet: StyleTokenType.ColorName,
527 | brown: StyleTokenType.ColorName,
528 | burlywood: StyleTokenType.ColorName,
529 | cadetblue: StyleTokenType.ColorName,
530 | chartreuse: StyleTokenType.ColorName,
531 | chocolate: StyleTokenType.ColorName,
532 | coral: StyleTokenType.ColorName,
533 | cornflowerblue: StyleTokenType.ColorName,
534 | cornsilk: StyleTokenType.ColorName,
535 | crimson: StyleTokenType.ColorName,
536 | cyan: StyleTokenType.ColorName,
537 | darkblue: StyleTokenType.ColorName,
538 | darkcyan: StyleTokenType.ColorName,
539 | darkgoldenrod: StyleTokenType.ColorName,
540 | darkgray: StyleTokenType.ColorName,
541 | darkgreen: StyleTokenType.ColorName,
542 | darkgrey: StyleTokenType.ColorName,
543 | darkkhaki: StyleTokenType.ColorName,
544 | darkmagenta: StyleTokenType.ColorName,
545 | darkolivegreen: StyleTokenType.ColorName,
546 | darkorange: StyleTokenType.ColorName,
547 | darkorchid: StyleTokenType.ColorName,
548 | darkred: StyleTokenType.ColorName,
549 | darksalmon: StyleTokenType.ColorName,
550 | darkseagreen: StyleTokenType.ColorName,
551 | darkslateblue: StyleTokenType.ColorName,
552 | darkslategray: StyleTokenType.ColorName,
553 | darkslategrey: StyleTokenType.ColorName,
554 | darkturquoise: StyleTokenType.ColorName,
555 | darkviolet: StyleTokenType.ColorName,
556 | deeppink: StyleTokenType.ColorName,
557 | deepskyblue: StyleTokenType.ColorName,
558 | dimgray: StyleTokenType.ColorName,
559 | dimgrey: StyleTokenType.ColorName,
560 | dodgerblue: StyleTokenType.ColorName,
561 | firebrick: StyleTokenType.ColorName,
562 | floralwhite: StyleTokenType.ColorName,
563 | forestgreen: StyleTokenType.ColorName,
564 | gainsboro: StyleTokenType.ColorName,
565 | ghostwhite: StyleTokenType.ColorName,
566 | gold: StyleTokenType.ColorName,
567 | goldenrod: StyleTokenType.ColorName,
568 | greenyellow: StyleTokenType.ColorName,
569 | grey: StyleTokenType.ColorName,
570 | honeydew: StyleTokenType.ColorName,
571 | hotpink: StyleTokenType.ColorName,
572 | indianred: StyleTokenType.ColorName,
573 | indigo: StyleTokenType.ColorName,
574 | ivory: StyleTokenType.ColorName,
575 | khaki: StyleTokenType.ColorName,
576 | lavender: StyleTokenType.ColorName,
577 | lavenderblush: StyleTokenType.ColorName,
578 | lawngreen: StyleTokenType.ColorName,
579 | lemonchiffon: StyleTokenType.ColorName,
580 | lightblue: StyleTokenType.ColorName,
581 | lightcoral: StyleTokenType.ColorName,
582 | lightcyan: StyleTokenType.ColorName,
583 | lightgoldenrodyellow: StyleTokenType.ColorName,
584 | lightgray: StyleTokenType.ColorName,
585 | lightgreen: StyleTokenType.ColorName,
586 | lightgrey: StyleTokenType.ColorName,
587 | lightpink: StyleTokenType.ColorName,
588 | lightsalmon: StyleTokenType.ColorName,
589 | lightseagreen: StyleTokenType.ColorName,
590 | lightskyblue: StyleTokenType.ColorName,
591 | lightslategray: StyleTokenType.ColorName,
592 | lightslategrey: StyleTokenType.ColorName,
593 | lightsteelblue: StyleTokenType.ColorName,
594 | lightyellow: StyleTokenType.ColorName,
595 | limegreen: StyleTokenType.ColorName,
596 | linen: StyleTokenType.ColorName,
597 | magenta: StyleTokenType.ColorName,
598 | mediumaquamarine: StyleTokenType.ColorName,
599 | mediumblue: StyleTokenType.ColorName,
600 | mediumorchid: StyleTokenType.ColorName,
601 | mediumpurple: StyleTokenType.ColorName,
602 | mediumseagreen: StyleTokenType.ColorName,
603 | mediumslateblue: StyleTokenType.ColorName,
604 | mediumspringgreen: StyleTokenType.ColorName,
605 | mediumturquoise: StyleTokenType.ColorName,
606 | mediumvioletred: StyleTokenType.ColorName,
607 | midnightblue: StyleTokenType.ColorName,
608 | mintcream: StyleTokenType.ColorName,
609 | mistyrose: StyleTokenType.ColorName,
610 | moccasin: StyleTokenType.ColorName,
611 | navajowhite: StyleTokenType.ColorName,
612 | oldlace: StyleTokenType.ColorName,
613 | olivedrab: StyleTokenType.ColorName,
614 | orangered: StyleTokenType.ColorName,
615 | orchid: StyleTokenType.ColorName,
616 | palegoldenrod: StyleTokenType.ColorName,
617 | palegreen: StyleTokenType.ColorName,
618 | paleturquoise: StyleTokenType.ColorName,
619 | palevioletred: StyleTokenType.ColorName,
620 | papayawhip: StyleTokenType.ColorName,
621 | peachpuff: StyleTokenType.ColorName,
622 | peru: StyleTokenType.ColorName,
623 | pink: StyleTokenType.ColorName,
624 | plum: StyleTokenType.ColorName,
625 | powderblue: StyleTokenType.ColorName,
626 | rosybrown: StyleTokenType.ColorName,
627 | royalblue: StyleTokenType.ColorName,
628 | saddlebrown: StyleTokenType.ColorName,
629 | salmon: StyleTokenType.ColorName,
630 | sandybrown: StyleTokenType.ColorName,
631 | seagreen: StyleTokenType.ColorName,
632 | seashell: StyleTokenType.ColorName,
633 | sienna: StyleTokenType.ColorName,
634 | skyblue: StyleTokenType.ColorName,
635 | slateblue: StyleTokenType.ColorName,
636 | slategray: StyleTokenType.ColorName,
637 | slategrey: StyleTokenType.ColorName,
638 | snow: StyleTokenType.ColorName,
639 | springgreen: StyleTokenType.ColorName,
640 | steelblue: StyleTokenType.ColorName,
641 | tan: StyleTokenType.ColorName,
642 | thistle: StyleTokenType.ColorName,
643 | tomato: StyleTokenType.ColorName,
644 | turquoise: StyleTokenType.ColorName,
645 | violet: StyleTokenType.ColorName,
646 | wheat: StyleTokenType.ColorName,
647 | whitesmoke: StyleTokenType.ColorName,
648 | yellowgreen: StyleTokenType.ColorName,
649 |
650 | // CSS Level 4
651 | rebeccapurple: StyleTokenType.ColorName,
652 |
653 | // --- Cursor tokens
654 | default: StyleTokenType.Default,
655 | "context-menu": StyleTokenType.Cursor,
656 | help: StyleTokenType.Cursor,
657 | pointer: StyleTokenType.Cursor,
658 | progress: StyleTokenType.Cursor,
659 | wait: StyleTokenType.Cursor,
660 | cell: StyleTokenType.Cursor,
661 | crosshair: StyleTokenType.Cursor,
662 | "vertical-text": StyleTokenType.Cursor,
663 | alias: StyleTokenType.Cursor,
664 | copy: StyleTokenType.Cursor,
665 | move: StyleTokenType.Cursor,
666 | "no-drop": StyleTokenType.Cursor,
667 | "not-allowed": StyleTokenType.Cursor,
668 | grab: StyleTokenType.Cursor,
669 | grabbing: StyleTokenType.Cursor,
670 | "all-scroll": StyleTokenType.Cursor,
671 | "col-resize": StyleTokenType.Cursor,
672 | "row-resize": StyleTokenType.Cursor,
673 | "n-resize": StyleTokenType.Cursor,
674 | "e-resize": StyleTokenType.Cursor,
675 | "s-resize": StyleTokenType.Cursor,
676 | "w-resize": StyleTokenType.Cursor,
677 | "ne-resize": StyleTokenType.Cursor,
678 | "nw-resize": StyleTokenType.Cursor,
679 | "se-resize": StyleTokenType.Cursor,
680 | "sw-resize": StyleTokenType.Cursor,
681 | "ew-resize": StyleTokenType.Cursor,
682 | "ns-resize": StyleTokenType.Cursor,
683 | "nesw-resize": StyleTokenType.Cursor,
684 | "nwse-resize": StyleTokenType.Cursor,
685 | "zoom-in": StyleTokenType.Cursor,
686 | "zoom-out": StyleTokenType.Cursor,
687 | };
688 |
```