This is page 30 of 141. Use http://codebase.md/xmlui-org/xmlui/mockApiDef.js?page={x} to view the full context. # Directory Structure ``` ├── .changeset │ └── config.json ├── .eslintrc.cjs ├── .github │ ├── build-checklist.png │ ├── ISSUE_TEMPLATE │ │ ├── bug_report.md │ │ └── feature_request.md │ └── workflows │ ├── deploy-blog.yml │ ├── deploy-docs-optimized.yml │ ├── deploy-docs.yml │ ├── prepare-versions.yml │ ├── release-packages.yml │ ├── run-all-tests.yml │ └── run-smoke-tests.yml ├── .gitignore ├── .prettierrc.js ├── .vscode │ ├── launch.json │ └── settings.json ├── blog │ ├── .gitignore │ ├── .gitkeep │ ├── CHANGELOG.md │ ├── extensions.ts │ ├── index.html │ ├── index.ts │ ├── layout-changes.md │ ├── package.json │ ├── public │ │ ├── blog │ │ │ ├── images │ │ │ │ ├── blog-page-component.png │ │ │ │ ├── blog-scrabble.png │ │ │ │ ├── integrated-blog-search.png │ │ │ │ └── lorem-ipsum.png │ │ │ ├── lorem-ipsum.md │ │ │ ├── newest-post.md │ │ │ ├── older-post.md │ │ │ └── welcome-to-the-xmlui-blog.md │ │ ├── mockServiceWorker.js │ │ ├── resources │ │ │ ├── favicon.ico │ │ │ ├── files │ │ │ │ └── for-download │ │ │ │ └── xmlui │ │ │ │ └── xmlui-standalone.umd.js │ │ │ ├── github.svg │ │ │ ├── llms.txt │ │ │ ├── logo-dark.svg │ │ │ ├── logo.svg │ │ │ ├── pg-popout.svg │ │ │ ├── rss.svg │ │ │ └── xmlui-logo.svg │ │ ├── serve.json │ │ └── web.config │ ├── scripts │ │ ├── download-latest-xmlui.js │ │ ├── generate-rss.js │ │ ├── get-releases.js │ │ └── utils.js │ ├── src │ │ ├── components │ │ │ ├── BlogOverview.xmlui │ │ │ ├── BlogPage.xmlui │ │ │ └── PageNotFound.xmlui │ │ ├── config.ts │ │ ├── Main.xmlui │ │ └── themes │ │ └── 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 │ │ ├── HelloMd.md │ │ ├── HeroSection.md │ │ └── ScrollToTop.md │ ├── extensions.ts │ ├── index.html │ ├── index.ts │ ├── package.json │ ├── public │ │ ├── feed.rss │ │ ├── mockServiceWorker.js │ │ ├── pages │ │ │ ├── _meta.json │ │ │ ├── app-structure.md │ │ │ ├── build-editor-component.md │ │ │ ├── build-hello-world-component.md │ │ │ ├── components-intro.md │ │ │ ├── context-variables.md │ │ │ ├── forms.md │ │ │ ├── globals.md │ │ │ ├── glossary.md │ │ │ ├── helper-tags.md │ │ │ ├── hosted-deployment.md │ │ │ ├── howto │ │ │ │ ├── assign-a-complex-json-literal-to-a-component-variable.md │ │ │ │ ├── chain-a-refetch.md │ │ │ │ ├── debug-a-component.md │ │ │ │ ├── delay-a-datasource-until-another-datasource-is-ready.md │ │ │ │ ├── delegate-a-method.md │ │ │ │ ├── do-custom-form-validation.md │ │ │ │ ├── expose-a-method-from-a-component.md │ │ │ │ ├── filter-and-transform-data-from-an-api.md │ │ │ │ ├── group-items-in-list-by-a-property.md │ │ │ │ ├── handle-background-operations.md │ │ │ │ ├── hide-an-element-until-its-datasource-is-ready.md │ │ │ │ ├── make-a-set-of-equal-width-cards.md │ │ │ │ ├── make-a-table-responsive.md │ │ │ │ ├── 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 │ ├── xmlui-animations │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── Animation.tsx │ │ │ ├── AnimationNative.tsx │ │ │ ├── FadeAnimation.tsx │ │ │ ├── FadeInAnimation.tsx │ │ │ ├── FadeOutAnimation.tsx │ │ │ ├── index.tsx │ │ │ ├── ScaleAnimation.tsx │ │ │ └── SlideInAnimation.tsx │ │ └── tsconfig.json │ ├── xmlui-devtools │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── devtools │ │ │ │ ├── DevTools.tsx │ │ │ │ ├── DevToolsNative.module.scss │ │ │ │ ├── DevToolsNative.tsx │ │ │ │ ├── ModalDialog.module.scss │ │ │ │ ├── ModalDialog.tsx │ │ │ │ ├── ModalVisibilityContext.tsx │ │ │ │ ├── Tooltip.module.scss │ │ │ │ ├── Tooltip.tsx │ │ │ │ └── utils.ts │ │ │ ├── editor │ │ │ │ └── Editor.tsx │ │ │ └── index.tsx │ │ ├── tsconfig.json │ │ └── vite.config-overrides.ts │ ├── xmlui-hello-world │ │ ├── .gitignore │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── HelloWorld.module.scss │ │ │ ├── HelloWorld.tsx │ │ │ ├── HelloWorldNative.tsx │ │ │ └── index.tsx │ │ └── tsconfig.json │ ├── xmlui-os-frames │ │ ├── .gitignore │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── IPhoneFrame.module.scss │ │ │ ├── IPhoneFrame.tsx │ │ │ ├── MacOSAppFrame.module.scss │ │ │ ├── MacOSAppFrame.tsx │ │ │ ├── WindowsAppFrame.module.scss │ │ │ └── WindowsAppFrame.tsx │ │ └── tsconfig.json │ ├── xmlui-pdf │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ ├── components │ │ │ │ └── Pdf.xmlui │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── LazyPdfNative.tsx │ │ │ ├── Pdf.module.scss │ │ │ └── Pdf.tsx │ │ └── tsconfig.json │ ├── xmlui-playground │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── hooks │ │ │ │ ├── usePlayground.ts │ │ │ │ └── useToast.ts │ │ │ ├── index.tsx │ │ │ ├── playground │ │ │ │ ├── Box.module.scss │ │ │ │ ├── Box.tsx │ │ │ │ ├── CodeSelector.tsx │ │ │ │ ├── ConfirmationDialog.module.scss │ │ │ │ ├── ConfirmationDialog.tsx │ │ │ │ ├── Editor.tsx │ │ │ │ ├── Header.module.scss │ │ │ │ ├── Header.tsx │ │ │ │ ├── Playground.tsx │ │ │ │ ├── PlaygroundContent.module.scss │ │ │ │ ├── PlaygroundContent.tsx │ │ │ │ ├── PlaygroundNative.module.scss │ │ │ │ ├── PlaygroundNative.tsx │ │ │ │ ├── Preview.module.scss │ │ │ │ ├── Preview.tsx │ │ │ │ ├── Select.module.scss │ │ │ │ ├── StandalonePlayground.tsx │ │ │ │ ├── StandalonePlaygroundNative.module.scss │ │ │ │ ├── StandalonePlaygroundNative.tsx │ │ │ │ ├── ThemeSwitcher.module.scss │ │ │ │ ├── ThemeSwitcher.tsx │ │ │ │ ├── ToneSwitcher.tsx │ │ │ │ ├── Tooltip.module.scss │ │ │ │ ├── Tooltip.tsx │ │ │ │ └── utils.ts │ │ │ ├── providers │ │ │ │ ├── Toast.module.scss │ │ │ │ └── ToastProvider.tsx │ │ │ ├── state │ │ │ │ └── store.ts │ │ │ ├── themes │ │ │ │ └── theme.ts │ │ │ └── utils │ │ │ └── helpers.ts │ │ └── tsconfig.json │ ├── xmlui-search │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── Search.module.scss │ │ │ └── Search.tsx │ │ └── tsconfig.json │ ├── xmlui-spreadsheet │ │ ├── .gitignore │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── Spreadsheet.tsx │ │ │ └── SpreadsheetNative.tsx │ │ └── tsconfig.json │ └── xmlui-website-blocks │ ├── .gitignore │ ├── CHANGELOG.md │ ├── demo │ │ ├── components │ │ │ ├── HeroBackgroundBreakoutPage.xmlui │ │ │ ├── HeroBackgroundsPage.xmlui │ │ │ ├── HeroContentsPage.xmlui │ │ │ ├── HeroTextAlignPage.xmlui │ │ │ ├── HeroTextPage.xmlui │ │ │ └── HeroTonesPage.xmlui │ │ ├── Main.xmlui │ │ └── themes │ │ └── default.ts │ ├── index.html │ ├── index.ts │ ├── meta │ │ └── componentsMetadata.ts │ ├── package.json │ ├── public │ │ └── resources │ │ ├── building.jpg │ │ └── xmlui-logo.svg │ ├── src │ │ ├── Carousel │ │ │ ├── Carousel.module.scss │ │ │ ├── Carousel.tsx │ │ │ ├── CarouselContext.tsx │ │ │ └── CarouselNative.tsx │ │ ├── FancyButton │ │ │ ├── FancyButton.module.scss │ │ │ ├── FancyButton.tsx │ │ │ └── FancyButton.xmlui │ │ ├── Hello │ │ │ ├── Hello.tsx │ │ │ ├── Hello.xmlui │ │ │ └── Hello.xmlui.xs │ │ ├── HeroSection │ │ │ ├── HeroSection.module.scss │ │ │ ├── HeroSection.tsx │ │ │ └── HeroSectionNative.tsx │ │ ├── index.tsx │ │ ├── ScrollToTop │ │ │ ├── ScrollToTop.module.scss │ │ │ ├── ScrollToTop.tsx │ │ │ └── ScrollToTopNative.tsx │ │ └── vite-env.d.ts │ └── tsconfig.json ├── README.md ├── tools │ ├── codefence │ │ └── xmlui-code-fence-docs.md │ ├── create-app │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── create-app.ts │ │ ├── helpers │ │ │ ├── copy.ts │ │ │ ├── get-pkg-manager.ts │ │ │ ├── git.ts │ │ │ ├── install.ts │ │ │ ├── is-folder-empty.ts │ │ │ ├── is-writeable.ts │ │ │ ├── make-dir.ts │ │ │ └── validate-pkg.ts │ │ ├── index.ts │ │ ├── package.json │ │ ├── templates │ │ │ ├── default │ │ │ │ └── ts │ │ │ │ ├── gitignore │ │ │ │ ├── index.html │ │ │ │ ├── index.ts │ │ │ │ ├── public │ │ │ │ │ ├── mockServiceWorker.js │ │ │ │ │ ├── resources │ │ │ │ │ │ ├── favicon.ico │ │ │ │ │ │ └── xmlui-logo.svg │ │ │ │ │ └── serve.json │ │ │ │ └── src │ │ │ │ ├── components │ │ │ │ │ ├── ApiAware.xmlui │ │ │ │ │ ├── Home.xmlui │ │ │ │ │ ├── IncButton.xmlui │ │ │ │ │ └── PagePanel.xmlui │ │ │ │ ├── config.ts │ │ │ │ └── Main.xmlui │ │ │ ├── index.ts │ │ │ └── types.ts │ │ └── tsconfig.json │ ├── create-xmlui-hello-world │ │ ├── index.js │ │ └── package.json │ └── vscode │ ├── .gitignore │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ ├── .vscodeignore │ ├── build.sh │ ├── CHANGELOG.md │ ├── esbuild.js │ ├── eslint.config.mjs │ ├── formatter-docs.md │ ├── generate-test-sample.sh │ ├── LICENSE.md │ ├── package-lock.json │ ├── package.json │ ├── README.md │ ├── resources │ │ ├── xmlui-logo.png │ │ └── xmlui-markup-syntax-highlighting.png │ ├── src │ │ ├── extension.ts │ │ └── server.ts │ ├── syntaxes │ │ └── xmlui.tmLanguage.json │ ├── test-samples │ │ └── sample.xmlui │ ├── tsconfig.json │ └── tsconfig.tsbuildinfo ├── turbo.json └── xmlui ├── .gitignore ├── bin │ ├── bootstrap.js │ ├── build-lib.ts │ ├── build.ts │ ├── index.ts │ ├── preview.ts │ ├── start.ts │ ├── vite-xmlui-plugin.ts │ └── viteConfig.ts ├── CHANGELOG.md ├── conventions │ ├── component-qa-checklist.md │ ├── copilot-conventions.md │ ├── create-xmlui-components.md │ ├── mermaid.md │ ├── testing-conventions.md │ └── xmlui-in-a-nutshell.md ├── dev-docs │ ├── accessibility.md │ ├── build-system.md │ ├── build-xmlui.md │ ├── component-behaviors.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 │ ├── ud-components.md │ └── xmlui-repo.md ├── package.json ├── playwright.config.ts ├── scripts │ ├── coverage-only.js │ ├── e2e-test-summary.js │ ├── generate-docs │ │ ├── build-downloads-map.mjs │ │ ├── build-pages-map.mjs │ │ ├── components-config.json │ │ ├── configuration-management.mjs │ │ ├── constants.mjs │ │ ├── create-theme-files.mjs │ │ ├── DocsGenerator.mjs │ │ ├── error-handling.mjs │ │ ├── extensions-config.json │ │ ├── folders.mjs │ │ ├── generate-summary-files.mjs │ │ ├── get-docs.mjs │ │ ├── input-handler.mjs │ │ ├── logger.mjs │ │ ├── logging-standards.mjs │ │ ├── MetadataProcessor.mjs │ │ ├── pattern-utilities.mjs │ │ └── utils.mjs │ ├── get-langserver-metadata.mjs │ ├── inline-links.mjs │ └── README-e2e-summary.md ├── src │ ├── abstractions │ │ ├── _conventions.md │ │ ├── ActionDefs.ts │ │ ├── AppContextDefs.ts │ │ ├── ComponentDefs.ts │ │ ├── ContainerDefs.ts │ │ ├── ExtensionDefs.ts │ │ ├── FunctionDefs.ts │ │ ├── RendererDefs.ts │ │ ├── scripting │ │ │ ├── BlockScope.ts │ │ │ ├── Compilation.ts │ │ │ ├── LogicalThread.ts │ │ │ ├── LoopScope.ts │ │ │ ├── modules.ts │ │ │ ├── ScriptParserError.ts │ │ │ ├── Token.ts │ │ │ ├── TryScope.ts │ │ │ └── TryScopeExp.ts │ │ └── ThemingDefs.ts │ ├── components │ │ ├── _conventions.md │ │ ├── abstractions.ts │ │ ├── Accordion │ │ │ ├── Accordion.md │ │ │ ├── Accordion.module.scss │ │ │ ├── Accordion.spec.ts │ │ │ ├── Accordion.tsx │ │ │ ├── AccordionContext.tsx │ │ │ ├── AccordionItem.tsx │ │ │ ├── AccordionItemNative.tsx │ │ │ └── AccordionNative.tsx │ │ ├── Animation │ │ │ └── AnimationNative.tsx │ │ ├── APICall │ │ │ ├── APICall.md │ │ │ ├── APICall.spec.ts │ │ │ ├── APICall.tsx │ │ │ └── APICallNative.tsx │ │ ├── App │ │ │ ├── App.md │ │ │ ├── App.module.scss │ │ │ ├── App.spec.ts │ │ │ ├── App.tsx │ │ │ ├── AppLayoutContext.ts │ │ │ ├── AppNative.tsx │ │ │ ├── AppStateContext.ts │ │ │ ├── doc-resources │ │ │ │ ├── condensed-sticky.xmlui │ │ │ │ ├── condensed.xmlui │ │ │ │ ├── horizontal-sticky.xmlui │ │ │ │ ├── horizontal.xmlui │ │ │ │ ├── vertical-full-header.xmlui │ │ │ │ ├── vertical-sticky.xmlui │ │ │ │ └── vertical.xmlui │ │ │ ├── IndexerContext.ts │ │ │ ├── LinkInfoContext.ts │ │ │ ├── SearchContext.tsx │ │ │ ├── Sheet.module.scss │ │ │ └── Sheet.tsx │ │ ├── AppHeader │ │ │ ├── AppHeader.md │ │ │ ├── AppHeader.module.scss │ │ │ ├── AppHeader.spec.ts │ │ │ ├── AppHeader.tsx │ │ │ └── AppHeaderNative.tsx │ │ ├── AppState │ │ │ ├── AppState.md │ │ │ ├── AppState.spec.ts │ │ │ ├── AppState.tsx │ │ │ └── AppStateNative.tsx │ │ ├── AutoComplete │ │ │ ├── AutoComplete.md │ │ │ ├── AutoComplete.module.scss │ │ │ ├── AutoComplete.spec.ts │ │ │ ├── AutoComplete.tsx │ │ │ ├── AutoCompleteContext.tsx │ │ │ └── AutoCompleteNative.tsx │ │ ├── Avatar │ │ │ ├── Avatar.md │ │ │ ├── Avatar.module.scss │ │ │ ├── Avatar.spec.ts │ │ │ ├── Avatar.tsx │ │ │ └── AvatarNative.tsx │ │ ├── Backdrop │ │ │ ├── Backdrop.md │ │ │ ├── Backdrop.module.scss │ │ │ ├── Backdrop.spec.ts │ │ │ ├── Backdrop.tsx │ │ │ └── BackdropNative.tsx │ │ ├── Badge │ │ │ ├── Badge.md │ │ │ ├── Badge.module.scss │ │ │ ├── Badge.spec.ts │ │ │ ├── Badge.tsx │ │ │ └── BadgeNative.tsx │ │ ├── Bookmark │ │ │ ├── Bookmark.md │ │ │ ├── Bookmark.module.scss │ │ │ ├── Bookmark.spec.ts │ │ │ ├── Bookmark.tsx │ │ │ └── BookmarkNative.tsx │ │ ├── Breakout │ │ │ ├── Breakout.module.scss │ │ │ ├── Breakout.spec.ts │ │ │ ├── Breakout.tsx │ │ │ └── BreakoutNative.tsx │ │ ├── Button │ │ │ ├── Button-style.spec.ts │ │ │ ├── Button.md │ │ │ ├── Button.module.scss │ │ │ ├── Button.spec.ts │ │ │ ├── Button.tsx │ │ │ └── ButtonNative.tsx │ │ ├── Card │ │ │ ├── Card.md │ │ │ ├── Card.module.scss │ │ │ ├── Card.spec.ts │ │ │ ├── Card.tsx │ │ │ └── CardNative.tsx │ │ ├── Carousel │ │ │ ├── Carousel.md │ │ │ ├── Carousel.module.scss │ │ │ ├── Carousel.spec.ts │ │ │ ├── Carousel.tsx │ │ │ ├── CarouselContext.tsx │ │ │ ├── CarouselItem.tsx │ │ │ ├── CarouselItemNative.tsx │ │ │ └── CarouselNative.tsx │ │ ├── ChangeListener │ │ │ ├── ChangeListener.md │ │ │ ├── ChangeListener.spec.ts │ │ │ ├── ChangeListener.tsx │ │ │ └── ChangeListenerNative.tsx │ │ ├── chart-color-schemes.ts │ │ ├── Charts │ │ │ ├── AreaChart │ │ │ │ ├── AreaChart.md │ │ │ │ ├── AreaChart.spec.ts │ │ │ │ ├── AreaChart.tsx │ │ │ │ └── AreaChartNative.tsx │ │ │ ├── BarChart │ │ │ │ ├── BarChart.md │ │ │ │ ├── BarChart.module.scss │ │ │ │ ├── BarChart.spec.ts │ │ │ │ ├── BarChart.tsx │ │ │ │ └── BarChartNative.tsx │ │ │ ├── DonutChart │ │ │ │ ├── DonutChart.spec.ts │ │ │ │ └── DonutChart.tsx │ │ │ ├── LabelList │ │ │ │ ├── LabelList.spec.ts │ │ │ │ ├── LabelList.tsx │ │ │ │ ├── LabelListNative.module.scss │ │ │ │ └── LabelListNative.tsx │ │ │ ├── Legend │ │ │ │ ├── Legend.spec.ts │ │ │ │ ├── Legend.tsx │ │ │ │ └── LegendNative.tsx │ │ │ ├── LineChart │ │ │ │ ├── LineChart.md │ │ │ │ ├── LineChart.module.scss │ │ │ │ ├── LineChart.spec.ts │ │ │ │ ├── LineChart.tsx │ │ │ │ └── LineChartNative.tsx │ │ │ ├── PieChart │ │ │ │ ├── PieChart.md │ │ │ │ ├── PieChart.spec.ts │ │ │ │ ├── PieChart.tsx │ │ │ │ ├── PieChartNative.module.scss │ │ │ │ └── PieChartNative.tsx │ │ │ ├── RadarChart │ │ │ │ ├── RadarChart.md │ │ │ │ ├── RadarChart.spec.ts │ │ │ │ ├── RadarChart.tsx │ │ │ │ └── RadarChartNative.tsx │ │ │ ├── Tooltip │ │ │ │ ├── TooltipContent.module.scss │ │ │ │ ├── TooltipContent.spec.ts │ │ │ │ └── TooltipContent.tsx │ │ │ └── utils │ │ │ ├── abstractions.ts │ │ │ └── ChartProvider.tsx │ │ ├── Checkbox │ │ │ ├── Checkbox.md │ │ │ ├── Checkbox.spec.ts │ │ │ └── Checkbox.tsx │ │ ├── CodeBlock │ │ │ ├── CodeBlock.module.scss │ │ │ ├── CodeBlock.spec.ts │ │ │ ├── CodeBlock.tsx │ │ │ ├── CodeBlockNative.tsx │ │ │ └── highlight-code.ts │ │ ├── collectedComponentMetadata.ts │ │ ├── ColorPicker │ │ │ ├── ColorPicker.md │ │ │ ├── ColorPicker.module.scss │ │ │ ├── ColorPicker.spec.ts │ │ │ ├── ColorPicker.tsx │ │ │ └── ColorPickerNative.tsx │ │ ├── Column │ │ │ ├── Column.md │ │ │ ├── Column.tsx │ │ │ ├── ColumnNative.tsx │ │ │ ├── doc-resources │ │ │ │ └── list-component-data.js │ │ │ └── TableContext.tsx │ │ ├── component-utils.ts │ │ ├── ComponentProvider.tsx │ │ ├── ComponentRegistryContext.tsx │ │ ├── container-helpers.tsx │ │ ├── ContentSeparator │ │ │ ├── ContentSeparator.md │ │ │ ├── ContentSeparator.module.scss │ │ │ ├── ContentSeparator.spec.ts │ │ │ ├── ContentSeparator.tsx │ │ │ └── ContentSeparatorNative.tsx │ │ ├── DataSource │ │ │ ├── DataSource.md │ │ │ └── DataSource.tsx │ │ ├── DateInput │ │ │ ├── DateInput.md │ │ │ ├── DateInput.module.scss │ │ │ ├── DateInput.spec.ts │ │ │ ├── DateInput.tsx │ │ │ └── DateInputNative.tsx │ │ ├── DatePicker │ │ │ ├── DatePicker.md │ │ │ ├── DatePicker.module.scss │ │ │ ├── DatePicker.spec.ts │ │ │ ├── DatePicker.tsx │ │ │ └── DatePickerNative.tsx │ │ ├── DropdownMenu │ │ │ ├── DropdownMenu.md │ │ │ ├── DropdownMenu.module.scss │ │ │ ├── DropdownMenu.spec.ts │ │ │ ├── DropdownMenu.tsx │ │ │ ├── DropdownMenuNative.tsx │ │ │ ├── MenuItem.md │ │ │ └── SubMenuItem.md │ │ ├── EmojiSelector │ │ │ ├── EmojiSelector.md │ │ │ ├── EmojiSelector.spec.ts │ │ │ ├── EmojiSelector.tsx │ │ │ └── EmojiSelectorNative.tsx │ │ ├── ExpandableItem │ │ │ ├── ExpandableItem.module.scss │ │ │ ├── ExpandableItem.spec.ts │ │ │ ├── ExpandableItem.tsx │ │ │ └── ExpandableItemNative.tsx │ │ ├── FileInput │ │ │ ├── FileInput.md │ │ │ ├── FileInput.module.scss │ │ │ ├── FileInput.spec.ts │ │ │ ├── FileInput.tsx │ │ │ └── FileInputNative.tsx │ │ ├── FileUploadDropZone │ │ │ ├── FileUploadDropZone.md │ │ │ ├── FileUploadDropZone.module.scss │ │ │ ├── FileUploadDropZone.spec.ts │ │ │ ├── FileUploadDropZone.tsx │ │ │ └── FileUploadDropZoneNative.tsx │ │ ├── FlowLayout │ │ │ ├── FlowLayout.md │ │ │ ├── FlowLayout.module.scss │ │ │ ├── FlowLayout.spec.ts │ │ │ ├── FlowLayout.spec.ts-snapshots │ │ │ │ └── Edge-cases-boxShadow-is-not-clipped-1-non-smoke-darwin.png │ │ │ ├── FlowLayout.tsx │ │ │ └── FlowLayoutNative.tsx │ │ ├── Footer │ │ │ ├── Footer.md │ │ │ ├── Footer.module.scss │ │ │ ├── Footer.spec.ts │ │ │ ├── Footer.tsx │ │ │ └── FooterNative.tsx │ │ ├── Form │ │ │ ├── Form.md │ │ │ ├── Form.module.scss │ │ │ ├── Form.spec.ts │ │ │ ├── Form.tsx │ │ │ ├── formActions.ts │ │ │ ├── FormContext.ts │ │ │ └── FormNative.tsx │ │ ├── FormItem │ │ │ ├── FormItem.md │ │ │ ├── FormItem.module.scss │ │ │ ├── FormItem.spec.ts │ │ │ ├── FormItem.tsx │ │ │ ├── FormItemNative.tsx │ │ │ ├── HelperText.module.scss │ │ │ ├── HelperText.tsx │ │ │ ├── ItemWithLabel.tsx │ │ │ └── Validations.ts │ │ ├── FormSection │ │ │ ├── FormSection.md │ │ │ ├── FormSection.ts │ │ │ └── FormSection.xmlui │ │ ├── Fragment │ │ │ ├── Fragment.spec.ts │ │ │ └── Fragment.tsx │ │ ├── Heading │ │ │ ├── abstractions.ts │ │ │ ├── H1.md │ │ │ ├── H1.spec.ts │ │ │ ├── H2.md │ │ │ ├── H2.spec.ts │ │ │ ├── H3.md │ │ │ ├── H3.spec.ts │ │ │ ├── H4.md │ │ │ ├── H4.spec.ts │ │ │ ├── H5.md │ │ │ ├── H5.spec.ts │ │ │ ├── H6.md │ │ │ ├── H6.spec.ts │ │ │ ├── Heading.md │ │ │ ├── Heading.module.scss │ │ │ ├── Heading.spec.ts │ │ │ ├── Heading.tsx │ │ │ └── HeadingNative.tsx │ │ ├── HoverCard │ │ │ ├── HoverCard.tsx │ │ │ └── HovercardNative.tsx │ │ ├── HtmlTags │ │ │ ├── HtmlTags.module.scss │ │ │ ├── HtmlTags.spec.ts │ │ │ └── HtmlTags.tsx │ │ ├── Icon │ │ │ ├── AdmonitionDanger.tsx │ │ │ ├── AdmonitionInfo.tsx │ │ │ ├── AdmonitionNote.tsx │ │ │ ├── AdmonitionTip.tsx │ │ │ ├── AdmonitionWarning.tsx │ │ │ ├── ApiIcon.tsx │ │ │ ├── ArrowDropDown.module.scss │ │ │ ├── ArrowDropDown.tsx │ │ │ ├── ArrowDropUp.module.scss │ │ │ ├── ArrowDropUp.tsx │ │ │ ├── ArrowLeft.module.scss │ │ │ ├── ArrowLeft.tsx │ │ │ ├── ArrowRight.module.scss │ │ │ ├── ArrowRight.tsx │ │ │ ├── Attach.tsx │ │ │ ├── Binding.module.scss │ │ │ ├── Binding.tsx │ │ │ ├── BoardIcon.tsx │ │ │ ├── BoxIcon.tsx │ │ │ ├── CheckIcon.tsx │ │ │ ├── ChevronDownIcon.tsx │ │ │ ├── ChevronLeft.tsx │ │ │ ├── ChevronRight.tsx │ │ │ ├── ChevronUpIcon.tsx │ │ │ ├── CodeFileIcon.tsx │ │ │ ├── CodeSandbox.tsx │ │ │ ├── CompactListIcon.tsx │ │ │ ├── ContentCopyIcon.tsx │ │ │ ├── DarkToLightIcon.tsx │ │ │ ├── DatabaseIcon.module.scss │ │ │ ├── DatabaseIcon.tsx │ │ │ ├── DocFileIcon.tsx │ │ │ ├── DocIcon.tsx │ │ │ ├── DotMenuHorizontalIcon.tsx │ │ │ ├── DotMenuIcon.tsx │ │ │ ├── EmailIcon.tsx │ │ │ ├── EmptyFolderIcon.tsx │ │ │ ├── ErrorIcon.tsx │ │ │ ├── ExpressionIcon.tsx │ │ │ ├── FillPlusCricleIcon.tsx │ │ │ ├── FilterIcon.tsx │ │ │ ├── FolderIcon.tsx │ │ │ ├── GlobeIcon.tsx │ │ │ ├── HomeIcon.tsx │ │ │ ├── HyperLinkIcon.tsx │ │ │ ├── Icon.md │ │ │ ├── Icon.module.scss │ │ │ ├── Icon.spec.ts │ │ │ ├── Icon.tsx │ │ │ ├── IconNative.tsx │ │ │ ├── ImageFileIcon.tsx │ │ │ ├── Inspect.tsx │ │ │ ├── LightToDark.tsx │ │ │ ├── LinkIcon.tsx │ │ │ ├── ListIcon.tsx │ │ │ ├── LooseListIcon.tsx │ │ │ ├── MoonIcon.tsx │ │ │ ├── MoreOptionsIcon.tsx │ │ │ ├── NoSortIcon.tsx │ │ │ ├── PDFIcon.tsx │ │ │ ├── PenIcon.tsx │ │ │ ├── PhoneIcon.tsx │ │ │ ├── PhotoIcon.tsx │ │ │ ├── PlusIcon.tsx │ │ │ ├── SearchIcon.tsx │ │ │ ├── ShareIcon.tsx │ │ │ ├── SortAscendingIcon.tsx │ │ │ ├── SortDescendingIcon.tsx │ │ │ ├── StarsIcon.tsx │ │ │ ├── SunIcon.tsx │ │ │ ├── svg │ │ │ │ ├── admonition_danger.svg │ │ │ │ ├── admonition_info.svg │ │ │ │ ├── admonition_note.svg │ │ │ │ ├── admonition_tip.svg │ │ │ │ ├── admonition_warning.svg │ │ │ │ ├── api.svg │ │ │ │ ├── arrow-dropdown.svg │ │ │ │ ├── arrow-left.svg │ │ │ │ ├── arrow-right.svg │ │ │ │ ├── arrow-up.svg │ │ │ │ ├── attach.svg │ │ │ │ ├── binding.svg │ │ │ │ ├── box.svg │ │ │ │ ├── bulb.svg │ │ │ │ ├── code-file.svg │ │ │ │ ├── code-sandbox.svg │ │ │ │ ├── dark_to_light.svg │ │ │ │ ├── database.svg │ │ │ │ ├── doc.svg │ │ │ │ ├── empty-folder.svg │ │ │ │ ├── expression.svg │ │ │ │ ├── eye-closed.svg │ │ │ │ ├── eye-dark.svg │ │ │ │ ├── eye.svg │ │ │ │ ├── file-text.svg │ │ │ │ ├── filter.svg │ │ │ │ ├── folder.svg │ │ │ │ ├── img.svg │ │ │ │ ├── inspect.svg │ │ │ │ ├── light_to_dark.svg │ │ │ │ ├── moon.svg │ │ │ │ ├── pdf.svg │ │ │ │ ├── photo.svg │ │ │ │ ├── share.svg │ │ │ │ ├── stars.svg │ │ │ │ ├── sun.svg │ │ │ │ ├── trending-down.svg │ │ │ │ ├── trending-level.svg │ │ │ │ ├── trending-up.svg │ │ │ │ ├── txt.svg │ │ │ │ ├── unknown-file.svg │ │ │ │ ├── unlink.svg │ │ │ │ └── xls.svg │ │ │ ├── TableDeleteColumnIcon.tsx │ │ │ ├── TableDeleteRowIcon.tsx │ │ │ ├── TableInsertColumnIcon.tsx │ │ │ ├── TableInsertRowIcon.tsx │ │ │ ├── TrashIcon.tsx │ │ │ ├── TrendingDownIcon.tsx │ │ │ ├── TrendingLevelIcon.tsx │ │ │ ├── TrendingUpIcon.tsx │ │ │ ├── TxtIcon.tsx │ │ │ ├── UnknownFileIcon.tsx │ │ │ ├── UnlinkIcon.tsx │ │ │ ├── UserIcon.tsx │ │ │ ├── WarningIcon.tsx │ │ │ └── XlsIcon.tsx │ │ ├── IconProvider.tsx │ │ ├── IconRegistryContext.tsx │ │ ├── IFrame │ │ │ ├── IFrame.md │ │ │ ├── IFrame.module.scss │ │ │ ├── IFrame.spec.ts │ │ │ ├── IFrame.tsx │ │ │ └── IFrameNative.tsx │ │ ├── Image │ │ │ ├── Image.md │ │ │ ├── Image.module.scss │ │ │ ├── Image.spec.ts │ │ │ ├── Image.tsx │ │ │ └── ImageNative.tsx │ │ ├── Input │ │ │ ├── index.ts │ │ │ ├── InputAdornment.module.scss │ │ │ ├── InputAdornment.tsx │ │ │ ├── InputDivider.module.scss │ │ │ ├── InputDivider.tsx │ │ │ ├── InputLabel.module.scss │ │ │ ├── InputLabel.tsx │ │ │ ├── PartialInput.module.scss │ │ │ └── PartialInput.tsx │ │ ├── InspectButton │ │ │ ├── InspectButton.module.scss │ │ │ └── InspectButton.tsx │ │ ├── Items │ │ │ ├── Items.md │ │ │ ├── Items.spec.ts │ │ │ ├── Items.tsx │ │ │ └── ItemsNative.tsx │ │ ├── Link │ │ │ ├── Link.md │ │ │ ├── Link.module.scss │ │ │ ├── Link.spec.ts │ │ │ ├── Link.tsx │ │ │ └── LinkNative.tsx │ │ ├── List │ │ │ ├── doc-resources │ │ │ │ └── list-component-data.js │ │ │ ├── List.md │ │ │ ├── List.module.scss │ │ │ ├── List.spec.ts │ │ │ ├── List.tsx │ │ │ └── ListNative.tsx │ │ ├── Logo │ │ │ ├── doc-resources │ │ │ │ └── xmlui-logo.svg │ │ │ ├── Logo.md │ │ │ ├── Logo.tsx │ │ │ └── LogoNative.tsx │ │ ├── Markdown │ │ │ ├── CodeText.module.scss │ │ │ ├── CodeText.tsx │ │ │ ├── Markdown.md │ │ │ ├── Markdown.module.scss │ │ │ ├── Markdown.spec.ts │ │ │ ├── Markdown.tsx │ │ │ ├── MarkdownNative.tsx │ │ │ ├── parse-binding-expr.ts │ │ │ └── utils.ts │ │ ├── metadata-helpers.ts │ │ ├── ModalDialog │ │ │ ├── ConfirmationModalContextProvider.tsx │ │ │ ├── Dialog.module.scss │ │ │ ├── Dialog.tsx │ │ │ ├── ModalDialog.md │ │ │ ├── ModalDialog.module.scss │ │ │ ├── ModalDialog.spec.ts │ │ │ ├── ModalDialog.tsx │ │ │ ├── ModalDialogNative.tsx │ │ │ └── ModalVisibilityContext.tsx │ │ ├── NavGroup │ │ │ ├── NavGroup.md │ │ │ ├── NavGroup.module.scss │ │ │ ├── NavGroup.spec.ts │ │ │ ├── NavGroup.tsx │ │ │ ├── NavGroupContext.ts │ │ │ └── NavGroupNative.tsx │ │ ├── NavLink │ │ │ ├── NavLink.md │ │ │ ├── NavLink.module.scss │ │ │ ├── NavLink.spec.ts │ │ │ ├── NavLink.tsx │ │ │ └── NavLinkNative.tsx │ │ ├── NavPanel │ │ │ ├── NavPanel.md │ │ │ ├── NavPanel.module.scss │ │ │ ├── NavPanel.spec.ts │ │ │ ├── NavPanel.tsx │ │ │ └── NavPanelNative.tsx │ │ ├── NestedApp │ │ │ ├── AppWithCodeView.module.scss │ │ │ ├── AppWithCodeView.tsx │ │ │ ├── AppWithCodeViewNative.tsx │ │ │ ├── defaultProps.tsx │ │ │ ├── logo.svg │ │ │ ├── NestedApp.module.scss │ │ │ ├── NestedApp.tsx │ │ │ ├── NestedAppNative.tsx │ │ │ ├── Tooltip.module.scss │ │ │ ├── Tooltip.tsx │ │ │ └── utils.ts │ │ ├── NoResult │ │ │ ├── NoResult.md │ │ │ ├── NoResult.module.scss │ │ │ ├── NoResult.spec.ts │ │ │ ├── NoResult.tsx │ │ │ └── NoResultNative.tsx │ │ ├── NumberBox │ │ │ ├── numberbox-abstractions.ts │ │ │ ├── NumberBox.md │ │ │ ├── NumberBox.module.scss │ │ │ ├── NumberBox.spec.ts │ │ │ ├── NumberBox.tsx │ │ │ └── NumberBoxNative.tsx │ │ ├── Option │ │ │ ├── Option.md │ │ │ ├── Option.spec.ts │ │ │ ├── Option.tsx │ │ │ ├── OptionNative.tsx │ │ │ └── OptionTypeProvider.tsx │ │ ├── PageMetaTitle │ │ │ ├── PageMetaTilteNative.tsx │ │ │ ├── PageMetaTitle.md │ │ │ ├── PageMetaTitle.spec.ts │ │ │ └── PageMetaTitle.tsx │ │ ├── Pages │ │ │ ├── Page.md │ │ │ ├── Pages.md │ │ │ ├── Pages.module.scss │ │ │ ├── Pages.tsx │ │ │ └── PagesNative.tsx │ │ ├── Pagination │ │ │ ├── Pagination.md │ │ │ ├── Pagination.module.scss │ │ │ ├── Pagination.spec.ts │ │ │ ├── Pagination.tsx │ │ │ └── PaginationNative.tsx │ │ ├── PositionedContainer │ │ │ ├── PositionedContainer.module.scss │ │ │ ├── PositionedContainer.tsx │ │ │ └── PositionedContainerNative.tsx │ │ ├── ProfileMenu │ │ │ ├── ProfileMenu.module.scss │ │ │ └── ProfileMenu.tsx │ │ ├── ProgressBar │ │ │ ├── ProgressBar.md │ │ │ ├── ProgressBar.module.scss │ │ │ ├── ProgressBar.spec.ts │ │ │ ├── ProgressBar.tsx │ │ │ └── ProgressBarNative.tsx │ │ ├── Queue │ │ │ ├── Queue.md │ │ │ ├── Queue.spec.ts │ │ │ ├── Queue.tsx │ │ │ ├── queueActions.ts │ │ │ └── QueueNative.tsx │ │ ├── RadioGroup │ │ │ ├── RadioGroup.md │ │ │ ├── RadioGroup.module.scss │ │ │ ├── RadioGroup.spec.ts │ │ │ ├── RadioGroup.tsx │ │ │ ├── RadioGroupNative.tsx │ │ │ ├── RadioItem.tsx │ │ │ └── RadioItemNative.tsx │ │ ├── RealTimeAdapter │ │ │ ├── RealTimeAdapter.tsx │ │ │ └── RealTimeAdapterNative.tsx │ │ ├── Redirect │ │ │ ├── Redirect.md │ │ │ ├── Redirect.spec.ts │ │ │ └── Redirect.tsx │ │ ├── ResponsiveBar │ │ │ ├── README.md │ │ │ ├── ResponsiveBar.md │ │ │ ├── ResponsiveBar.module.scss │ │ │ ├── ResponsiveBar.spec.ts │ │ │ ├── ResponsiveBar.tsx │ │ │ └── ResponsiveBarNative.tsx │ │ ├── Select │ │ │ ├── HiddenOption.tsx │ │ │ ├── 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.mjs │ ├── logging │ │ ├── LoggerContext.tsx │ │ ├── LoggerInitializer.tsx │ │ ├── LoggerService.ts │ │ └── xmlui.ts │ ├── logo.svg │ ├── parsers │ │ ├── common │ │ │ ├── GenericToken.ts │ │ │ ├── InputStream.ts │ │ │ └── utils.ts │ │ ├── scripting │ │ │ ├── code-behind-collect.ts │ │ │ ├── Lexer.ts │ │ │ ├── modules.ts │ │ │ ├── Parser.ts │ │ │ ├── ParserError.ts │ │ │ ├── ScriptingNodeTypes.ts │ │ │ ├── TokenTrait.ts │ │ │ ├── TokenType.ts │ │ │ └── tree-visitor.ts │ │ ├── style-parser │ │ │ ├── errors.ts │ │ │ ├── source-tree.ts │ │ │ ├── StyleInputStream.ts │ │ │ ├── StyleLexer.ts │ │ │ ├── StyleParser.ts │ │ │ └── tokens.ts │ │ └── xmlui-parser │ │ ├── CharacterCodes.ts │ │ ├── diagnostics.ts │ │ ├── fileExtensions.ts │ │ ├── index.ts │ │ ├── lint.ts │ │ ├── parser.ts │ │ ├── ParserError.ts │ │ ├── scanner.ts │ │ ├── syntax-kind.ts │ │ ├── syntax-node.ts │ │ ├── transform.ts │ │ ├── utils.ts │ │ ├── xmlui-serializer.ts │ │ └── xmlui-tree.ts │ ├── react-app-env.d.ts │ ├── syntax │ │ ├── monaco │ │ │ ├── grammar.monacoLanguage.ts │ │ │ ├── index.ts │ │ │ ├── xmlui-dark.ts │ │ │ ├── xmlui-light.ts │ │ │ └── xmluiscript.monacoLanguage.ts │ │ └── textMate │ │ ├── index.ts │ │ ├── xmlui-dark.json │ │ ├── xmlui-light.json │ │ ├── xmlui.json │ │ └── xmlui.tmLanguage.json │ ├── testing │ │ ├── assertions.ts │ │ ├── component-test-helpers.ts │ │ ├── ComponentDrivers.ts │ │ ├── drivers │ │ │ ├── DateInputDriver.ts │ │ │ ├── ModalDialogDriver.ts │ │ │ ├── NumberBoxDriver.ts │ │ │ ├── TextBoxDriver.ts │ │ │ ├── TimeInputDriver.ts │ │ │ ├── TimerDriver.ts │ │ │ └── TreeDriver.ts │ │ ├── fixtures.ts │ │ ├── infrastructure │ │ │ ├── index.html │ │ │ ├── main.tsx │ │ │ ├── public │ │ │ │ ├── mockServiceWorker.js │ │ │ │ ├── resources │ │ │ │ │ ├── bell.svg │ │ │ │ │ ├── box.svg │ │ │ │ │ ├── doc.svg │ │ │ │ │ ├── eye.svg │ │ │ │ │ ├── flower-640x480.jpg │ │ │ │ │ ├── sun.svg │ │ │ │ │ ├── test-image-100x100.jpg │ │ │ │ │ └── txt.svg │ │ │ │ └── serve.json │ │ │ └── TestBed.tsx │ │ └── themed-app-test-helpers.ts │ └── vite-env.d.ts ├── tests │ ├── components │ │ ├── CodeBlock │ │ │ └── hightlight-code.test.ts │ │ ├── playground-pattern.test.ts │ │ └── Tree │ │ └── Tree-states.test.ts │ ├── components-core │ │ ├── abstractions │ │ │ └── treeAbstractions.test.ts │ │ ├── container │ │ │ └── buildProxy.test.ts │ │ ├── interception │ │ │ ├── orderBy.test.ts │ │ │ ├── ReadOnlyCollection.test.ts │ │ │ └── request-param-converter.test.ts │ │ ├── scripts-runner │ │ │ ├── AttributeValueParser.test.ts │ │ │ ├── eval-tree-arrow-async.test.ts │ │ │ ├── eval-tree-arrow.test.ts │ │ │ ├── eval-tree-func-decl-async.test.ts │ │ │ ├── eval-tree-func-decl.test.ts │ │ │ ├── eval-tree-pre-post.test.ts │ │ │ ├── eval-tree-regression.test.ts │ │ │ ├── eval-tree.test.ts │ │ │ ├── function-proxy.test.ts │ │ │ ├── parser-regression.test.ts │ │ │ ├── process-event.test.ts │ │ │ ├── process-function.test.ts │ │ │ ├── process-implicit-context.test.ts │ │ │ ├── process-statement-asgn.test.ts │ │ │ ├── process-statement-destruct.test.ts │ │ │ ├── process-statement-regs.test.ts │ │ │ ├── process-statement-sync.test.ts │ │ │ ├── process-statement.test.ts │ │ │ ├── process-switch-sync.test.ts │ │ │ ├── process-switch.test.ts │ │ │ ├── process-try-sync.test.ts │ │ │ ├── process-try.test.ts │ │ │ └── test-helpers.ts │ │ ├── test-metadata-handler.ts │ │ ├── theming │ │ │ ├── border-segments.test.ts │ │ │ ├── component-layout.resolver.test.ts │ │ │ ├── layout-property-parser.test.ts │ │ │ ├── layout-resolver.test.ts │ │ │ ├── layout-resolver2.test.ts │ │ │ ├── layout-vp-override.test.ts │ │ │ └── padding-segments.test.ts │ │ └── utils │ │ ├── date-utils.test.ts │ │ ├── format-human-elapsed-time.test.ts │ │ └── LruCache.test.ts │ ├── language-server │ │ ├── completion.test.ts │ │ ├── format.test.ts │ │ ├── hover.test.ts │ │ └── mockData.ts │ └── parsers │ ├── common │ │ └── input-stream.test.ts │ ├── markdown │ │ └── parse-binding-expression.test.ts │ ├── parameter-parser.test.ts │ ├── paremeter-parser.test.ts │ ├── scripting │ │ ├── eval-tree-arrow.test.ts │ │ ├── eval-tree-pre-post.test.ts │ │ ├── eval-tree.test.ts │ │ ├── function-proxy.test.ts │ │ ├── lexer-literals.test.ts │ │ ├── lexer-misc.test.ts │ │ ├── module-parse.test.ts │ │ ├── parser-arrow.test.ts │ │ ├── parser-assignments.test.ts │ │ ├── parser-binary.test.ts │ │ ├── parser-destructuring.test.ts │ │ ├── parser-errors.test.ts │ │ ├── parser-expressions.test.ts │ │ ├── parser-function.test.ts │ │ ├── parser-literals.test.ts │ │ ├── parser-primary.test.ts │ │ ├── parser-regex.test.ts │ │ ├── parser-statements.test.ts │ │ ├── parser-unary.test.ts │ │ ├── process-event.test.ts │ │ ├── process-implicit-context.test.ts │ │ ├── process-statement-asgn.test.ts │ │ ├── process-statement-destruct.test.ts │ │ ├── process-statement-regs.test.ts │ │ ├── process-statement-sync.test.ts │ │ ├── process-statement.test.ts │ │ ├── process-switch-sync.test.ts │ │ ├── process-switch.test.ts │ │ ├── process-try-sync.test.ts │ │ ├── process-try.test.ts │ │ ├── simplify-expression.test.ts │ │ ├── statement-hooks.test.ts │ │ └── test-helpers.ts │ ├── style-parser │ │ ├── generateHvarChain.test.ts │ │ ├── parseHVar.test.ts │ │ ├── parser.test.ts │ │ └── tokens.test.ts │ └── xmlui │ ├── lint.test.ts │ ├── parser.test.ts │ ├── scanner.test.ts │ ├── transform.attr.test.ts │ ├── transform.circular.test.ts │ ├── transform.element.test.ts │ ├── transform.errors.test.ts │ ├── transform.escape.test.ts │ ├── transform.regression.test.ts │ ├── transform.script.test.ts │ ├── transform.test.ts │ └── xmlui.ts ├── tests-e2e │ ├── api-bound-component-regression.spec.ts │ ├── api-call-as-extracted-component.spec.ts │ ├── assign-to-object-or-array-regression.spec.ts │ ├── binding-regression.spec.ts │ ├── children-as-template-context-vars.spec.ts │ ├── compound-component.spec.ts │ ├── context-vars-regression.spec.ts │ ├── data-bindings.spec.ts │ ├── datasource-and-api-usage-in-var.spec.ts │ ├── datasource-direct-binding.spec.ts │ ├── datasource-onLoaded-regression.spec.ts │ ├── modify-array-item-regression.spec.ts │ ├── namespaces.spec.ts │ ├── push-to-array-regression.spec.ts │ ├── screen-breakpoints.spec.ts │ ├── scripting.spec.ts │ ├── state-scope-in-pages.spec.ts │ └── state-var-scopes.spec.ts ├── tsconfig.bin.json ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── vitest.config.ts ``` # Files -------------------------------------------------------------------------------- /xmlui/tests/parsers/xmlui/transform.errors.test.ts: -------------------------------------------------------------------------------- ```typescript import { describe, expect, it, assert } from "vitest"; import { transformSource } from "./xmlui"; describe("Xmlui transform - errors", () => { it("Missing name in compound component", () => { try { transformSource("<Component />"); assert.fail("Exception expected"); } catch (err) { expect(err.toString()).includes("T003"); } }); it("Invalid name in compound component #1", () => { try { transformSource("<Component name=''/>"); assert.fail("Exception expected"); } catch (err) { expect(err.toString()).includes("T004"); } }); it("Invalid name in compound component #2", () => { try { transformSource("<Component name='alma'/>"); assert.fail("Exception expected"); } catch (err) { expect(err.toString()).includes("T004"); } }); it("Compound child in compound component", () => { try { transformSource("<Component name='MyComp'><Component /></Component>"); assert.fail("Exception expected"); } catch (err) { expect(err.toString()).includes("T006"); } }); it("Invalid attribute in compound component", () => { try { transformSource("<Component name='MyComp' blabla='123'><Stack/></Component>"); assert.fail("Exception expected"); } catch (err) { expect(err.toString()).includes("T021"); } }); it("Event attribute starts with 'on'", () => { try { transformSource("<Stack><event name='onClick' /></Stack>"); assert.fail("Exception expected"); } catch (err) { expect(err.toString()).includes("T008"); } }); it("'uses' is invalid in a compound component", () => { try { transformSource("<Component name='MyComp'><Stack/><uses /></Component>"); assert.fail("Exception expected"); } catch (err) { expect(err.toString()).includes("T009"); } }); it("'loaders' is invalid in a compound component", () => { try { transformSource("<Component name='MyComp'><Stack/><loaders /></Component>"); assert.fail("Exception expected"); } catch (err) { expect(err.toString()).includes("T009"); } }); it("Invalid attribute in prop", () => { try { transformSource("<Stack><property name='my' blabal='123'/></Stack>"); assert.fail("Exception expected"); } catch (err) { expect(err.toString()).includes("T011"); } }); it("Invalid attribute in event", () => { try { transformSource("<Stack><event name='my' blabal='123'/></Stack>"); assert.fail("Exception expected"); } catch (err) { expect(err.toString()).includes("T011"); } }); it("Invalid attribute in var", () => { try { transformSource("<Stack><variable name='my' blabal='123'/></Stack>"); assert.fail("Exception expected"); } catch (err) { expect(err.toString()).includes("T011"); } }); it("Invalid attribute in api", () => { try { transformSource("<Stack><property name='my' blabal='123'/></Stack>"); assert.fail("Exception expected"); } catch (err) { expect(err.toString()).includes("T011"); } }); it("Name required in prop #1", () => { try { transformSource("<Stack><property /></Stack>"); assert.fail("Exception expected"); } catch (err) { expect(err.toString()).includes("T012"); } }); it("Name required in prop #2", () => { try { transformSource("<Stack><property name='' /></Stack>"); assert.fail("Exception expected"); } catch (err) { expect(err.toString()).includes("T012"); } }); it("Name required in event #1", () => { try { transformSource("<Stack><event /></Stack>"); assert.fail("Exception expected"); } catch (err) { expect(err.toString()).includes("T012"); } }); it("Name required in event #2", () => { try { transformSource("<Stack><event name='' /></Stack>"); assert.fail("Exception expected"); } catch (err) { expect(err.toString()).includes("T012"); } }); it("Name required in var #1", () => { try { transformSource("<Stack><variable/></Stack>"); assert.fail("Exception expected"); } catch (err) { expect(err.toString()).includes("T012"); } }); it("Name required in var #2", () => { try { transformSource("<Stack><variable name='' /></Stack>"); assert.fail("Exception expected"); } catch (err) { expect(err.toString()).includes("T012"); } }); it("Name required in method #1", () => { try { transformSource("<Stack><method /></Stack>"); assert.fail("Exception expected"); } catch (err) { expect(err.toString()).includes("T012"); } }); it("Name required in api #2", () => { try { transformSource("<Stack><method name='' /></Stack>"); assert.fail("Exception expected"); } catch (err) { expect(err.toString()).includes("T012"); } }); it("A 'uses' must have value #1", () => { try { transformSource("<Stack><uses value=''/></Stack>"); assert.fail("Exception expected"); } catch (err) { expect(err.toString()).includes("T015"); } }); it("A 'uses' must have value #2", () => { try { transformSource("<Stack><uses/></Stack>"); assert.fail("Exception expected"); } catch (err) { expect(err.toString()).includes("T015"); } }); it("A 'value' can have 'field' and 'item' child", () => { try { transformSource("<Stack><property name='my'><dummy /></property></Stack>"); assert.fail("Exception expected"); } catch (err) { expect(err.toString()).includes("T016"); } }); it("Cannot mix field and item children #1", () => { try { transformSource( "<Stack><property name='my'><field name='my' /><item value='3'/></property></Stack>", ); assert.fail("Exception expected"); } catch (err) { expect(err.toString()).includes("T017"); } }); it("Cannot mix field and item children #2", () => { try { transformSource( `<Stack> <property name='my'> <item value='3'/> <field name='my' /> </property> </Stack>`, ); assert.fail("Exception expected"); } catch (err) { expect(err.toString()).includes("T017"); } }); it("Item cannot have a 'name' attribute", () => { try { transformSource("<Stack><property name='my'><item name='my' value='3'/></property></Stack>"); assert.fail("Exception expected"); } catch (err) { expect(err.toString()).includes("T018"); } }); it("throws script errors in script tag", () => { try { transformSource(` <App> <script> var a =; </script> <Text>Hello World!</Text> </App> `); assert.fail("Exception expected"); } catch (err) { expect(err.toString()).include("W001"); } }); }); ``` -------------------------------------------------------------------------------- /xmlui/src/abstractions/RendererDefs.ts: -------------------------------------------------------------------------------- ```typescript import type { CSSProperties, ForwardedRef, ReactNode, RefObject } from "react"; import type { AppContextObject } from "./AppContextDefs"; import type { ComponentDef, ComponentMetadata, CompoundComponentDef, DynamicChildComponentDef, ParentRenderContext, } from "./ComponentDefs"; import type { ContainerState } from "./ContainerDefs"; import type { LookupActionOptions, LookupAsyncFn, LookupSyncFn } from "./ActionDefs"; import type { AsyncFunction } from "./FunctionDefs"; import type {ComponentApi} from "../components-core/rendering/ContainerWrapper"; // This interface defines the renderer context for the exposed components of the // XMLUI framework. export interface RendererContext<TMd extends ComponentMetadata = ComponentMetadata> extends ComponentRendererContextBase<TMd> { uid: symbol; // The unique identifier of the component instance updateState: UpdateStateFn; // A component invokes this function to change its internal state // Context variables (all keys starting with "$") available in the current container contextVars: Record<string, any>; // When a component wants to access a property value (which may contain a binding // expression to evaluate), it must use this property to get the current value. extractValue: ValueExtractor; // This function gets a physical resource URL according to the provided logical URL. extractResourceUrl: (url?: string) => string | undefined; // This function gets an async executable function that handles an event. lookupEventHandler: LookupEventHandlerFn<TMd>; registerComponentApi: RegisterComponentApiFn; // A component can register its APIs with this function lookupAction: LookupAsyncFn; // This function obtains an action by its name with the specified options // This function retrieves a sync function the component can use as a callback lookupSyncCallback: LookupSyncFn; className?: string; } export type UpdateStateFn = (componentState: any, options?: any) => void; // This function updates the state of a component. // This type represent the function that extracts the value from a component property export type ValueExtractor = { (expression?: any, strict?: boolean): any; // Get a value (any) from a component property asString(expression?: any): string; // Get a string value from an expression // Get an optional string value from an expression asOptionalString<T extends string>(expression?: any, defValue?: string): T | undefined; // Get an optional string value from an expression asOptionalStringArray(expression?: any): (string | undefined)[]; asDisplayText(expression?: any): string; // Get a display string value from an expression asNumber(expression?: any): number; // Get a number value from an expression // Get an optional number value from an expression asOptionalNumber(expression?: any, defValue?: number): number | undefined; // Get a boolean value (JavaScript semantics) from an expression asBoolean(expression?: any): boolean; // Get an optional Boolean value from an expression asOptionalBoolean(expression?: any, defValue?: boolean): boolean | undefined; // Get a CSS size value from an expression asSize(expression?: any): string; }; // This function retrieves an async function for a particular component's specified // event to be invoked as an event handler (`undefined` if the particular event // handler is not defined). export type LookupEventHandlerFn<TMd extends ComponentMetadata = ComponentMetadata> = ( eventName: keyof NonNullable<TMd["events"]>, actionOptions?: LookupActionOptions, ) => AsyncFunction | undefined; // This type represents a function that registers all API endpoints of a particular component. export type RegisterComponentApiFn = (componentApi: ComponentApi) => void; // Function signature to render a particular child component (or set of child components) export type RenderChildFn<L extends ComponentDef = ComponentDef> = ( children?: | ComponentDef | ComponentDef[] | DynamicChildComponentDef | DynamicChildComponentDef[] | string, layoutContext?: LayoutContext<L>, parentRenderContext?: ParentRenderContext, uidInfoRef?: RefObject<Record<string, any>>, ref?: ForwardedRef<any>, rest?: Record<string, any> ) => ReactNode | ReactNode[]; // Each component is rendered in a particular layout context (for example, within a // stack). This type provides information about that context and the operations that // render children in it. export type LayoutContext<T extends ComponentDef = ComponentDef> = { type?: string; // The type of the layout context // This function allows the React representation of a particular child node to be // wrapped in whatever React components to accommodate the current layout context. // When the engine is about to render children in a particular layout context, it // checks the existence of this function. If declared, the engine invokes it. wrapChild?: ( context: RendererContext<T>, renderedChild: ReactNode, metadata?: ComponentMetadata, ) => ReactNode; // Arbitrary props extending the layout context [key: string]: any; }; export type NonCssLayoutProps = { horizontalAlignment?: string; verticalAlignment?: string; orientation?: string; }; // This function renders a component definition into a React component export type ComponentRendererFn<T extends ComponentDef> = ( context: RendererContext<T>, ) => ReactNode; // This function renders a component definition into a React component export type CompoundComponentRendererInfo = { compoundComponentDef: CompoundComponentDef; metadata?: ComponentMetadata; }; // Components must be registered with a component registry so the engine can use them. // This type collects the information held by the registry. export type ComponentRendererDef<T extends ComponentDef = any> = { // The component's type identifier. In the markup, the component must use this name // to be recognized. type: string; // This function renders the component from its definition to its React representation. renderer: ComponentRendererFn<T>; // The metadata to use when rendering the component metadata?: ComponentMetadata; }; // Rendering components (turning component definitions into their React node // representation) is a complicated process that requires information describing the // actual context. This interface defines the common properties of that context. export interface ComponentRendererContextBase<TMd extends ComponentMetadata = ComponentMetadata> { // The definition of the component to render node: ComponentDef<TMd>; // The state of the container in which the component is rendered state: ContainerState; // The application context the component (and its binding expressions) can use appContext?: AppContextObject; // The component can use this function to render its child components renderChild: RenderChildFn; // Information about the layout context in which the component is rendered layoutContext?: LayoutContext; } ``` -------------------------------------------------------------------------------- /xmlui/src/components-core/TableOfContentsContext.tsx: -------------------------------------------------------------------------------- ```typescript import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, } from "react"; import { useIsomorphicLayoutEffect, useScrollEventHandler, useScrollParent } from "./utils/hooks"; import { useNavigate } from "@remix-run/react"; import { EMPTY_ARRAY, EMPTY_OBJECT } from "./constants"; import { useAppContext } from "./AppContext"; // --- Stores the information about a particular heading to be displayed in the TOC. type HeadingItem = { // --- The id of the heading. id: string; // --- Heading level level: number; // --- Heading thext to display in the TOC. text: string; // --- Reference to the anchor element. anchor: HTMLAnchorElement | null; }; type ActiveAnchorChangedCallback = (id: string) => void; // --- The context object that is used to store the hierarchy of headings. interface ITableOfContentsContext { // --- The list of headings in the TOC headings: HeadingItem[]; // --- This method allows adding a new heading to the TOC. registerHeading: (headingItem: HeadingItem) => void; // --- This flag indicates whether the intersection observer is enabled. hasTableOfContents: boolean; // --- This method allows setting the id of the active anchor. scrollToAnchor: (id: string, smoothScrolling: boolean) => void; subscribeToActiveAnchorChange: (callback: ActiveAnchorChangedCallback) => () => void; activeAnchorId: string; } /** * Several components work together to represent the hierarchy of a particular * app page as a TOC. This React component provides a context for storing this * hierarchy information. */ export const TableOfContentsContext = createContext<ITableOfContentsContext | null>(null); /** * This provider component injects the specified children into the TOC context. */ export function TableOfContentsProvider({ children }: { children: React.ReactNode }) { const [headings, setHeadings] = useState<Record<string, HeadingItem>>(EMPTY_OBJECT); const [callbacks, setCallbacks] = useState<Array<ActiveAnchorChangedCallback>>(EMPTY_ARRAY); const observer = useRef<IntersectionObserver | null>(null); const {forceRefreshAnchorScroll} = useAppContext(); const thisRef = useRef({ suspendPositionBasedSetActiveId: false, }); const scrollParent = useScrollParent(Object.values(headings)?.[0]?.anchor); useScrollEventHandler(scrollParent, { onScrollEnd: useCallback(() => { thisRef.current.suspendPositionBasedSetActiveId = false; }, []), }); const [activeAnchorId, setActiveAnchorId] = useState(null); const notify = useCallback( (id) => { callbacks.forEach((cb) => cb(id)); setActiveAnchorId(id); }, [callbacks], ); useEffect(() => { if (callbacks.length) { const handleObserver = (entries: any) => { entries.forEach((entry: any) => { if (entry?.isIntersecting) { if (!thisRef.current.suspendPositionBasedSetActiveId) { notify(entry.target.id); } } }); }; // stolen from nextra: https://github.com/shuding/nextra/blob/3729f67059f1fbdd3f98125bebabbe568c918694/packages/nextra-theme-docs/src/mdx-components/heading-anchor.client.tsx // let headerHeight = getComputedStyle(scrollParent || document.body).getPropertyValue( // '--header-abs-height' // ); observer.current = new IntersectionObserver(handleObserver, { rootMargin: `0% 0% -80%`, // root: scrollParent, // threshold: [0, 1], }); Object.values(headings).forEach((elem) => observer?.current?.observe?.(elem.anchor!)); return () => observer.current?.disconnect(); } }, [callbacks.length, headings, notify, scrollParent]); const registerHeading = useCallback((headingItem: HeadingItem) => { setHeadings((prevHeadings) => { return { ...prevHeadings, [headingItem.id]: headingItem, }; }); return () => { setHeadings((prevHeadings) => { const newHeadings = { ...prevHeadings }; delete newHeadings[headingItem.id]; return newHeadings; }); }; }, []); const navigate = useNavigate(); const scrollToAnchor = useCallback( (id: string, smoothScrolling: boolean) => { const value = headings[id]; if (value) { thisRef.current.suspendPositionBasedSetActiveId = true; value.anchor.scrollIntoView({ block: "start", inline: "start", behavior: smoothScrolling ? "smooth" : "auto", }); notify(id); requestAnimationFrame(() => { navigate( { hash: `#${value.id}`, }, { state: { preventHashScroll: true, }, }, ); //we clear the preventHashScroll route state: https://stackoverflow.com/questions/72121228/how-to-update-location-state-in-react-router-v6 requestAnimationFrame(()=>{ navigate({ hash: `#${value.id}`, }, { replace: true }); }) }); } }, [headings, navigate, notify], ); const sortedHeadings = useMemo(() => { return Object.values(headings).sort(function (a, b) { if (a.anchor === b.anchor) return 0; if (a.anchor.compareDocumentPosition(b.anchor) & Node.DOCUMENT_POSITION_PRECEDING) { // b comes before a return 1; } return -1; }); }, [headings]); //the content could take time to load, this way we try to force the scroll to anchor mechanism to kick in const hasHeadings = sortedHeadings.length > 0; useIsomorphicLayoutEffect(()=>{ if(hasHeadings){ forceRefreshAnchorScroll(); } }, [forceRefreshAnchorScroll, hasHeadings]); const subscribeToActiveAnchorChange = useCallback((cb: ActiveAnchorChangedCallback) => { setCallbacks((prev) => { return [...prev, cb]; }); return () => { setCallbacks((prev) => { return prev.filter((item) => item !== cb); }); }; }, []); const contextValue: ITableOfContentsContext = useMemo(() => { return { registerHeading, headings: sortedHeadings, scrollToAnchor, subscribeToActiveAnchorChange, hasTableOfContents: callbacks.length > 0, activeAnchorId }; }, [ registerHeading, sortedHeadings, scrollToAnchor, subscribeToActiveAnchorChange, callbacks.length, activeAnchorId, ]); return ( <TableOfContentsContext.Provider value={contextValue}> {children} </TableOfContentsContext.Provider> ); } export function useTableOfContents() { const context = useContext(TableOfContentsContext); if (!context) { throw new Error(`The TableOfContents component can only be used inside a Page component. <App> <Pages> <Page url="/"> <Heading>Harry Potter and the Sorcerer's Stone</Heading> <TableOfContents /> </Page> </Pages> </App> `); } return context; } ``` -------------------------------------------------------------------------------- /xmlui/src/components/NumberBox/NumberBox.tsx: -------------------------------------------------------------------------------- ```typescript import styles from "./NumberBox.module.scss"; import { createComponentRenderer } from "../../components-core/renderers"; import { parseScssVar } from "../../components-core/theming/themeVars"; import { createMetadata, d, dAutoFocus, dDidChange, dEnabled, dEndIcon, dEndText, dGotFocus, dInitialValue, dLostFocus, dMaxLength, dPlaceholder, dReadonly, dRequired, dStartIcon, dStartText, dValidationStatus, } from "../metadata-helpers"; import { defaultProps, NumberBox } from "./NumberBoxNative"; const COMP = "NumberBox"; export const NumberBoxMd = createMetadata({ status: "stable", description: "`NumberBox` provides a specialized input field for numeric values with built-in " + "validation, spinner buttons, and flexible formatting options. It supports both " + "integer and floating-point numbers, handles empty states as null values, and " + "integrates seamlessly with form validation.", parts: { label: { description: "The label displayed for the text box.", }, startAdornment: { description: "The adornment displayed at the start of the text box.", }, endAdornment: { description: "The adornment displayed at the end of the text box.", }, input: { description: "The text box input area.", }, }, props: { placeholder: dPlaceholder(), initialValue: dInitialValue(), maxLength: dMaxLength(), autoFocus: dAutoFocus(), required: dRequired(), readOnly: dReadonly(), enabled: dEnabled(), validationStatus: dValidationStatus(), startText: dStartText(), startIcon: dStartIcon(), endText: dEndText(), endIcon: dEndIcon(), gap: { description: "This property defines the gap between the adornments and the input area.", }, hasSpinBox: { description: `This boolean prop shows (\`true\`) or hides (\`false\`) the spinner buttons for the input field.`, valueType: "boolean", defaultValue: defaultProps.hasSpinBox, }, spinnerUpIcon: d( `Allows setting an alternate icon displayed in the ${COMP} spinner for incrementing values. You can change ` + `the default icon for all ${COMP} instances with the "icon.spinnerUp:NumberBox" declaration in the ` + `app configuration file.`, ), spinnerDownIcon: d( `Allows setting an alternate icon displayed in the ${COMP} spinner for decrementing values. You can change ` + `the default icon for all ${COMP} instances with the "icon.spinnerDown:NumberBox" declaration in the ` + `app configuration file.`, ), step: { description: `This prop governs how big the step when clicking on the spinner of the field.`, valueType: "number", defaultValue: defaultProps.step, }, integersOnly: { description: `This boolean property signs whether the input field accepts integers only (\`true\`) ` + `or not (\`false\`).`, valueType: "boolean", defaultValue: defaultProps.integersOnly, }, zeroOrPositive: { description: `This boolean property determines whether the input value can only be 0 or positive numbers ` + `(\`true\`) or also negative (\`false\`).`, valueType: "boolean", defaultValue: defaultProps.zeroOrPositive, }, minValue: { description: "The minimum value the input field allows. Can be a float or an integer if " + "[\`integersOnly\`](#integersonly) is set to \`false\`, otherwise it can only be an integer." + "If not set, no minimum value check is done.", defaultValue: defaultProps.min, }, maxValue: { description: "The maximum value the input field allows. Can be a float or an integer if " + "[\`integersOnly\`](#integersonly) is set to \`false\`, otherwise it can only be an integer." + "If not set, no maximum value check is done.", defaultValue: defaultProps.max, }, }, events: { gotFocus: dGotFocus(COMP), lostFocus: dLostFocus(COMP), didChange: dDidChange(COMP), }, apis: { focus: { description: `This API focuses the input field of the \`${COMP}\`. You can use it to programmatically focus the field.`, signature: "focus(): void", }, value: { description: `This API retrieves the current value of the \`${COMP}\`. You can use it to get the value programmatically.`, signature: "get value(): number | undefined", }, setValue: { description: `This API sets the value of the \`${COMP}\`. You can use it to programmatically change the value.`, signature: "setValue(value: number | undefined): void", }, }, themeVars: parseScssVar(styles.themeVars), defaultThemeVars: { [`paddingVertical-${COMP}`]: "$space-2", [`paddingHorizontal-${COMP}`]: "$space-2", }, }); export const numberBoxComponentRenderer = createComponentRenderer( COMP, NumberBoxMd, ({ node, state, updateState, lookupEventHandler, extractValue, className, registerComponentApi, }) => { let extractedInitialValue; try { extractedInitialValue = extractValue.asOptionalNumber(node.props.initialValue); } catch {} return ( <NumberBox className={className} value={state?.value} initialValue={extractedInitialValue} step={extractValue(node.props.step)} enabled={extractValue.asOptionalBoolean(node.props.enabled)} placeholder={extractValue.asOptionalString(node.props.placeholder)} validationStatus={extractValue(node.props.validationStatus)} updateState={updateState} onDidChange={lookupEventHandler("didChange")} onFocus={lookupEventHandler("gotFocus")} onBlur={lookupEventHandler("lostFocus")} registerComponentApi={registerComponentApi} hasSpinBox={extractValue.asOptionalBoolean(node.props.hasSpinBox)} integersOnly={extractValue.asOptionalBoolean(node.props.integersOnly)} zeroOrPositive={extractValue.asOptionalBoolean(node.props.zeroOrPositive)} min={extractValue.asOptionalNumber(node.props.minValue)} max={extractValue.asOptionalNumber(node.props.maxValue)} startText={extractValue.asOptionalString(node.props.startText)} startIcon={extractValue.asOptionalString(node.props.startIcon)} endText={extractValue.asOptionalString(node.props.endText)} gap={extractValue.asOptionalString(node.props.gap)} endIcon={extractValue.asOptionalString(node.props.endIcon)} spinnerUpIcon={extractValue.asOptionalString(node.props.spinnerUpIcon)} spinnerDownIcon={extractValue.asOptionalString(node.props.spinnerDownIcon)} autoFocus={extractValue.asOptionalBoolean(node.props.autoFocus)} readOnly={extractValue.asOptionalBoolean(node.props.readOnly)} maxLength={extractValue(node.props.maxLength)} required={extractValue.asOptionalBoolean(node.props.required)} direction={extractValue(node.props.direction)} /> ); }, ); ``` -------------------------------------------------------------------------------- /xmlui/src/components/TextBox/TextBoxNative.tsx: -------------------------------------------------------------------------------- ```typescript import { type CSSProperties, type ForwardedRef, forwardRef, useId, useState } from "react"; import React, { useCallback, useEffect, useRef } from "react"; import classnames from "classnames"; import styles from "./TextBox.module.scss"; import type { RegisterComponentApiFn, UpdateStateFn } from "../../abstractions/RendererDefs"; import { noop } from "../../components-core/constants"; import { useEvent } from "../../components-core/utils/misc"; import { Adornment } from "../Input/InputAdornment"; import type { ValidationStatus } from "../abstractions"; import { PART_START_ADORNMENT, PART_INPUT, PART_END_ADORNMENT } from "../../components-core/parts"; /** * TextBox component that supports text input with various configurations. * Features: * - Standard text, password, and search input types * - Input validation states * - Start/end adornments (icons and text) * - Password visibility toggle option */ type Props = { id?: string; type?: "text" | "password" | "search"; value?: string; updateState?: UpdateStateFn; initialValue?: string; style?: CSSProperties; className?: string; maxLength?: number; enabled?: boolean; placeholder?: string; validationStatus?: ValidationStatus; onDidChange?: (newValue: string) => void; onFocus?: () => void; onBlur?: () => void; onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => void; registerComponentApi?: RegisterComponentApiFn; startText?: string; startIcon?: string; endText?: string; endIcon?: string; gap?: string; autoFocus?: boolean; readOnly?: boolean; tabIndex?: number; required?: boolean; /** * When true and type is "password", displays a toggle icon to show/hide password text * Default: false */ showPasswordToggle?: boolean; /** * The icon to show when the password is visible * Default: "eye" */ passwordVisibleIcon?: string; /** * The icon to show when the password is hidden * Default: "eye-off" */ passwordHiddenIcon?: string; }; export const defaultProps: Pick< Props, | "type" | "value" | "initialValue" | "enabled" | "validationStatus" | "onDidChange" | "onFocus" | "onBlur" | "onKeyDown" | "updateState" | "passwordVisibleIcon" | "passwordHiddenIcon" > = { type: "text", value: "", initialValue: "", enabled: true, validationStatus: "none", onDidChange: noop, onFocus: noop, onBlur: noop, onKeyDown: noop, updateState: noop, passwordVisibleIcon: "eye", passwordHiddenIcon: "eye-off", }; export const TextBox = forwardRef(function TextBox( { id, type = defaultProps.type, value = defaultProps.value, updateState = defaultProps.updateState, initialValue = defaultProps.initialValue, style, className, maxLength, enabled = defaultProps.enabled, placeholder, validationStatus = defaultProps.validationStatus, onDidChange = defaultProps.onDidChange, onFocus = defaultProps.onFocus, onBlur = defaultProps.onBlur, onKeyDown = defaultProps.onKeyDown, registerComponentApi, startText, startIcon, endText, endIcon, gap, autoFocus, readOnly, tabIndex, required, showPasswordToggle, passwordVisibleIcon = defaultProps.passwordVisibleIcon, passwordHiddenIcon = defaultProps.passwordHiddenIcon, ...rest }: Props, ref: ForwardedRef<HTMLDivElement>, ) { const inputRef = useRef<HTMLInputElement>(null); // State to control password visibility const [showPassword, setShowPassword] = useState(false); // Determine the actual input type based on the password visibility toggle const actualType = type === "password" && showPassword ? "text" : type; // Toggle password visibility const togglePasswordVisibility = useCallback(() => { setShowPassword((prev) => !prev); }, []); useEffect(() => { if (autoFocus) { setTimeout(() => { inputRef.current?.focus(); }, 0); } }, [autoFocus, inputRef]); // --- NOTE: This is a workaround for the jumping caret issue. // --- Local state can sync up values that can get set asynchronously outside the component. const [localValue, setLocalValue] = React.useState(value); useEffect(() => { setLocalValue(value); }, [value]); // --- End NOTE // --- Initialize the related field with the input's initial value useEffect(() => { updateState({ value: initialValue }, { initial: true }); }, [initialValue, updateState]); const updateValue = useCallback( (value: string) => { setLocalValue(value); updateState({ value }); onDidChange(value); }, [onDidChange, updateState], ); // --- Handle the value change events for this input const onInputChange = useCallback( (event: React.ChangeEvent<HTMLInputElement>) => { updateValue(event.target.value); }, [updateValue], ); // --- Manage obtaining and losing the focus const handleOnFocus = useCallback(() => { onFocus?.(); }, [onFocus]); const handleOnBlur = useCallback(() => { onBlur?.(); }, [onBlur]); const focus = useCallback(() => { inputRef.current?.focus(); }, []); const setValue = useEvent((newValue) => { updateValue(newValue); }); useEffect(() => { registerComponentApi?.({ focus, setValue, }); }, [focus, registerComponentApi, setValue]); return ( <div {...rest} ref={ref} className={classnames(className, styles.inputRoot, { [styles.disabled]: !enabled, [styles.readOnly]: readOnly, [styles.error]: validationStatus === "error", [styles.warning]: validationStatus === "warning", [styles.valid]: validationStatus === "valid", })} tabIndex={-1} onFocus={focus} style={{ ...style, gap }} > <Adornment data-part-id={PART_START_ADORNMENT} text={startText} iconName={startIcon} className={classnames(styles.adornment)} /> <input id={id} ref={inputRef} data-part-id={PART_INPUT} type={actualType} className={classnames(styles.input, { [styles.readOnly]: readOnly, })} disabled={!enabled} value={localValue} maxLength={maxLength} placeholder={placeholder} onChange={onInputChange} onFocus={handleOnFocus} onBlur={handleOnBlur} onKeyDown={onKeyDown} readOnly={readOnly} autoFocus={autoFocus} tabIndex={enabled ? tabIndex : -1} required={required} /> {type === "password" && showPasswordToggle ? ( <Adornment data-part-id={PART_END_ADORNMENT} iconName={showPassword ? passwordVisibleIcon : passwordHiddenIcon} className={classnames(styles.adornment, styles.passwordToggle)} onClick={togglePasswordVisibility} /> ) : ( <Adornment data-part-id={PART_END_ADORNMENT} text={endText} iconName={endIcon} className={styles.adornment} /> )} </div> ); }); ``` -------------------------------------------------------------------------------- /packages/xmlui-website-blocks/src/HeroSection/HeroSectionNative.tsx: -------------------------------------------------------------------------------- ```typescript import { forwardRef, ReactNode } from "react"; import { Button, Icon, Breakout, useTheme } from "xmlui"; import styles from "./HeroSection.module.scss"; import classnames from "classnames"; import { Theme } from "xmlui"; const PART_HEADER = "header"; const PART_CONTENT = "content"; const PART_HEADING_SECTION = "headingSection"; const PART_PREAMBLE = "preamble"; const PART_HEADLINE = "headline"; const PART_SUBHEADLINE = "subheadline"; const PART_MAIN_TEXT = "mainText"; const PART_CTA_BUTTON = "ctaButton"; const PART_IMAGE = "image"; const PART_BACKGROUND = "background"; type Props = { children?: ReactNode; backgroundTemplate?: ReactNode; contentPlacement?: "left" | "right" | "bottom"; contentAlignment?: "start" | "center" | "end"; headerWidth?: string | number; contentWidth?: string | number; gap?: string | number; headerAlignment?: string; preamble?: string; headline?: string; subheadline?: string; mainText?: string; mainTextTemplate?: ReactNode; ctaButtonIcon?: string; ctaButtonText?: string; ctaButtonTemplate?: ReactNode; image?: string; imageWidth?: number | string; imageHeight?: number | string; fullWidthBackground?: boolean; className?: string; headerTone?: string; contentTone?: string; onCtaClick?: () => void; }; export const defaultProps: Pick< Props, "fullWidthBackground" | "contentPlacement" | "contentAlignment" | "headerWidth" | "contentWidth" > = { headerWidth: "50%", fullWidthBackground: true, contentPlacement: "bottom", contentAlignment: "center", contentWidth: "$maxWidth-content", }; export const HeroSection = forwardRef( ( { children, backgroundTemplate, headerAlignment, contentPlacement = defaultProps.contentPlacement, contentAlignment = defaultProps.contentAlignment, headerWidth = defaultProps.headerWidth, contentWidth, gap, preamble, headline, subheadline, mainText, mainTextTemplate, ctaButtonIcon, ctaButtonText, ctaButtonTemplate, image, imageWidth, imageHeight, fullWidthBackground = defaultProps.fullWidthBackground, className, headerTone, contentTone, onCtaClick, }: Props, ref: React.Ref<HTMLDivElement>, ) => { // Validate contentPlacement and default to "bottom" if invalid const validContentPlacements = ["left", "right", "bottom"] as const; const effectiveContentPlacement = validContentPlacements.includes(contentPlacement as any) ? contentPlacement : "bottom"; // Default headerAlignment to "center" if not provided const effectiveHeaderAlignment = headerAlignment || "center"; const isHorizontal = effectiveContentPlacement === "left" || effectiveContentPlacement === "right"; // Optional tone colors const { activeThemeTone } = useTheme(); const effectiveTone = (prop?: string) => { switch (prop) { case "light": case "dark": return prop; case "reverse": return activeThemeTone === "light" ? "dark" : "light"; default: return activeThemeTone; } }; let effectiveHeaderTone = effectiveTone(headerTone); let effectiveContentTone = effectiveTone(contentTone); // Only render CTA button if there's content for it const ctaButton = (ctaButtonTemplate || ctaButtonText) && (ctaButtonTemplate || ( <Button data-part-id={PART_CTA_BUTTON} className={classnames(styles.ctaButton)} icon={ctaButtonIcon && <Icon name={ctaButtonIcon} aria-hidden />} onClick={onCtaClick} > {ctaButtonText} </Button> )); // Header section (preamble to CTA button) const headerSection = ( <div data-part-id={PART_HEADER} className={classnames(styles.header)} style={{ width: isHorizontal ? headerWidth : undefined, flexShrink: isHorizontal ? 0 : undefined, }} > <Theme tone={effectiveHeaderTone}> <div data-part-id={PART_HEADING_SECTION} className={classnames(styles.headingSection, { [styles.start]: effectiveHeaderAlignment === "start", [styles.center]: effectiveHeaderAlignment === "center", [styles.end]: effectiveHeaderAlignment === "end", })} > {preamble && ( <div data-part-id={PART_PREAMBLE} className={styles.preamble}> {preamble} </div> )} {headline && ( <div data-part-id={PART_HEADLINE} className={styles.headline}> {headline} </div> )} {subheadline && ( <div data-part-id={PART_SUBHEADLINE} className={styles.subheadline}> {subheadline} </div> )} {mainTextTemplate && ( <div data-part-id={PART_MAIN_TEXT} className={styles.textWrapper}> {mainTextTemplate} </div> )} {!mainTextTemplate && mainText && ( <div data-part-id={PART_MAIN_TEXT} className={styles.mainText}> {mainText} </div> )} {ctaButton && <div className={styles.ctaButtonWrapper}>{ctaButton}</div>} </div> </Theme> </div> ); // Content section (image + children) const contentSection = ( <div data-part-id={PART_CONTENT} className={classnames(styles.content, { [styles.contentStart]: contentAlignment === "start", [styles.contentCenter]: contentAlignment === "center", [styles.contentEnd]: contentAlignment === "end", })} > <Theme tone={effectiveContentTone}> <> {image && ( <img data-part-id={PART_IMAGE} className={styles.image} src={image} style={{ width: imageWidth, height: imageHeight }} aria-hidden /> )} {children} </> </Theme> </div> ); const heroContent = ( <div ref={ref} data-part-id={PART_BACKGROUND} className={classnames(styles.heroWrapper, className)} > {backgroundTemplate && ( <div className={styles.backgroundTemplate}>{backgroundTemplate}</div> )} <div className={classnames(styles.heroContent, { [styles.horizontal]: isHorizontal, [styles.vertical]: !isHorizontal, })} style={{ gap, width: contentWidth }} > {effectiveContentPlacement === "left" && contentSection} {headerSection} {(effectiveContentPlacement === "right" || effectiveContentPlacement === "bottom") && contentSection} </div> </div> ); return fullWidthBackground ? <Breakout>{heroContent}</Breakout> : heroContent; }, ); ``` -------------------------------------------------------------------------------- /xmlui/tests/parsers/scripting/parser-expressions.test.ts: -------------------------------------------------------------------------------- ```typescript import { describe, expect, it } from "vitest"; import { Parser } from "../../../src/parsers/scripting/Parser"; import { FunctionInvocationExpression, MemberAccessExpression, PostfixOpExpression, PrefixOpExpression, SequenceExpression, SpreadExpression, T_ARRAY_LITERAL, T_BINARY_EXPRESSION, T_CALCULATED_MEMBER_ACCESS_EXPRESSION, T_CONDITIONAL_EXPRESSION, T_FUNCTION_INVOCATION_EXPRESSION, T_IDENTIFIER, T_LITERAL, T_MEMBER_ACCESS_EXPRESSION, T_POSTFIX_OP_EXPRESSION, T_PREFIX_OP_EXPRESSION, T_SEQUENCE_EXPRESSION, T_SPREAD_EXPRESSION, T_UNARY_EXPRESSION, } from "../../../src/components-core/script-runner/ScriptingSourceTree"; describe("Parser - miscellaneous expressions", () => { const sequenceCases = [ { src: "a, b, a+b", len: 3, idx: 0, exp: T_IDENTIFIER }, { src: "a, b, a+b", len: 3, idx: 1, exp: T_IDENTIFIER }, { src: "a, b, a+b", len: 3, idx: 2, exp: T_BINARY_EXPRESSION }, { src: "a(b), b.a, a[b], !a", len: 4, idx: 0, exp: T_FUNCTION_INVOCATION_EXPRESSION }, { src: "a(b), b.a, a[b], !a", len: 4, idx: 1, exp: T_MEMBER_ACCESS_EXPRESSION }, { src: "a(b), b.a, a[b], !a", len: 4, idx: 2, exp: T_CALCULATED_MEMBER_ACCESS_EXPRESSION }, { src: "a(b), b.a, a[b], !a", len: 4, idx: 3, exp: T_UNARY_EXPRESSION }, { src: 'a, 12.3, "Hello"', len: 3, idx: 1, exp: T_LITERAL }, { src: 'a, 12.3, "Hello"', len: 3, idx: 2, exp: T_LITERAL }, ]; sequenceCases.forEach((c) => { it(`Sequence expression: ${c.src}`, () => { // --- Arrange const wParser = new Parser(c.src); // --- Act const expr = wParser.parseExpr(); // --- Assert expect(expr).not.equal(null); if (!expr) return; expect(expr.type).equal(T_SEQUENCE_EXPRESSION); const sequence = expr as SequenceExpression; expect(sequence.exprs.length).equal(c.len); expect(sequence.exprs[c.idx].type).equal(c.exp); }); }); const invocationCases = [ { src: "func()", len: 0, idx: -1, exp: null }, { src: "func(a, b)", len: 2, idx: 0, exp: T_IDENTIFIER }, { src: "func(a, b)", len: 2, idx: 1, exp: T_IDENTIFIER }, { src: "func(123, a+b, a[b])", len: 3, idx: 0, exp: T_LITERAL }, { src: "func(123, a+b, a[b])", len: 3, idx: 1, exp: T_BINARY_EXPRESSION }, { src: "func(123, a+b, a[b])", len: 3, idx: 2, exp: T_CALCULATED_MEMBER_ACCESS_EXPRESSION }, ]; invocationCases.forEach((c) => { it(`FunctionInvocation: ${c.src}`, () => { // --- Arrange const wParser = new Parser(c.src); // --- Act const expr = wParser.parseExpr(); // --- Assert expect(expr).not.equal(null); if (!expr) return; expect(expr.type).equal(T_FUNCTION_INVOCATION_EXPRESSION); const invocation = expr as FunctionInvocationExpression; expect(invocation.obj.type).equal(T_IDENTIFIER); expect(invocation.arguments.length).equal(c.len); if (c.len > 0) { // eslint-disable-next-line jest/no-conditional-expect expect(invocation.arguments[c.idx].type).equal(c.exp); } }); }); const objectCases = [ { src: "func()", exp: T_IDENTIFIER }, { src: "(+a)()", exp: T_UNARY_EXPRESSION }, { src: "(a+b)()", exp: T_BINARY_EXPRESSION }, { src: "(a ? b : c)()", exp: T_CONDITIONAL_EXPRESSION }, { src: "(123)()", exp: T_LITERAL }, { src: '("Hello")()', exp: T_LITERAL }, { src: "(func(a, b))()", exp: T_FUNCTION_INVOCATION_EXPRESSION }, { src: "(a.b)()", exp: T_MEMBER_ACCESS_EXPRESSION }, { src: "(a[b])()", exp: T_CALCULATED_MEMBER_ACCESS_EXPRESSION }, ]; objectCases.forEach((c) => { it(`FunctionInvocation object: ${c.src}`, () => { // --- Arrange const wParser = new Parser(c.src); // --- Act const expr = wParser.parseExpr(); // --- Assert expect(expr).not.equal(null); if (!expr) return; expect(expr.type).equal(T_FUNCTION_INVOCATION_EXPRESSION); const invocation = expr as FunctionInvocationExpression; expect(invocation.obj.type).equal(c.exp); }); }); const memberAccessCases = [ { src: "a.b", exp: T_IDENTIFIER }, { src: "(+a).b", exp: T_UNARY_EXPRESSION }, { src: "(a+b).b", exp: T_BINARY_EXPRESSION }, { src: "(a ? b : c).b", exp: T_CONDITIONAL_EXPRESSION }, { src: "(123).b", exp: T_LITERAL }, { src: '("Hello").b', exp: T_LITERAL }, { src: "(func(a, b)).b", exp: T_FUNCTION_INVOCATION_EXPRESSION }, { src: "(a.b).b", exp: T_MEMBER_ACCESS_EXPRESSION }, { src: "(a[b]).b", exp: T_CALCULATED_MEMBER_ACCESS_EXPRESSION }, ]; memberAccessCases.forEach((c) => { it(`MemberAccess: ${c.src}`, () => { // --- Arrange const wParser = new Parser(c.src); // --- Act const expr = wParser.parseExpr(); // --- Assert expect(expr).not.equal(null); if (!expr) return; expect(expr.type).equal(T_MEMBER_ACCESS_EXPRESSION); const memberAcc = expr as MemberAccessExpression; expect(memberAcc.member).eq("b"); expect(memberAcc.obj.type).equal(c.exp); }); }); const spreadCases = [ { src: "...[1, 2, 3]", exp: T_ARRAY_LITERAL }, { src: "...apple", exp: T_IDENTIFIER }, ]; spreadCases.forEach((c) => { it(`Spread: ${c.src}`, () => { // --- Arrange const wParser = new Parser(c.src); // --- Act const expr = wParser.parseExpr(); // --- Assert expect(expr).not.equal(null); if (!expr) return; expect(expr.type).equal(T_SPREAD_EXPRESSION); const spread = expr as SpreadExpression; expect(spread.expr.type).equal(c.exp); }); }); const prefixCases = [ { src: "++i", op: "++", exp: T_IDENTIFIER }, { src: "++j[2]", op: "++", exp: T_CALCULATED_MEMBER_ACCESS_EXPRESSION }, { src: "--i", op: "--", exp: T_IDENTIFIER }, { src: "--j[2]", op: "--", exp: T_CALCULATED_MEMBER_ACCESS_EXPRESSION }, ]; prefixCases.forEach((c) => { it(`Prefix: ${c.src}`, () => { // --- Arrange const wParser = new Parser(c.src); // --- Act const expr = wParser.parseExpr(); // --- Assert expect(expr).not.equal(null); if (!expr) return; expect(expr.type).equal(T_PREFIX_OP_EXPRESSION); const prefixExpr = expr as PrefixOpExpression; expect(prefixExpr.expr.type).equal(c.exp); expect(prefixExpr.op).equal(c.op); }); }); const postfixCases = [ { src: "i++", op: "++", exp: T_IDENTIFIER }, { src: "j[2]++", op: "++", exp: T_CALCULATED_MEMBER_ACCESS_EXPRESSION }, { src: "i--", op: "--", exp: T_IDENTIFIER }, { src: "j[2]--", op: "--", exp: T_CALCULATED_MEMBER_ACCESS_EXPRESSION }, ]; postfixCases.forEach((c) => { it(`Postfix: ${c.src}`, () => { // --- Arrange const wParser = new Parser(c.src); // --- Act const expr = wParser.parseExpr(); // --- Assert expect(expr).not.equal(null); if (!expr) return; expect(expr.type).equal(T_POSTFIX_OP_EXPRESSION); const postfixExpr = expr as PostfixOpExpression; expect(postfixExpr.expr.type).equal(c.exp); expect(postfixExpr.op).equal(c.op); }); }); }); ``` -------------------------------------------------------------------------------- /xmlui/src/components/AutoComplete/AutoComplete.tsx: -------------------------------------------------------------------------------- ```typescript import styles from "./AutoComplete.module.scss"; import { createComponentRenderer } from "../../components-core/renderers"; import { parseScssVar } from "../../components-core/theming/themeVars"; import { MemoizedItem } from "../container-helpers"; import { dPlaceholder, dInitialValue, dMaxLength, dAutoFocus, dRequired, dReadonly, dEnabled, dValidationStatus, dComponent, dDidChange, dGotFocus, dLostFocus, dMulti, createMetadata, d, } from "../metadata-helpers"; import { AutoComplete, defaultProps } from "./AutoCompleteNative"; const COMP = "AutoComplete"; export const AutoCompleteMd = createMetadata({ status: "experimental", description: "`AutoComplete` is a searchable dropdown input that allows users to type and " + "filter through options, with support for single or multiple selections. Unlike " + "a basic [`Select`](/components/Select), it provides type-ahead functionality " + "and can allow users to create new options.", props: { placeholder: dPlaceholder(), initialValue: dInitialValue(), maxLength: dMaxLength(), autoFocus: { ...dAutoFocus(), defaultValue: defaultProps.autoFocus, }, required: { ...dRequired(), defaultValue: defaultProps.required, }, readOnly: { ...dReadonly(), defaultValue: defaultProps.readOnly, }, enabled: { ...dEnabled(), defaultValue: defaultProps.enabled, }, initiallyOpen: d( `This property determines whether the dropdown list is open when the component is first rendered.`, null, "boolean", defaultProps.initiallyOpen, ), creatable: d( `This property allows the user to create new items that are not present in the list of options.`, null, "boolean", defaultProps.creatable, ), validationStatus: { ...dValidationStatus(), defaultValue: defaultProps.validationStatus, }, dropdownHeight: d("This property sets the height of the dropdown list."), multi: { ...dMulti(), defaultValue: defaultProps.multi, }, optionTemplate: dComponent( `This property enables the customization of list items. To access the attributes of ` + `a list item use the \`$item\` context variable.`, ), emptyListTemplate: dComponent( "This property defines the template to display when the list of options is empty.", ), }, events: { gotFocus: dGotFocus(COMP), lostFocus: dLostFocus(COMP), didChange: dDidChange(COMP), itemCreated: { description: "This event is triggered when a new item is created by the user " + "(if `creatable` is enabled).", }, }, apis: { focus: { description: `This method focuses the ${COMP} component.`, signature: "focus()", }, value: { description: "This API allows you to get or set the value of the component. If no value is set, " + "it will retrieve `undefined`.", signature: "get value(): any", }, setValue: { description: "This API allows you to set the value of the component. If the value is not valid, " + "the component will not update its internal state.", signature: "setValue(value: any)", parameters: { value: "The value to set.", }, }, }, contextVars: { $item: d( "This context value represents an item when you define an option item template. " + "Use `$item.value` and `$item.label` to refer to the value and label of the " + "particular option.", ), }, themeVars: parseScssVar(styles.themeVars), defaultThemeVars: { [`backgroundColor-menu-${COMP}`]: "$color-surface-raised", [`boxShadow-menu-${COMP}`]: "$boxShadow-md", [`borderRadius-menu-${COMP}`]: "$borderRadius", [`borderWidth-menu-${COMP}`]: "1px", [`borderColor-menu-${COMP}`]: "$borderColor", [`backgroundColor-${COMP}-badge`]: "$color-primary-500", [`fontSize-${COMP}-badge`]: "$fontSize-sm", [`paddingHorizontal-${COMP}-badge`]: "$space-2_5", [`paddingVertical-${COMP}-badge`]: "$space-0_5", [`borderRadius-${COMP}-badge`]: "$borderRadius", [`paddingHorizontal-item-${COMP}`]: "$space-2", [`paddingVertical-item-${COMP}`]: "$space-2", [`paddingHorizontal-${COMP}`]: "$space-2", [`paddingVertical-${COMP}`]: "$space-2", [`opacity-text-item-${COMP}--disabled`]: "0.5", [`opacity-${COMP}--disabled`]: "0.5", [`backgroundColor-${COMP}-badge--hover`]: "$color-primary-400", [`backgroundColor-${COMP}-badge--active`]: "$color-primary-500", [`textColor-item-${COMP}--disabled`]: "$color-surface-200", [`textColor-${COMP}-badge`]: "$const-color-surface-50", [`backgroundColor-item-${COMP}`]: "$backgroundColor-dropdown-item", [`backgroundColor-item-${COMP}--hover`]: "$backgroundColor-dropdown-item--hover", [`backgroundColor-item-${COMP}--active`]: "$backgroundColor-dropdown-item--active", // Default borderColor-Input--disabled is too light so the disabled component is barely visible [`borderColor-${COMP}--disabled`]: "initial", }, }); export const autoCompleteComponentRenderer = createComponentRenderer( COMP, AutoCompleteMd, ({ node, state, updateState, extractValue, renderChild, lookupEventHandler, registerComponentApi, className, }) => { return ( <AutoComplete multi={extractValue.asOptionalBoolean(node.props.multi)} className={className} updateState={updateState} initialValue={extractValue(node.props.initialValue)} value={state?.value} creatable={extractValue.asOptionalBoolean(node.props.creatable)} autoFocus={extractValue.asOptionalBoolean(node.props.autoFocus)} enabled={extractValue.asOptionalBoolean(node.props.enabled)} placeholder={extractValue.asOptionalString(node.props.placeholder)} validationStatus={extractValue(node.props.validationStatus)} onDidChange={lookupEventHandler("didChange")} onFocus={lookupEventHandler("gotFocus")} onBlur={lookupEventHandler("lostFocus")} onItemCreated={lookupEventHandler("itemCreated")} registerComponentApi={registerComponentApi} emptyListTemplate={renderChild(node.props.emptyListTemplate)} dropdownHeight={extractValue(node.props.dropdownHeight)} readOnly={extractValue.asOptionalBoolean(node.props.readOnly)} initiallyOpen={extractValue.asOptionalBoolean(node.props.initiallyOpen)} optionRenderer={ node.props.optionTemplate ? (item, val, inTrigger) => { return ( <MemoizedItem node={node.props.optionTemplate} item={item} context={{ $selectedValue: val, $inTrigger: inTrigger, }} renderChild={renderChild} /> ); } : undefined } > {renderChild(node.children)} </AutoComplete> ); }, ); ``` -------------------------------------------------------------------------------- /xmlui/src/components/Tooltip/TooltipNative.tsx: -------------------------------------------------------------------------------- ```typescript import { type ReactNode, type ForwardedRef, forwardRef } from "react"; import * as RadixTooltip from "@radix-ui/react-tooltip"; import { isPlainObject } from "lodash-es"; import styles from "./Tooltip.module.scss"; import { useTheme } from "../../components-core/theming/ThemeContext"; import { Markdown } from "../Markdown/Markdown"; type TooltipOptions = { /** * The duration from when the mouse enters a tooltip trigger until the tooltip opens */ delayDuration?: number; /** * How much time a user has to enter another trigger without incurring a delay again */ skipDelayDuration?: number; /** * The open state of the tooltip when it is initially rendered */ defaultOpen?: boolean; /** * Whether to show the arrow pointing to the trigger element */ showArrow?: boolean; /** * The preferred side of the trigger to render against when open */ side?: "top" | "right" | "bottom" | "left"; /** * The preferred alignment against the trigger */ align?: "start" | "center" | "end"; /** * The distance in pixels from the trigger */ sideOffset?: number; /** * An offset in pixels from the "start" or "end" alignment options */ alignOffset?: number; /** * When true, overrides the side and align preferences to prevent collisions with boundary edges */ avoidCollisions?: boolean; }; type TooltipProps = TooltipOptions & { /** * The open state of the tooltip externally controlled */ open?: boolean; /** * The text content to display in the tooltip */ text: string; /** * The markdown content to display in the tooltip */ markdown?: string; /** * The template for the tooltip content */ tooltipTemplate?: ReactNode; /** * The content that will trigger the tooltip (used when triggerRef is not provided) */ children?: ReactNode; }; export type { TooltipProps, TooltipOptions }; export const defaultProps: TooltipOptions = { delayDuration: 700, skipDelayDuration: 300, defaultOpen: false, showArrow: false, side: "top", align: "center", sideOffset: 4, alignOffset: 0, avoidCollisions: true, }; export const Tooltip = forwardRef(function Tooltip({ text, markdown, tooltipTemplate, delayDuration = defaultProps.delayDuration, skipDelayDuration = defaultProps.skipDelayDuration, defaultOpen = defaultProps.defaultOpen, showArrow = defaultProps.showArrow, side = defaultProps.side, align = defaultProps.align, sideOffset = defaultProps.sideOffset, alignOffset = defaultProps.alignOffset, avoidCollisions = defaultProps.avoidCollisions, children, open, }: TooltipProps, ref: ForwardedRef<HTMLDivElement>) { const { root } = useTheme(); const showTooltip = !!(text || markdown || tooltipTemplate); return ( <RadixTooltip.Provider delayDuration={delayDuration} skipDelayDuration={skipDelayDuration}> <RadixTooltip.Root defaultOpen={defaultOpen} open={open}> <RadixTooltip.Trigger asChild>{children}</RadixTooltip.Trigger> <RadixTooltip.Portal container={root}> {showTooltip && ( <RadixTooltip.Content ref={ref} className={styles.content} side={side} align={align} sideOffset={sideOffset} alignOffset={alignOffset} avoidCollisions={avoidCollisions} data-tooltip-container > {tooltipTemplate ? ( tooltipTemplate ) : markdown ? ( <Markdown>{markdown}</Markdown> ) : ( text )} {showArrow && <RadixTooltip.Arrow className={styles.arrow} />} </RadixTooltip.Content> )} </RadixTooltip.Portal> </RadixTooltip.Root> </RadixTooltip.Provider> ); }); /** * Parses tooltip options from any input and returns an object containing only the option properties * of Tooltip (excludes non-option properties like text, triggerRef, and children) */ export function parseTooltipOptions(input: any): Partial<TooltipOptions> { // If input is a plain object, return it as TooltipOptions if (isPlainObject(input)) { return input as Partial<TooltipOptions>; } // If input is a string, split by semicolon if (typeof input === "string") { const values = input .split(";") .map((value) => value.trim()) .filter((value) => value.length > 0); const options: Partial<TooltipOptions> = {}; for (const value of values) { // Check if it's a name-value pair (contains colon) if (value.includes(":")) { const [name, val] = value.split(":").map((part) => part.trim()); if (name && val) { // Parse the value based on the property type const parsedValue = parseOptionValue(name, val); if (parsedValue !== undefined) { (options as any)[name] = parsedValue; } } } else { // Single value case - check various option types const sideValues = ["top", "right", "bottom", "left"]; const alignValues = ["start", "center", "end"]; const booleanOptions = ["defaultOpen", "showArrow", "avoidCollisions"]; if (sideValues.includes(value)) { options.side = value as "top" | "right" | "bottom" | "left"; } else if (alignValues.includes(value)) { options.align = value as "start" | "center" | "end"; } else if (booleanOptions.includes(value)) { // Boolean option with true value (options as any)[value] = true; } else if (value.startsWith("!") && value.length > 1) { // Boolean option with false value (negated with !) const optionName = value.substring(1); if (booleanOptions.includes(optionName)) { (options as any)[optionName] = false; } } // If it doesn't match any known values, ignore it } } return options; } // For any other type, return empty object return {}; } /** * Helper function to parse option values based on the property name */ function parseOptionValue(name: string, value: string): any { switch (name) { case "delayDuration": case "skipDelayDuration": case "sideOffset": case "alignOffset": // Parse as number const num = parseInt(value, 10); return isNaN(num) ? undefined : num; case "defaultOpen": case "showArrow": case "avoidCollisions": // Parse as boolean const lowerVal = value.toLowerCase(); if (lowerVal === "true" || lowerVal === "1" || lowerVal === "yes") return true; if (lowerVal === "false" || lowerVal === "0" || lowerVal === "no") return false; return undefined; case "side": // Validate side values if (["top", "right", "bottom", "left"].includes(value)) { return value as "top" | "right" | "bottom" | "left"; } return undefined; case "align": // Validate align values if (["start", "center", "end"].includes(value)) { return value as "start" | "center" | "end"; } return undefined; default: // Unknown property, return undefined return undefined; } } ``` -------------------------------------------------------------------------------- /xmlui/src/components/NavLink/NavLink.module.scss: -------------------------------------------------------------------------------- ```scss @use "../../components-core/theming/themes" as t; // --- This code snippet is required to collect the theme variables used in this module $themeVars: (); @function createThemeVar($componentVariable) { $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global; @return t.getThemeVar($themeVars, $componentVariable); } $component: "NavLink"; $themeVars: t.composePaddingVars($themeVars, $component); $themeVars: t.composeBorderVars($themeVars, $component); $backgroundColor-NavLink: createThemeVar("backgroundColor-#{$component}"); $backgroundColor-NavLink--hover: createThemeVar("backgroundColor-#{$component}--hover"); $backgroundColor-NavLink--hover--active: createThemeVar("backgroundColor-#{$component}--hover--active"); $backgroundColor-NavLink--active: createThemeVar("backgroundColor-#{$component}--active"); $backgroundColor-NavLink--pressed: createThemeVar("backgroundColor-#{$component}--pressed"); $backgroundColor-NavLink--pressed--active: createThemeVar("backgroundColor-#{$component}--pressed--active"); $fontSize-NavLink: createThemeVar("fontSize-#{$component}"); $wordWrap-NavLink: createThemeVar("wordWrap-#{$component}"); $textColor-NavLink: createThemeVar("textColor-#{$component}"); $textColor-NavLink--hover: createThemeVar("textColor-#{$component}--hover"); $textColor-NavLink--active: createThemeVar("textColor-#{$component}--active"); $textColor-NavLink--hover--active: createThemeVar("textColor-#{$component}--hover--active"); $textColor-NavLink--pressed: createThemeVar("textColor-#{$component}--pressed"); $textColor-NavLink--pressed--active: createThemeVar("textColor-#{$component}--pressed--active"); $color-icon-NavLink: createThemeVar("color-icon-#{$component}"); $fontFamily-NavLink: createThemeVar("fontFamily-#{$component}"); $fontWeight-NavLink: createThemeVar("fontWeight-#{$component}"); $fontWeight-NavLink--pressed: createThemeVar("fontWeight-#{$component}--pressed"); $fontWeight-NavLink--active: createThemeVar("fontWeight-#{$component}--active"); $borderRadius-indicator-NavLink: createThemeVar("borderRadius-indicator-#{$component}"); $thickness-indicator-NavLink: createThemeVar("thickness-indicator-#{$component}"); $color-indicator-NavLink: createThemeVar("color-indicator-#{$component}"); $color-indicator-NavLink--hover: createThemeVar("color-indicator-#{$component}--hover"); $color-indicator-NavLink--active: createThemeVar("color-indicator-#{$component}--active"); $color-indicator-NavLink--pressed: createThemeVar("color-indicator-#{$component}--pressed"); $transition: color 0.1s, background-color 0.1s; @layer components { .content { --nav-link-level: 0; &.base { &::before{ content: ""; width: calc(var(--nav-link-level) * 1em); display: block; flex-shrink: 0; } @include t.borderVars($themeVars, $component); @include t.paddingVars($themeVars, $component); outline: inherit; text-decoration: none; cursor: pointer; display: flex; align-items: center; font-size: $fontSize-NavLink; font-weight: $fontWeight-NavLink; font-family: $fontFamily-NavLink; word-wrap: $wordWrap-NavLink; position: relative; transition: $transition; color: $textColor-NavLink; background-color: $backgroundColor-NavLink; flex-shrink: 0; white-space: nowrap; .icon { color: $color-icon-NavLink; } * { color: $textColor-NavLink; } &.includeHoverIndicator { &:after { transition: $transition; position: absolute; left: 0.1em; right: 0.1em; bottom: 0; height: $thickness-indicator-NavLink; border-radius: $borderRadius-indicator-NavLink; content: ""; } } &:hover, &:focus-visible { color: $textColor-NavLink--hover; background-color: $backgroundColor-NavLink--hover; * { color: $textColor-NavLink--hover; } &:after { background-color: $color-indicator-NavLink--hover; } } &:focus-visible{ outline-width: createThemeVar('outlineWidth-#{$component}--focus'); outline-color: createThemeVar('outlineColor-#{$component}--focus'); outline-style: createThemeVar('outlineStyle-#{$component}--focus'); outline-offset: createThemeVar('outlineOffset-#{$component}--focus'); } &[role='menuitem']{ //e.g. inside a navGroup, for hover it focuses the item, so it's always visible, that's why we have this selector here outline: none; &:not(:hover):focus-visible{ outline-width: createThemeVar('outlineWidth-#{$component}--focus'); outline-color: createThemeVar('outlineColor-#{$component}--focus'); outline-style: createThemeVar('outlineStyle-#{$component}--focus'); outline-offset: createThemeVar('outlineOffset-#{$component}--focus'); } } &:active.displayActive { color: $textColor-NavLink--pressed; background-color: $backgroundColor-NavLink--pressed; font-weight: $fontWeight-NavLink--pressed; * { font-weight: $fontWeight-NavLink--pressed; color: $textColor-NavLink--pressed; } &:after { background-color: $color-indicator-NavLink--pressed; } } &.navItemActive { color: $textColor-NavLink--active; background-color: $backgroundColor-NavLink--active; font-weight: $fontWeight-NavLink--active; * { font-weight: $fontWeight-NavLink--active; color: $textColor-NavLink--active; } & > * { z-index: 1; } &:hover { color: $textColor-NavLink--hover--active; background-color: $backgroundColor-NavLink--hover--active; * { color: $textColor-NavLink--hover--active; } } &:after { z-index: 0; background-color: $color-indicator-NavLink--active; } &:active { color: $textColor-NavLink--pressed--active; background-color: $backgroundColor-NavLink--pressed--active; font-weight: $fontWeight-NavLink--pressed; * { font-weight: $fontWeight-NavLink--pressed; color: $textColor-NavLink--pressed--active; } &:after { background-color: $color-indicator-NavLink--pressed; } } } &.disabled { cursor: not-allowed; color: t.$textColor--disabled; } &.vertical { &:after { top: 0; bottom: 0; left: 0; right: auto; width: $thickness-indicator-NavLink; height: auto; border-radius: $borderRadius-indicator-NavLink; } } &.indented { padding-left: 2.4em; flex-shrink: 0; } } } .innerContent{ display: flex; align-items: center; gap: t.$space-2; flex: 1; white-space: break-spaces; overflow-wrap: anywhere; } } // --- We export the theme variables to add them to the component renderer :export { themeVars: t.json-stringify($themeVars); } ``` -------------------------------------------------------------------------------- /blog/scripts/get-releases.js: -------------------------------------------------------------------------------- ```javascript #!/usr/bin/env node const fs = require("fs").promises; const path = require("path"); const { exit } = require("process"); const { sortByVersion, XMLUI_STANDALONE_PATTERN } = require("./utils.js"); const MD_HEADING_PATTERN = /^#{1,6} \S/; const MD_LIST_COMMIT_SHA_PATTERN = /^-\s*[a-f0-9]{6,40}:\s*/; const DEF_MAX_RELEASES_STR = "10"; if (require.main === module) { main(); } function processEnvVars() { if (!process.env.GITHUB_TOKEN) { console.error( `No GitHub token provided. Skipping updating releases. If you want to update the info about the releases, set the GITHUB_TOKEN environment variable to your Personal Access Token`, ); exit(0); } const githubToken = process.env.GITHUB_TOKEN; const owner = process.env.GITHUB_REPOSITORY?.split("/")[0] || "xmlui-org"; const repo = process.env.GITHUB_REPOSITORY?.split("/")[1] || "xmlui"; const maxReleases = process.env.DOCS_XMLUI_MAX_RELEASES_LENGTH; return { owner, repo, githubToken, maxReleases, }; } function processOptions(args) { handleHelpOption(args); const envVars = processEnvVars(); const writeToStdout = args.includes("--stdout"); const outputFile = getOptionValue(args, "--output"); const maxReleasesStr = getOptionValue(args, "--maxReleases") ?? envVars.maxReleases ?? DEF_MAX_RELEASES_STR; const maxReleasesParse = parseInt(maxReleasesStr); const maxReleases = Number.isNaN(maxReleasesParse) ? parseInt(DEF_MAX_RELEASES_STR) : maxReleasesParse; return { ...envVars, outputFile, writeToStdout, maxReleases, }; } async function getXmluiReleases(options) { try { const { Octokit } = await import("@octokit/rest"); const octokit = new Octokit({ auth: options.githubToken, }); console.error("Fetching releases from GitHub API..."); const { data: releases } = await octokit.rest.repos.listReleases({ owner: options.owner, repo: options.repo, }); const xmluiReleases = releases.filter((release) => release.tag_name.startsWith("xmlui@")); xmluiReleases.sort(sortByVersion); const releasesToProcess = xmluiReleases.slice(0, options.maxReleases); const availableVersions = []; for (const release of releasesToProcess) { const xmluiStandaloneAsset = release.assets.find((asset) => XMLUI_STANDALONE_PATTERN.test(asset.name), ); const changes = release.body ? parseBodyIntoChanges(release.body) : []; if (xmluiStandaloneAsset) { availableVersions.push({ tag_name: release.tag_name, published_at: release.published_at, changes: changes, assets: [ { name: xmluiStandaloneAsset.name, browser_download_url: xmluiStandaloneAsset.browser_download_url, }, ], }); } } return availableVersions; } catch (error) { console.error("Error fetching xmlui releases:", error.message); if (error.status === 401) { console.error("Authentication failed. Please check your GitHub token"); } else if (error.status === 403) { console.error("Rate limit exceeded or insufficient permissions."); } return null; } } async function main() { const args = process.argv.slice(2); const options = processOptions(args); const outputFile = options.outputFile; try { const releases = await getXmluiReleases(options); console.error(`Got ${releases.length} xmlui releases`); if (releases === null) { console.error("Failed to fetch xmlui releases. Exiting with code 1."); process.exit(1); } if (outputFile === null || options.writeToStdout) { console.log(JSON.stringify(releases, null, 2)); } else { // Ensure output directory exists const outputDir = path.dirname(outputFile); await fs.mkdir(outputDir, { recursive: true }); // Write to file await fs.writeFile(outputFile, JSON.stringify(releases, null, 2), "utf8"); console.error(`Successfully updated ${outputFile}`); } } catch (error) { console.error("Error writing release info:", error); process.exit(1); } } function getOptionValue(args, optionName) { const optionIndex = args.indexOf(optionName); if (optionIndex === -1) return null; const value = args[optionIndex + 1]; if (value === undefined) return null; if (value.startsWith("--")) return null; return value; } function handleHelpOption(args) { if (args.includes("--help") || args.includes("-h")) { const helpMessage = ` Fetches xmlui release information from GitHub and outputs it in JSON format. This script relies on the GITHUB_TOKEN environment variable for authentication when fetching data directly from the GitHub API. Usage: ./get-releases.js [options] Options: --output <file> Specify the path to the output JSON file. If this option is not provided the output will be directed to standard output. --stdout Force the JSON output to standard output. --maxReleases Specify the maximum number of xmlui releases to include. Defaults to ${DEF_MAX_RELEASES_STR}. --help, -h Display this help message and exit. Environment Variables: GITHUB_TOKEN Required for fetching data from the GitHub API. A GitHub Personal Access Token with 'repo' scope (or at least permissions to read repository releases). The script will fail if this is not set and API access is attempted. GITHUB_REPOSITORY Optional. The 'owner/repo' string. Defaults to "xmlui-org/xmlui". DOCS_XMLUI_MAX_RELEASES_LENGTH Optional. The maximum number of xmlui releases to include. Can be overridden by the --maxReleases command line option. Defaults to ${DEF_MAX_RELEASES_STR}. `; console.log(helpMessage.trimStart().trimEnd()); process.exit(0); } } /** * Parse markdown body into individual changes with commit SHA and description * @param {string} body * @returns {Array<{description: string, commit_sha: string}>} */ function parseBodyIntoChanges(body) { const changes = []; const lines = body.split("\n"); for (const line of lines) { // Skip markdown headings if (MD_HEADING_PATTERN.test(line)) { continue; } // Process list items that may contain commit SHA if (line.startsWith("- ")) { const match = line.match(MD_LIST_COMMIT_SHA_PATTERN); if (match) { // Extract commit SHA and description const commitSha = match[0].match(/[a-f0-9]{6,40}/)[0]; const description = line.replace(MD_LIST_COMMIT_SHA_PATTERN, "").trim(); changes.push({ description: description, commit_sha: commitSha, }); } else { // List item without commit SHA const description = line.substring(2).trim(); // Remove "- " prefix if (description) { changes.push({ description: description, commit_sha: null, // No commit SHA available }); } } } } return changes; } ``` -------------------------------------------------------------------------------- /docs/scripts/get-releases.js: -------------------------------------------------------------------------------- ```javascript #!/usr/bin/env node const fs = require("fs").promises; const path = require("path"); const { exit } = require("process"); const { sortByVersion, XMLUI_STANDALONE_PATTERN } = require("./utils.js"); const MD_HEADING_PATTERN = /^#{1,6} \S/; const MD_LIST_COMMIT_SHA_PATTERN = /^-\s*[a-f0-9]{6,40}:\s*/; const DEF_MAX_RELEASES_STR = "10"; if (require.main === module) { main(); } function processEnvVars() { if (!process.env.GITHUB_TOKEN) { console.error( `No GitHub token provided. Skipping updating releases. If you want to update the info about the releases, set the GITHUB_TOKEN environment variable to your Personal Access Token`, ); exit(0); } const githubToken = process.env.GITHUB_TOKEN; const owner = process.env.GITHUB_REPOSITORY?.split("/")[0] || "xmlui-org"; const repo = process.env.GITHUB_REPOSITORY?.split("/")[1] || "xmlui"; const maxReleases = process.env.DOCS_XMLUI_MAX_RELEASES_LENGTH; return { owner, repo, githubToken, maxReleases, }; } function processOptions(args) { handleHelpOption(args); const envVars = processEnvVars(); const writeToStdout = args.includes("--stdout"); const outputFile = getOptionValue(args, "--output"); const maxReleasesStr = getOptionValue(args, "--maxReleases") ?? envVars.maxReleases ?? DEF_MAX_RELEASES_STR; const maxReleasesParse = parseInt(maxReleasesStr); const maxReleases = Number.isNaN(maxReleasesParse) ? parseInt(DEF_MAX_RELEASES_STR) : maxReleasesParse; return { ...envVars, outputFile, writeToStdout, maxReleases, }; } async function getXmluiReleases(options) { try { const { Octokit } = await import("@octokit/rest"); const octokit = new Octokit({ auth: options.githubToken, }); console.error("Fetching releases from GitHub API..."); const { data: releases } = await octokit.rest.repos.listReleases({ owner: options.owner, repo: options.repo, }); const xmluiReleases = releases.filter((release) => release.tag_name.startsWith("xmlui@")); xmluiReleases.sort(sortByVersion); const releasesToProcess = xmluiReleases.slice(0, options.maxReleases); const availableVersions = []; for (const release of releasesToProcess) { const xmluiStandaloneAsset = release.assets.find((asset) => XMLUI_STANDALONE_PATTERN.test(asset.name), ); const changes = release.body ? parseBodyIntoChanges(release.body) : []; if (xmluiStandaloneAsset) { availableVersions.push({ tag_name: release.tag_name, published_at: release.published_at, changes: changes, assets: [ { name: xmluiStandaloneAsset.name, browser_download_url: xmluiStandaloneAsset.browser_download_url, }, ], }); } } return availableVersions; } catch (error) { console.error("Error fetching xmlui releases:", error.message); if (error.status === 401) { console.error("Authentication failed. Please check your GitHub token"); } else if (error.status === 403) { console.error("Rate limit exceeded or insufficient permissions."); } return null; } } async function main() { const args = process.argv.slice(2); const options = processOptions(args); const outputFile = options.outputFile; try { const releases = await getXmluiReleases(options); console.error(`Got ${releases.length} xmlui releases`); if (releases === null) { console.error("Failed to fetch xmlui releases. Exiting with code 1."); process.exit(1); } if (outputFile === null || options.writeToStdout) { console.log(JSON.stringify(releases, null, 2)); } else { // Ensure output directory exists const outputDir = path.dirname(outputFile); await fs.mkdir(outputDir, { recursive: true }); // Write to file await fs.writeFile(outputFile, JSON.stringify(releases, null, 2), "utf8"); console.error(`Successfully updated ${outputFile}`); } } catch (error) { console.error("Error writing release info:", error); process.exit(1); } } function getOptionValue(args, optionName) { const optionIndex = args.indexOf(optionName); if (optionIndex === -1) return null; const value = args[optionIndex + 1]; if (value === undefined) return null; if (value.startsWith("--")) return null; return value; } function handleHelpOption(args) { if (args.includes("--help") || args.includes("-h")) { const helpMessage = ` Fetches xmlui release information from GitHub and outputs it in JSON format. This script relies on the GITHUB_TOKEN environment variable for authentication when fetching data directly from the GitHub API. Usage: ./get-releases.js [options] Options: --output <file> Specify the path to the output JSON file. If this option is not provided the output will be directed to standard output. --stdout Force the JSON output to standard output. --maxReleases Specify the maximum number of xmlui releases to include. Defaults to ${DEF_MAX_RELEASES_STR}. --help, -h Display this help message and exit. Environment Variables: GITHUB_TOKEN Required for fetching data from the GitHub API. A GitHub Personal Access Token with 'repo' scope (or at least permissions to read repository releases). The script will fail if this is not set and API access is attempted. GITHUB_REPOSITORY Optional. The 'owner/repo' string. Defaults to "xmlui-org/xmlui". DOCS_XMLUI_MAX_RELEASES_LENGTH Optional. The maximum number of xmlui releases to include. Can be overridden by the --maxReleases command line option. Defaults to ${DEF_MAX_RELEASES_STR}. `; console.log(helpMessage.trimStart().trimEnd()); process.exit(0); } } /** * Parse markdown body into individual changes with commit SHA and description * @param {string} body * @returns {Array<{description: string, commit_sha: string}>} */ function parseBodyIntoChanges(body) { const changes = []; const lines = body.split("\n"); for (const line of lines) { // Skip markdown headings if (MD_HEADING_PATTERN.test(line)) { continue; } // Process list items that may contain commit SHA if (line.startsWith("- ")) { const match = line.match(MD_LIST_COMMIT_SHA_PATTERN); if (match) { // Extract commit SHA and description const commitSha = match[0].match(/[a-f0-9]{6,40}/)[0]; const description = line.replace(MD_LIST_COMMIT_SHA_PATTERN, "").trim(); changes.push({ description: description, commit_sha: commitSha, }); } else { // List item without commit SHA const description = line.substring(2).trim(); // Remove "- " prefix if (description) { changes.push({ description: description, commit_sha: null, // No commit SHA available }); } } } } return changes; } ``` -------------------------------------------------------------------------------- /xmlui/src/components/TimeInput/TimeInput.tsx: -------------------------------------------------------------------------------- ```typescript import styles from "./TimeInput.module.scss"; import { createComponentRenderer } from "../../components-core/renderers"; import { parseScssVar } from "../../components-core/theming/themeVars"; import { createMetadata, d, dAutoFocus, dDidChange, dEnabled, dEndIcon, dEndText, dGotFocus, dInitialValue, dLostFocus, dReadonly, dStartIcon, dStartText, dValidationStatus, } from "../metadata-helpers"; import { TimeInputNative, defaultProps } from "./TimeInputNative"; const COMP = "TimeInput"; export const TimeInputMd = createMetadata({ status: "experimental", description: "`TimeInput` provides time input with support for 12-hour and 24-hour formats " + "and configurable precision for hours, minutes, and seconds.", parts: { hour: { description: "The hour input field.", }, minute: { description: "The minute input field.", }, second: { description: "The second input field.", }, ampm: { description: "The AM/PM indicator.", }, clearButton: { description: "The button to clear the time input.", }, }, props: { initialValue: dInitialValue(), autoFocus: dAutoFocus(), readOnly: dReadonly(), enabled: dEnabled(defaultProps.enabled), validationStatus: dValidationStatus(defaultProps.validationStatus), hour24: { description: "Whether to use 24-hour format (true) or 12-hour format with AM/PM (false)", valueType: "boolean", defaultValue: defaultProps.hour24, }, seconds: { description: "Whether to show and allow input of seconds", valueType: "boolean", defaultValue: defaultProps.seconds, }, minTime: { description: "Minimum time that the user can select", valueType: "string", }, maxTime: { description: "Maximum time that the user can select", valueType: "string", }, clearable: { description: "Whether to show a clear button that allows clearing the selected time", valueType: "boolean", defaultValue: defaultProps.clearable, }, clearIcon: { description: "The icon to display in the clear button.", valueType: "string", }, clearToInitialValue: { description: "Whether the clear button resets the time input to its initial value", valueType: "boolean", defaultValue: defaultProps.clearToInitialValue, }, required: { description: "Whether the time input should be required", valueType: "boolean", defaultValue: defaultProps.required, }, startText: dStartText(), startIcon: dStartIcon(), endText: dEndText(), endIcon: dEndIcon(), gap: { description: "This property defines the gap between the adornments and the input area. If not " + "set, the gap declared by the current theme is used.", valueType: "string", }, emptyCharacter: { description: "Character to use as placeholder for empty time values. If longer than 1 character, uses the first character. Defaults to '-'", valueType: "string", defaultValue: defaultProps.emptyCharacter, }, }, events: { didChange: dDidChange(COMP), gotFocus: dGotFocus(COMP), lostFocus: dLostFocus(COMP), invalidTime: d("Fired when the user enters an invalid time"), }, apis: { focus: { description: `Focus the ${COMP} component.`, signature: "focus(): void", }, value: { description: `You can query the component's value. If no value is set, it will retrieve \`undefined\`.`, signature: "get value(): any", }, setValue: { description: `This method sets the current value of the ${COMP}.`, signature: "set value(value: any): void", parameters: { value: "The new time value to set for the time picker.", }, }, isoValue: { description: `Get the current time value formatted in ISO standard (HH:MM:SS) using 24-hour format, suitable for JSON serialization.`, signature: "isoValue(): string | null", }, }, themeVars: parseScssVar(styles.themeVars), defaultThemeVars: { // TimeInput specific theme variables [`paddingHorizontal-${COMP}`]: "$space-2", [`paddingVertical-${COMP}`]: "$space-2", [`color-divider-${COMP}`]: "$textColor-secondary", [`spacing-divider-${COMP}`]: "1px 0", [`width-input-${COMP}`]: "1.8em", [`minWidth-input-${COMP}`]: "0.54em", [`padding-input-${COMP}`]: "0 2px", [`textAlign-input-${COMP}`]: "center", [`fontSize-input-${COMP}`]: "inherit", [`borderRadius-input-${COMP}`]: "$borderRadius", [`backgroundColor-input-${COMP}-invalid`]: "rgba(220, 53, 69, 0.15)", [`padding-button-${COMP}`]: "4px 4px", [`borderRadius-button-${COMP}`]: "$borderRadius", [`hoverColor-button-${COMP}`]: "$color-surface-800", [`disabledColor-button-${COMP}`]: "$textColor-disabled", [`outlineColor-button-${COMP}--focused`]: "$color-accent-500", [`outlineWidth-button-${COMP}--focused`]: "2px", [`outlineOffset-button-${COMP}--focused`]: "0", [`minWidth-ampm-${COMP}`]: "2.2em", [`fontSize-ampm-${COMP}`]: "inherit", }, }); export const timeInputComponentRenderer = createComponentRenderer( COMP, TimeInputMd, ({ node, state, updateState, extractValue, className, lookupEventHandler, registerComponentApi, }) => { const extractedInitialValue = extractValue(node.props.initialValue); const stateValue = state?.value; return ( <TimeInputNative className={className} initialValue={extractedInitialValue} value={stateValue} updateState={updateState} registerComponentApi={registerComponentApi} enabled={extractValue.asOptionalBoolean(node.props.enabled, defaultProps.enabled)} autoFocus={extractValue.asOptionalBoolean(node.props.autoFocus, defaultProps.autoFocus)} readOnly={extractValue.asOptionalBoolean(node.props.readOnly, defaultProps.readOnly)} validationStatus={extractValue(node.props.validationStatus)} hour24={extractValue.asOptionalBoolean(node.props.hour24, defaultProps.hour24)} seconds={extractValue.asOptionalBoolean(node.props.seconds, defaultProps.seconds)} minTime={extractValue(node.props.minTime)} maxTime={extractValue(node.props.maxTime)} clearable={extractValue.asOptionalBoolean(node.props.clearable, defaultProps.clearable)} clearIcon={extractValue(node.props.clearIcon)} clearToInitialValue={extractValue.asOptionalBoolean(node.props.clearToInitialValue, defaultProps.clearToInitialValue)} required={extractValue.asOptionalBoolean(node.props.required, defaultProps.required)} startText={extractValue(node.props.startText)} startIcon={extractValue(node.props.startIcon)} endText={extractValue(node.props.endText)} endIcon={extractValue(node.props.endIcon)} gap={extractValue.asOptionalString(node.props.gap)} emptyCharacter={extractValue.asOptionalString(node.props.emptyCharacter, defaultProps.emptyCharacter)} onDidChange={lookupEventHandler("didChange")} onFocus={lookupEventHandler("gotFocus")} onBlur={lookupEventHandler("lostFocus")} onInvalidChange={lookupEventHandler("invalidTime")} /> ); }, ); ``` -------------------------------------------------------------------------------- /xmlui/src/components/Form/Form.tsx: -------------------------------------------------------------------------------- ```typescript import styles from "./Form.module.scss"; import { createComponentRenderer } from "../../components-core/renderers"; import { parseScssVar } from "../../components-core/theming/themeVars"; import { createMetadata, d, dComponent, dEnabled, dInternal } from "../metadata-helpers"; import { labelPositionMd } from "../abstractions"; import { FormWithContextVar, defaultProps } from "./FormNative"; const COMP = "Form"; export const FormMd = createMetadata({ status: "stable", description: "`Form` provides a structured container for collecting and validating user " + "input, with built-in data binding, validation, and submission handling. It " + "automatically manages form state and provides context for nested form controls " + "to work together.", props: { buttonRowTemplate: dComponent( `This property allows defining a custom component to display the buttons at the bottom of the form.`, ), itemLabelPosition: { description: `This property sets the position of the item labels within the form.` + `Individual \`FormItem\` instances can override this property.`, availableValues: labelPositionMd, type: "string", defaultValue: defaultProps.itemLabelPosition, }, itemLabelWidth: { description: "This property sets the width of the item labels within the form. Individual " + "\`FormItem\` instances can override this property. If this property is not set, " + "each form item nested in the form uses its calculated label width. These widths " + "may be different for each item.", type: "string", }, itemLabelBreak: { description: `This boolean value indicates if form item labels can be split into multiple ` + `lines if it would overflow the available label width. Individual \`FormItem\` ` + `instances can override this property.`, type: "boolean", defaultValue: defaultProps.itemLabelBreak, }, keepModalOpenOnSubmit: { description: "This property prevents the modal from closing when the form is submitted.", type: "boolean", defaultValue: defaultProps.keepModalOpenOnSubmit, }, data: { description: "This property sets the initial value of the form's data structure. The form infrastructure " + "uses this value to set the initial state of form items within the form. If this property is" + "not set, the form does not have an initial value.", }, cancelLabel: { description: "This property defines the label of the Cancel button.", type: "string", defaultValue: defaultProps.cancelLabel, }, saveLabel: { description: `This property defines the label of the Save button.`, type: "string", defaultValue: defaultProps.saveLabel, }, saveInProgressLabel: { description: "This property defines the label of the Save button to display during the " + "form data submit (save) operation.", type: "string", defaultValue: defaultProps.saveInProgressLabel, }, swapCancelAndSave: { description: `By default, the Cancel button is to the left of the Save button. Set this property to ` + `\`true\` to swap them or \`false\` to keep their original location.`, type: "boolean", defaultValue: defaultProps.swapCancelAndSave, }, hideButtonRowUntilDirty: { description: `This property hides the button row until the form data is modified (dirty).`, type: "boolean", defaultValue: defaultProps.hideButtonRowUntilDirty, }, submitUrl: d(`URL to submit the form data.`), submitMethod: { description: "This property sets the HTTP method to use when submitting the form data. If not " + "defined, `put` is used when the form has initial data; otherwise, `post`.", }, inProgressNotificationMessage: d("This property sets the message to display when the form is being submitted."), completedNotificationMessage: d("This property sets the message to display when the form is submitted successfully."), errorNotificationMessage: d("This property sets the message to display when the form submission fails."), enabled: dEnabled(), _data_url: dInternal("when we have an api bound data prop, we inject the url here"), }, events: { willSubmit: d( `The form infrastructure fires this event just before the form is submitted. The event argument ` + `is the current \`data\` value to be submitted. You can cancel the submission by returning ` + `\`false\` from the event handler.`, ), submit: d( `The form infrastructure fires this event when the form is submitted. The event argument ` + `is the current \`data\` value to save.`, ), success: d("The form infrastructure fires this event when the form is submitted successfully."), cancel: d(`The form infrastructure fires this event when the form is canceled.`), reset: d(`The form infrastructure fires this event when the form is reset.`), }, contextVars: { $data: d( `This property represents the value of the form data. You can access the fields of the form ` + `using the IDs in the \`bindTo\` property of nested \`FormItem\` instances. \`$data\` also ` + `provides an \`update\` method as a shortcut to the Form's exposed \`update\` method.`, ), }, apis: { reset: { description: "This method resets the form to its initial state, clearing all user input.", signature: "reset(): void", }, update: { description: "You can pass a data object to update the form data. The properties in the passed data " + "object are updated to their values accordingly. Other form properties remain intact.", signature: "update(data: Record<string, any>): void", parameters: { data: "An object containing the form data to update.", }, }, }, themeVars: parseScssVar(styles.themeVars), defaultThemeVars: { "gap-Form": "$space-4", "gap-buttonRow-Form": "$space-4", "backgroundColor-ValidationDisplay-error": "$color-danger-100", "backgroundColor-ValidationDisplay-warning": "$color-warn-100", "backgroundColor-ValidationDisplay-info": "$color-primary-100", "backgroundColor-ValidationDisplay-valid": "$color-success-100", "color-accent-ValidationDisplay-error": "$color-error", "color-accent-ValidationDisplay-warning": "$color-warning", "color-accent-ValidationDisplay-info": "$color-info", "color-accent-ValidationDisplay-valid": "$color-valid", "textColor-ValidationDisplay-error": "$color-error", "textColor-ValidationDisplay-warning": "$color-warning", "textColor-ValidationDisplay-info": "$color-info", "textColor-ValidationDisplay-valid": "$color-valid", "marginTop-buttonRow-Form": "$space-4" }, }); export const formComponentRenderer = createComponentRenderer( COMP, FormMd, ({ node, renderChild, extractValue, className, lookupEventHandler, registerComponentApi }) => { return ( <FormWithContextVar node={node as any} renderChild={renderChild} extractValue={extractValue} lookupEventHandler={lookupEventHandler as any} className={className} registerComponentApi={registerComponentApi} /> ); }, ); ```