This is page 22 of 140. Use http://codebase.md/xmlui-org/xmlui/mockApiDef.js?page={x} to view the full context. # Directory Structure ``` ├── .changeset │ ├── config.json │ ├── cool-queens-look.md │ ├── twelve-guests-care.md │ └── wise-towns-dance.md ├── .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 │ ├── 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 │ │ ├── netlify.toml │ │ ├── resources │ │ │ ├── favicon.ico │ │ │ ├── files │ │ │ │ └── for-download │ │ │ │ └── xmlui │ │ │ │ └── xmlui-standalone.umd.js │ │ │ ├── github.svg │ │ │ ├── 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 │ │ │ ├── Debug.xmlui │ │ │ └── PageNotFound.xmlui │ │ ├── config.ts │ │ ├── Main.xmlui │ │ ├── Main.xmlui.xs │ │ └── themes │ │ ├── docs-theme.ts │ │ ├── earthtone.ts │ │ ├── xmlui-gray-on-default.ts │ │ ├── xmlui-green-on-default.ts │ │ └── xmlui-orange-on-default.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 │ ├── containers.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 │ ├── state-management.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 │ │ │ ├── MultiSelectOption.tsx │ │ │ ├── OptionContext.ts │ │ │ ├── Select.md │ │ │ ├── Select.module.scss │ │ │ ├── Select.spec.ts │ │ │ ├── Select.tsx │ │ │ ├── SelectContext.tsx │ │ │ ├── SelectNative.tsx │ │ │ ├── SelectOption.tsx │ │ │ └── SimpleSelect.tsx │ │ ├── SelectionStore │ │ │ ├── SelectionStore.md │ │ │ ├── SelectionStore.tsx │ │ │ └── SelectionStoreNative.tsx │ │ ├── Slider │ │ │ ├── Slider.md │ │ │ ├── Slider.module.scss │ │ │ ├── Slider.spec.ts │ │ │ ├── Slider.tsx │ │ │ └── SliderNative.tsx │ │ ├── Slot │ │ │ ├── Slot.md │ │ │ ├── Slot.spec.ts │ │ │ └── Slot.ts │ │ ├── SlotItem.tsx │ │ ├── SpaceFiller │ │ │ ├── SpaceFiller.md │ │ │ ├── SpaceFiller.module.scss │ │ │ ├── SpaceFiller.spec.ts │ │ │ ├── SpaceFiller.tsx │ │ │ └── SpaceFillerNative.tsx │ │ ├── Spinner │ │ │ ├── Spinner.md │ │ │ ├── Spinner.module.scss │ │ │ ├── Spinner.spec.ts │ │ │ ├── Spinner.tsx │ │ │ └── SpinnerNative.tsx │ │ ├── Splitter │ │ │ ├── HSplitter.md │ │ │ ├── HSplitter.spec.ts │ │ │ ├── Splitter.md │ │ │ ├── Splitter.module.scss │ │ │ ├── Splitter.spec.ts │ │ │ ├── Splitter.tsx │ │ │ ├── SplitterNative.tsx │ │ │ ├── utils.ts │ │ │ ├── VSplitter.md │ │ │ └── VSplitter.spec.ts │ │ ├── Stack │ │ │ ├── CHStack.md │ │ │ ├── CHStack.spec.ts │ │ │ ├── CVStack.md │ │ │ ├── CVStack.spec.ts │ │ │ ├── HStack.md │ │ │ ├── HStack.spec.ts │ │ │ ├── Stack.md │ │ │ ├── Stack.module.scss │ │ │ ├── Stack.spec.ts │ │ │ ├── Stack.tsx │ │ │ ├── StackNative.tsx │ │ │ ├── VStack.md │ │ │ └── VStack.spec.ts │ │ ├── StickyBox │ │ │ ├── StickyBox.md │ │ │ ├── StickyBox.module.scss │ │ │ ├── StickyBox.tsx │ │ │ └── StickyBoxNative.tsx │ │ ├── Switch │ │ │ ├── Switch.md │ │ │ ├── Switch.spec.ts │ │ │ └── Switch.tsx │ │ ├── Table │ │ │ ├── doc-resources │ │ │ │ └── list-component-data.js │ │ │ ├── react-table-config.d.ts │ │ │ ├── Table.md │ │ │ ├── Table.module.scss │ │ │ ├── Table.spec.ts │ │ │ ├── Table.tsx │ │ │ ├── TableNative.tsx │ │ │ └── useRowSelection.tsx │ │ ├── TableOfContents │ │ │ ├── TableOfContents.module.scss │ │ │ ├── TableOfContents.spec.ts │ │ │ ├── TableOfContents.tsx │ │ │ └── TableOfContentsNative.tsx │ │ ├── Tabs │ │ │ ├── TabContext.tsx │ │ │ ├── TabItem.md │ │ │ ├── TabItem.tsx │ │ │ ├── TabItemNative.tsx │ │ │ ├── Tabs.md │ │ │ ├── Tabs.module.scss │ │ │ ├── Tabs.spec.ts │ │ │ ├── Tabs.tsx │ │ │ └── TabsNative.tsx │ │ ├── Text │ │ │ ├── Text.md │ │ │ ├── Text.module.scss │ │ │ ├── Text.spec.ts │ │ │ ├── Text.tsx │ │ │ └── TextNative.tsx │ │ ├── TextArea │ │ │ ├── TextArea.md │ │ │ ├── TextArea.module.scss │ │ │ ├── TextArea.spec.ts │ │ │ ├── TextArea.tsx │ │ │ ├── TextAreaNative.tsx │ │ │ ├── TextAreaResizable.tsx │ │ │ └── useComposedRef.ts │ │ ├── TextBox │ │ │ ├── TextBox.md │ │ │ ├── TextBox.module.scss │ │ │ ├── TextBox.spec.ts │ │ │ ├── TextBox.tsx │ │ │ └── TextBoxNative.tsx │ │ ├── Theme │ │ │ ├── NotificationToast.tsx │ │ │ ├── Theme.md │ │ │ ├── Theme.module.scss │ │ │ ├── Theme.spec.ts │ │ │ ├── Theme.tsx │ │ │ └── ThemeNative.tsx │ │ ├── TimeInput │ │ │ ├── TimeInput.md │ │ │ ├── TimeInput.module.scss │ │ │ ├── TimeInput.spec.ts │ │ │ ├── TimeInput.tsx │ │ │ ├── TimeInputNative.tsx │ │ │ └── utils.ts │ │ ├── Timer │ │ │ ├── Timer.md │ │ │ ├── Timer.spec.ts │ │ │ ├── Timer.tsx │ │ │ └── TimerNative.tsx │ │ ├── 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 │ │ │ ├── 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/src/components-core/LoaderComponent.tsx: -------------------------------------------------------------------------------- ```typescript import { type MutableRefObject, useCallback, useEffect, useMemo } from "react"; import type { ContainerDispatcher, MemoedVars } from "./abstractions/ComponentRenderer"; import type { RegisterComponentApiFn } from "../abstractions/RendererDefs"; import type { ContainerState, RegisterComponentApiFnInner } from "./rendering/ContainerWrapper"; import type { ComponentDef } from "../abstractions/ComponentDefs"; import type { LookupAsyncFn, LookupAsyncFnInner, LookupSyncFn, LookupSyncFnInner, } from "../abstractions/ActionDefs"; import { useComponentRegistry } from "../components/ComponentRegistryContext"; import { ContainerActionKind } from "./rendering/containers"; import { createValueExtractor } from "./rendering/valueExtractor"; import { useReferenceTrackedApi } from "./utils/hooks"; import type { AppContextObject } from "../abstractions/AppContextDefs"; interface LoaderRendererContext { node: ComponentDef; state: ContainerState; dispatch: ContainerDispatcher; registerComponentApi: RegisterComponentApiFnInner; lookupAction: LookupAsyncFnInner; lookupSyncCallback: LookupSyncFnInner; memoedVarsRef: MutableRefObject<MemoedVars>; appContext: AppContextObject; onUnmount: (uid: symbol) => void; } export function LoaderComponent({ node, state, dispatch, lookupAction, lookupSyncCallback, registerComponentApi, onUnmount, appContext, memoedVarsRef, }: LoaderRendererContext) { const componentRegistry = useComponentRegistry(); const uid = useMemo(() => Symbol(node.uid), [node.uid]); useEffect(() => { return () => { onUnmount(uid); }; }, [onUnmount, uid]); // --- Memoizes component API registration const memoedRegisterComponentApi: RegisterComponentApiFn = useCallback( (api) => { registerComponentApi(uid, api); }, [registerComponentApi, uid], ); // --- Memoizes the action resolution by action definition value const memoedLookupAction: LookupAsyncFn = useCallback( (action, actionOptions) => { return lookupAction(action, uid, actionOptions); }, [lookupAction, uid], ); // --- Get the tracked APIs of the compomnent const referenceTrackedApi = useReferenceTrackedApi(state); // --- Memoizes the value extractor object const valueExtractor = useMemo(() => { return createValueExtractor(state, appContext, referenceTrackedApi, memoedVarsRef); }, [appContext, memoedVarsRef, referenceTrackedApi, state]); // --- Memoizes the action resolution by action definition value const memoedLookupSyncCallback: LookupSyncFn = useCallback( (action) => { if (!action) { return undefined; } return lookupSyncCallback(valueExtractor(action), uid); }, [lookupSyncCallback, uid, valueExtractor], ); const memoedLoaderInProgressChanged = useCallback( (isInProgress: boolean) => { dispatch(loaderInProgressChanged(uid, isInProgress)); }, [dispatch, uid], ); const memoedLoaderIsRefetchingChanged = useCallback( (isRefetching: boolean) => { dispatch(loaderIsRefetchingChanged(uid, isRefetching)); }, [dispatch, uid], ); const memoedLoaderLoaded = useCallback( (data: any, pageInfo: any) => { dispatch(loaderLoaded(uid, data, pageInfo)); }, [dispatch, uid], ); const memoedLoaderError = useCallback( (error: any) => { dispatch(loaderError(uid, error)); }, [dispatch, uid], ); const renderer = componentRegistry.lookupLoaderRenderer(node.type); if (!renderer) { console.error( `Loader ${node.type} is not available. Did you forget to register it in the loaderRegistry?`, ); return null; } return renderer({ loader: node, state, dispatch, loaderInProgressChanged: memoedLoaderInProgressChanged, loaderIsRefetchingChanged: memoedLoaderIsRefetchingChanged, loaderLoaded: memoedLoaderLoaded, loaderError: memoedLoaderError, extractValue: valueExtractor, registerComponentApi: memoedRegisterComponentApi, lookupAction: memoedLookupAction, lookupSyncCallback: memoedLookupSyncCallback, }); } // Signs that a particular loader (`uid`) has just started fetching its data (or executing its operation). function loaderInProgressChanged(uid: symbol, isInProgress: boolean) { return { type: ContainerActionKind.LOADER_IN_PROGRESS_CHANGED, payload: { uid, inProgress: isInProgress, }, }; } // Signs that a particular loader (`uid`) has just started refetching its data (or executing its operation). function loaderIsRefetchingChanged(uid: symbol, isRefetching: boolean) { return { type: ContainerActionKind.LOADER_IS_REFETCHING_CHANGED, payload: { uid, isRefetching, }, }; } // Signs that a particular loader (`uid`) has just fetched its data (`pageInfo`) successfully. function loaderLoaded(uid: symbol, data: any, pageInfo?: any) { return { type: ContainerActionKind.LOADER_LOADED, payload: { uid, data, pageInfo, }, }; } // Signs that a particular loader (`uid`) has has an `error` during its operation. function loaderError(uid: symbol, error: any) { return { type: ContainerActionKind.LOADER_ERROR, payload: { uid, error, }, }; } ``` -------------------------------------------------------------------------------- /xmlui/src/components-core/CompoundComponent.tsx: -------------------------------------------------------------------------------- ```typescript import React, { forwardRef, isValidElement, useMemo } from "react"; import { composeRefs } from "@radix-ui/react-compose-refs"; import type { ComponentDef } from "../abstractions/ComponentDefs"; import type { ContainerWrapperDef } from "./rendering/ContainerWrapper"; import type { CollectedDeclarations } from "./script-runner/ScriptingSourceTree"; import type { RendererContext } from "../abstractions/RendererDefs"; import { useEvent } from "./utils/misc"; import { useShallowCompareMemoize } from "./utils/hooks"; import { isArray, isObject } from "lodash-es"; import { EMPTY_ARRAY } from "./constants"; import { mergeProps } from "./utils/mergeProps"; type CompoundComponentProps = { // Definition of the `component` part of the compound component compound: ComponentDef; // The API of the compound component api?: Record<string, string>; scriptCollected?: CollectedDeclarations; } & RendererContext; // Acts as a bridge between a compound component definition and its renderer. export const CompoundComponent = forwardRef( ( { node, lookupSyncCallback, lookupEventHandler, compound, api, scriptCollected, renderChild, extractValue, layoutContext, uid, updateState, registerComponentApi, extractResourceUrl, appContext, state, lookupAction, ...restProps }: CompoundComponentProps, forwardedRef: React.ForwardedRef<any>, ) => { // --- Extract property values (resolve binding expressions) const resolvedPropsInner = useMemo(() => { const resolvedProps: any = {}; if (node.props) { Object.entries(node.props).forEach(([key, value]) => { const extractedProp = extractValue(value, true); if (extractedProp?._ARROW_EXPR_) { // --- Ensure arrow functions are called synchronously resolvedProps[key] = lookupSyncCallback(extractedProp); } else { resolvedProps[key] = extractedProp; } }); } return resolvedProps; }, [extractValue, lookupSyncCallback, node.props]); const resolvedProps = useShallowCompareMemoize(resolvedPropsInner); // --- Wrap the `component` part with a container that manages the const containerNode: ContainerWrapperDef = useMemo(() => { const { loaders, vars, functions, scriptError, ...rest } = compound; return { type: "Container", uses: EMPTY_ARRAY, api, scriptCollected, loaders: loaders, vars, functions: functions, scriptError: scriptError, containerUid: uid, props: { debug: (compound.props as any)?.debug, }, children: [rest], }; }, [api, compound, scriptCollected, uid]); const emitEvent = useEvent((eventName, ...args) => { const handler = lookupEventHandler(eventName); if (handler) { return handler(...args); } }); const hasEventHandler = useEvent((eventName) => !!lookupEventHandler(eventName)); const vars = useMemo(() => { return { $props: resolvedProps, ...containerNode.vars, emitEvent, hasEventHandler, updateState, }; }, [containerNode.vars, emitEvent, hasEventHandler, resolvedProps, updateState]); const stableVars = useShallowCompareMemoize(vars); // --- Inject implicit variable into the container of the compound component const nodeWithPropsAndEventsInner = useMemo(() => { return { ...containerNode, vars: stableVars, }; }, [containerNode, stableVars]); const nodeWithPropsAndEvents = useShallowCompareMemoize(nodeWithPropsAndEventsInner); const hasTemplateProps = useMemo(() => { return Object.entries(node.props).some(([key, value]) => { return ( //TODO this is a hack, we should have a better way to detect template props key.endsWith("Template") || (isObject(value) && (value as any).type !== undefined) || (isArray(value) && (value as any)[0]?.type !== undefined) ); }); }, [node.props]); const memoedParentRenderContext = useMemo(() => { if (!hasTemplateProps && (!node.children || node.children.length === 0)) { return undefined; } return { renderChild, props: node.props, children: node.children, }; }, [hasTemplateProps, node.children, node.props, renderChild]); //we remove the wrapChild prop from layout context, because that wrapping already happened for the compound component instance const safeLayoutContext = layoutContext ? { ...layoutContext, wrapChild: undefined } : layoutContext; const ret = renderChild(nodeWithPropsAndEvents, safeLayoutContext, memoedParentRenderContext); if (forwardedRef && ret && isValidElement(ret)) { return React.cloneElement(ret, { ref: composeRefs(forwardedRef, (ret as any).ref), ...mergeProps( ret.props, restProps ), } as any); } return React.isValidElement(ret) ? ret : <>{ret}</>; }, ); // --- Display a name for the component in developer tools CompoundComponent.displayName = "CompoundComponent"; ``` -------------------------------------------------------------------------------- /.github/workflows/release-packages.yml: -------------------------------------------------------------------------------- ```yaml name: Release Packages on: workflow_dispatch: pull_request: types: - closed branches: - main permissions: contents: write pull-requests: write id-token: write jobs: publish_and_github_release: name: Publish Stable and Create GitHub Release if: > github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.event.action == 'closed' && github.event.pull_request.merged == true && (startsWith(github.event.pull_request.title, 'Version Packages for Stable Release') || contains(github.event.pull_request.labels.*.name, 'changeset-release'))) runs-on: ubuntu-latest-8-core timeout-minutes: 60 env: NODE_OPTIONS: "--max-old-space-size=8192" # needs: [create_version_pr] # This is implied by the trigger conditions steps: - name: Checkout Repo uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 22 cache: "npm" registry-url: "https://registry.npmjs.org" - name: Install dependencies run: npm ci --prefer-offline - name: Cache for Turbo uses: rharkor/[email protected] - name: Store Playwright's Version run: | PLAYWRIGHT_VERSION=$(npm ls @playwright/test | grep @playwright | sed 's/.*@//' | sort | head -n 1) echo "Playwright's Version: $PLAYWRIGHT_VERSION" echo "PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION" >> $GITHUB_ENV - name: Cache Playwright Browsers for Playwright's Version id: cache-playwright-browsers uses: actions/cache@v4 with: path: ~/.cache/ms-playwright key: playwright-browsers-${{ env.PLAYWRIGHT_VERSION }} - name: Install Playwright Browsers if: steps.cache-playwright-browsers.outputs.cache-hit != 'true' run: npx playwright install --with-deps - name: Create Version PR or Publish to NPM and Create GitHub Releases id: changesets_publish uses: changesets/action@v1 with: publish: npm run publish-packages env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NODE_AUTH_TOKEN: ${{secrets.PUBLISH_NPM_TOKEN}} - name: Output Published Packages Info if: steps.changesets_publish.outputs.published == 'true' run: | echo "Packages published: ${{ steps.changesets_publish.outputs.publishedPackages }}" - name: Prepare standalone js file id: prepare_standalone if: steps.changesets_publish.outputs.published == 'true' && contains(fromJSON(steps.changesets_publish.outputs.publishedPackages).*.name, 'xmlui') run: | XMLUI_VERSION=$(jq -r .version xmlui/package.json) STANDALONE_FILENAME="xmlui-${XMLUI_VERSION}.js" cp xmlui/dist/standalone/xmlui-standalone.umd.js $STANDALONE_FILENAME echo "version=$XMLUI_VERSION" >> $GITHUB_OUTPUT echo "filename=$STANDALONE_FILENAME" >> $GITHUB_OUTPUT echo "prepared xmlui-standalone.umd.js for release with filename: $STANDALONE_FILENAME" - name: Upload standalone js file if: steps.prepare_standalone.outputs.filename != '' uses: softprops/action-gh-release@v1 with: files: "${{ steps.prepare_standalone.outputs.filename }}" tag_name: xmlui@${{ steps.prepare_standalone.outputs.version }} fail_on_unmatched_files: true - name: Get VSCode Extension release info id: xmlui_vscode_info if: steps.changesets_publish.outputs.published == 'true' run: | XMLUI_VSCODE_VERSION=$(jq -r .version tools/vscode/package.json) XMLUI_VSCODE_TAG="xmlui-vscode@${XMLUI_VSCODE_VERSION}" echo "tag=$XMLUI_VSCODE_TAG" >> $GITHUB_OUTPUT XMLUI_VSCODE_TAG_EXISTING=$(git tag --list $XMLUI_VSCODE_TAG) TAG_EXISTS="false" if [ ! -z "$XMLUI_VSCODE_TAG_EXISTING" ]; then TAG_EXISTS="true" fi echo "tagExists=$TAG_EXISTS" >> $GITHUB_OUTPUT echo "current VSCode extension version: $XMLUI_VSCODE_VERSION" echo "release tag for VSCode extension: $XMLUI_VSCODE_TAG" echo "tag already exists: $TAG_EXISTS" - name: Build VSCode extension if: steps.changesets_publish.outputs.published == 'true' && steps.xmlui_vscode_info.outputs.tagExists == 'false' run: npm run build-vscode-extension - name: Upload VSIX to Release if: steps.changesets_publish.outputs.published == 'true' && steps.xmlui_vscode_info.outputs.tagExists == 'false' uses: softprops/action-gh-release@v1 with: files: "tools/vscode/*.vsix" tag_name: ${{ steps.xmlui_vscode_info.outputs.tag }} fail_on_unmatched_files: true - name: Publish VSCode Extension to Marketplace if: steps.changesets_publish.outputs.published == 'true' && steps.xmlui_vscode_info.outputs.tagExists == 'false' run: | cd tools/vscode npx vsce publish \ --packagePath *.vsix \ --no-git-tag-version \ --no-update-package-json \ env: VSCE_PAT: ${{ secrets.VSCE_PAT }} ``` -------------------------------------------------------------------------------- /xmlui/src/components-core/interception/ApiInterceptorProvider.tsx: -------------------------------------------------------------------------------- ```typescript import type { ReactNode } from "react"; import { useCallback, useEffect, useMemo, useState } from "react"; import type { SetupWorker } from "msw/browser"; import type { IApiInterceptorContext } from "../../abstractions/AppContextDefs"; import type { ApiInterceptorDefinition } from "../interception/abstractions"; import { normalizePath } from "../utils/misc"; import { ApiInterceptorContext } from "./useApiInterceptorContext"; import type { ApiInterceptor } from "./ApiInterceptor"; type Props = { interceptor?: ApiInterceptorDefinition; children: ReactNode; parentInterceptorContext?: IApiInterceptorContext; waitForApiInterceptor?: boolean; useHashBasedRouting?: boolean; }; // This React component injects the API interceptor into the application's context export function ApiInterceptorProvider({ interceptor, children, parentInterceptorContext = null, waitForApiInterceptor = false, }: Props) { const hasParentInterceptorContext = parentInterceptorContext !== null; const [initialized, setInitialized] = useState(!interceptor); const [forceInitialize, setForceInitialize] = useState(false); const [interceptorWorker, setInterceptorWorker] = useState<SetupWorker | null>(null); const [apiInstance, setApiInstance] = useState<ApiInterceptor | null>(null); const useWorker = interceptor?.useWorker ?? true; // --- Whenever the interceptor changes, update the provider accordingly let forceInitializeParent = parentInterceptorContext?.forceInitialize; let parentInterceptorWorker = parentInterceptorContext?.interceptorWorker; //// if we don't use a worker, we initialize the api instance directly useEffect(() => { if (useWorker) { return; } if (!interceptor) { return; } void (async () => { const { initMock } = await import("./initMock"); const apiInstance = await initMock(interceptor); setApiInstance(apiInstance); setInitialized(true); })(); }, [interceptor, useWorker]); //// if we use a worker, we initialize the worker with the api instance useEffect(() => { if (!useWorker) { return; } if (!hasParentInterceptorContext) { if (interceptor || forceInitialize) { // setInitialized(false); // --- We use "msw" to manage the API interception let interceptorWorker: SetupWorker; void (async () => { // --- Create the worker on the fly if (process.env.VITE_MOCK_ENABLED) { const [{ createApiInterceptorWorker }, { initMock }] = await Promise.all([ useWorker ? import("./apiInterceptorWorker") : Promise.resolve({ createApiInterceptorWorker: () => null }), import("./initMock"), ]); if (interceptor || forceInitialize) { const apiInstance = await initMock(interceptor || {}); interceptorWorker = await createApiInterceptorWorker(apiInstance); // if the apiWorker comes from the outside, we don't handle the lifecycle here const workerFileLocation = normalizePath( process.env.VITE_MOCK_WORKER_LOCATION || "mockServiceWorker.js", ); await interceptorWorker.start({ onUnhandledRequest: "bypass", quiet: true, serviceWorker: { url: workerFileLocation, }, }); setInterceptorWorker(interceptorWorker); } } setInitialized(true); })(); return () => { // if the apiWorker comes from the outside, we don't handle the lifecycle here if (!parentInterceptorWorker) { interceptorWorker?.stop(); } setInitialized(false); }; } else { setInitialized(true); } } else { if (!interceptor) { return; } if (parentInterceptorWorker) { void (async () => { const [{ createApiInterceptorWorker }, { initMock }] = await Promise.all([ import("./apiInterceptorWorker"), import("./initMock"), ]); const apiInstance = await initMock(interceptor); await createApiInterceptorWorker(apiInstance, parentInterceptorWorker); setTimeout(() => { setInitialized(true); }, 0); })(); } else { forceInitializeParent?.(); } } }, [ useWorker, forceInitialize, hasParentInterceptorContext, interceptor, forceInitializeParent, parentInterceptorWorker, ]); const isMocked = useCallback( (url) => interceptor !== undefined && !!process.env.VITE_MOCK_ENABLED, [interceptor], ); const doForceInitialize = useCallback(() => { setForceInitialize(true); }, []); const contextValue: IApiInterceptorContext = useMemo(() => { return { interceptorWorker, apiInstance, initialized: initialized, forceInitialize: doForceInitialize, isMocked: isMocked, }; }, [interceptorWorker, apiInstance, initialized, doForceInitialize, isMocked]); return ( <ApiInterceptorContext.Provider value={contextValue}> {waitForApiInterceptor && !initialized ? null : children} </ApiInterceptorContext.Provider> ); } ``` -------------------------------------------------------------------------------- /xmlui/src/components-core/rendering/AppRoot.tsx: -------------------------------------------------------------------------------- ```typescript import { useEffect, useMemo } from "react"; import { QueryClient } from "@tanstack/react-query"; import { enableMapSet } from "immer"; import type { ComponentLike } from "../../abstractions/ComponentDefs"; import { resetErrors } from "../reportEngineError"; import { ComponentProvider } from "../../components/ComponentProvider"; import { DebugViewProvider } from "../DebugViewProvider"; import type StandaloneExtensionManager from "../StandaloneExtensionManager"; import type { AppWrapperProps } from "./AppWrapper"; import { AppWrapper } from "./AppWrapper"; import type { ComponentCompilation } from "../../abstractions/scripting/Compilation"; import { StyleProvider } from "../theming/StyleContext"; // --- We want to enable the produce method of `immer` on Map objects enableMapSet(); // --- This type represents an arbitrary set of global properties (name // --- and value pairs). export type GlobalProps = Record<string, any>; // --- We use this object in the app context to represent the `QlientQuery` // --- of the react-query package. export const queryClient = new QueryClient({ defaultOptions: { queries: { refetchOnWindowFocus: false, }, }, }); /** * This component is responsible for running a pre-compiled xmlui app. It * receives the internal representation of the app markup and code (coming * from either code-behind files or inlined markup expressions) and executes * the app accordingly. */ export function AppRoot({ apiInterceptor, contributes, node, decorateComponentsWithTestId, debugEnabled, defaultTheme, defaultTone, resources, globalProps, standalone, trackContainerHeight, routerBaseName, previewMode, resourceMap, sources, extensionManager, children, projectCompilation, isNested = false, onInit, }: AppWrapperProps & { extensionManager?: StandaloneExtensionManager; isNested?: boolean; }) { // --- Make sure, the root node is wrapped in a `Theme` component. Also, // --- the root node must be wrapped in a `Container` component managing // --- the app's top-level state. const rootNode = useMemo(() => { const themedRoot = { type: "Theme", props: { root: true, }, children: [node], }; return { type: "Container", uid: "root", children: [themedRoot], uses: [], }; }, [node]); if (projectCompilation) { const entryDeps = projectCompilation.entrypoint.dependencies; const transDeps = getTransitiveDependencies(entryDeps, projectCompilation.components); // console.log("projectCompilation: ", projectCompilation); // console.log("transitive dependencies of entrypoint: ", transDeps); } // --- Start with an error-free state resetErrors(); // --- Add isNested to global props so it can be accessed throughout the app const enhancedGlobalProps = useMemo(() => ({ ...globalProps, isNested, }), [globalProps, isNested]); // --- Render the app providing a component registry (in which the engine finds a // --- component definition by its name). Ensure the app has a context for debugging. return ( <ComponentProvider contributes={contributes} extensionManager={extensionManager}> <StyleProvider> <DebugViewProvider debugConfig={globalProps?.debug}> <AppWrapper projectCompilation={projectCompilation} resourceMap={resourceMap} apiInterceptor={apiInterceptor} node={rootNode as ComponentLike} contributes={contributes} resources={resources} routerBaseName={routerBaseName} decorateComponentsWithTestId={decorateComponentsWithTestId} debugEnabled={debugEnabled} defaultTheme={defaultTheme} defaultTone={defaultTone} globalProps={enhancedGlobalProps} standalone={standalone} trackContainerHeight={trackContainerHeight} previewMode={previewMode} sources={sources} onInit={onInit} > {children} </AppWrapper> </DebugViewProvider> </StyleProvider> </ComponentProvider> ); } /** * * @param directCompDepNames The direct dependencies (those are component names) * of the component for which the transitive dependencies will be returned * @param components the compilation info of all the components of the project */ function getTransitiveDependencies( directCompDepNames: Set<string>, components: ComponentCompilation[], ) { const allDepsByCompName: Map<string, Set<string>> = components.reduce( function addDepsByNameToCollection( collection: Map<string, Set<string>>, { definition: { name }, dependencies }, ) { return collection.set(name, dependencies); }, new Map(), ); const transitiveDeps = new Set<string>(); populateTransitiveDeps(directCompDepNames); return transitiveDeps; function populateTransitiveDeps(directCompDepNames: Set<string>) { if (!directCompDepNames) return; for (const directDep of directCompDepNames) { if (!transitiveDeps.has(directDep)) { transitiveDeps.add(directDep); const depsOfDirectDep = allDepsByCompName.get(directDep); populateTransitiveDeps(depsOfDirectDep); } } } } ``` -------------------------------------------------------------------------------- /docs/content/components/Theme.md: -------------------------------------------------------------------------------- ```markdown # Theme [#theme] `Theme` creates styling contexts to customize the appearance of nested components without using CSS. **Key features:** - **No CSS required**: Change component appearance using theme variables instead of custom stylesheets - **Brand consistency**: Maintain design system compliance while allowing contextual variations - **Scoped styling**: Apply theme changes only to nested components without affecting the global design - **Variable overrides**: Modify colors, spacing, typography, and other design variables declaratively - **Nested contexts**: Stack multiple `Theme` components for granular control with automatic specificity rules See [this guide](/themes-intro) and [these references](/styles-and-themes/layout-props) for details. ## Using `Theme` [#using-theme] In contrast to other components, `Theme` accepts theme variables as properties. You can define specific styles for components nested in `Theme` using these theme variables. The following example specifies a dark tone for the current theme and sets several theme variables to style the `ProgressBar` component: ```xmlui-pg copy {3-8} display name="Example: using Theme" <App> <Theme tone="dark" backgroundColor-ProgressBar="cyan" color-indicator-ProgressBar="purple" thickness-ProgressBar="12px" borderRadius-indicator-ProgressBar="12px" borderRadius-Progressbar="4px" > <VStack backgroundColor="$backgroundColor-primary"> <ProgressBar value="0"/> <ProgressBar value="0.2"/> <ProgressBar value="0.6"/> <ProgressBar value="1.0"/> </VStack> </Theme> </App> ``` ## Properties [#properties] ### `applyIf` (default: true) [#applyif-default-true] This property controls whether the theme wrapper is applied. When true (default), the theme wraps the children. When false, children are rendered unwrapped. The `applyIf` property controls whether the theme is conditionally applied to its children. When set to `false`, the children are rendered without the theme wrapper, effectively bypassing the theme styling. ```xmlui-pg copy {2,9,16} display name="Example: applyIf" <App var.apply="{false}"> <Theme backgroundColor-Button="rgb(255, 100, 100)" applyIf="true"> <VStack> <H3>Theme Applied (applyIf="true"):</H3> <Button>Themed Button</Button> </VStack> </Theme> <Theme backgroundColor-Button="rgb(255, 100, 100)" applyIf="false"> <VStack> <H3>Theme Not Applied (applyIf="false"):</H3> <Button>Default Button</Button> </VStack> </Theme> <Theme backgroundColor-Button="rgb(100, 192, 100)" applyIf="{apply}"> <VStack> <H3>Conditional Theme (dynamic):</H3> <Button onClick="apply = !apply"> {apply ? 'Themed' : 'Default'} - Click to Toggle </Button> </VStack> </Theme> </App> ``` This property is particularly useful for: - **Conditional styling**: Apply themes based on user preferences, feature flags, or application state - **Theme debugging**: Temporarily disable themes during development - **Progressive enhancement**: Provide fallback styling when themes fail to load - **Dynamic theming**: Switch themes on and off based on user interactions or data conditions ### `root` (default: false) [#root-default-false] This property indicates whether the component is at the root of the application. If so, it will set a number of important settings for the app: - what favicon to use - sets up font links - specifies the base css - sets up the root for the toast notification system Otherwise, the `Theme` component will just provide the theme context to its children. ### `themeId` [#themeid] This property specifies which theme to use by setting the theme's id. ```xmlui-pg copy {2, 9, 16} display name="Example: themeId" <App> <Theme themeId="xmlui"> <VStack backgroundColor="$backgroundColor-primary"> <H3>Use 'xmlui' theme:</H3> <ProgressBar value="0"/> <ProgressBar value="0.6"/> </VStack> </Theme> <Theme themeId="xmlui-green"> <VStack backgroundColor="$backgroundColor-primary"> <H3>Use 'xmlui-green' theme:</H3> <ProgressBar value="0"/> <ProgressBar value="0.6"/> </VStack> </Theme> <Theme themeId="xmlui-red"> <VStack backgroundColor="$backgroundColor-primary"> <H3>Use the 'xmlui-red' theme:</H3> <ProgressBar value="0"/> <ProgressBar value="0.6"/> </VStack> </Theme> </App> ``` ### `tone` (default: "light") [#tone-default-light] This property allows the setting of the current theme's tone. Available values: `light` **(default)**, `dark` ```xmlui-pg copy {2,9} display name="Example: tone" <App> <Theme tone="light"> <VStack backgroundColor="$backgroundColor-primary" > <H3>Use the light tone of the base theme:</H3> <ProgressBar value="0"/> <ProgressBar value="0.6"/> </VStack> </Theme> <Theme tone="dark"> <VStack backgroundColor="$backgroundColor-primary"> <H3>Use the dark tone of the base theme:</H3> <ProgressBar value="0"/> <ProgressBar value="0.6"/> </VStack> </Theme> </App> ``` ## Events [#events] This component does not have any events. ## Exposed Methods [#exposed-methods] This component does not expose any methods. ## Styling [#styling] The `Theme` component is a styling wrapper that influences the nested components' visual appearance. It cannot be styled. ``` -------------------------------------------------------------------------------- /xmlui/src/components/ExpandableItem/ExpandableItemNative.tsx: -------------------------------------------------------------------------------- ```typescript import { type CSSProperties, forwardRef, type ReactNode, useCallback, useEffect, useId, useRef } from "react"; import { useState } from "react"; import classNames from "classnames"; import styles from "./ExpandableItem.module.scss"; import type { RegisterComponentApiFn } from "../../abstractions/RendererDefs"; import { Icon } from "../Icon/IconNative"; import { Toggle } from "../Toggle/Toggle"; type ExpandableItemProps = { children?: ReactNode; summary?: ReactNode; className?: string; style?: CSSProperties; initiallyExpanded?: boolean; enabled?: boolean; iconExpanded?: string; iconCollapsed?: string; iconPosition?: "start" | "end"; withSwitch?: boolean; onExpandedChange?: (isExpanded: boolean) => void; registerComponentApi?: RegisterComponentApiFn; }; export const defaultExpandableItemProps: Pick< ExpandableItemProps, "initiallyExpanded" | "enabled" | "iconExpanded" | "iconCollapsed" | "iconPosition" | "withSwitch" > = { initiallyExpanded: false, enabled: true, iconExpanded: "chevrondown", iconCollapsed: "chevronright", iconPosition: "end", withSwitch: false, }; export const ExpandableItem = forwardRef(function ExpandableItem( { summary, children, className, style, initiallyExpanded = defaultExpandableItemProps.initiallyExpanded, enabled = defaultExpandableItemProps.enabled, iconExpanded = defaultExpandableItemProps.iconExpanded, iconCollapsed = defaultExpandableItemProps.iconCollapsed, iconPosition = defaultExpandableItemProps.iconPosition, withSwitch = defaultExpandableItemProps.withSwitch, onExpandedChange, registerComponentApi, ...rest }: ExpandableItemProps, ref, ) { const [isOpen, setIsOpen] = useState(initiallyExpanded); const generatedId = useId(); const summaryId = `${generatedId}-summary`; const contentId = `${generatedId}-content`; const toggleOpen = useCallback(() => { if (!enabled) return; const newValue = !isOpen; setIsOpen(newValue); onExpandedChange?.(newValue); }, [enabled, isOpen, onExpandedChange]); // Register component API const expand = useCallback(() => { if (!isOpen && enabled) { setIsOpen(true); onExpandedChange?.(true); } }, [enabled, isOpen, onExpandedChange]); const collapse = useCallback(() => { if (isOpen && enabled) { setIsOpen(false); onExpandedChange?.(false); } }, [enabled, isOpen, onExpandedChange]); const toggle = useCallback(() => { toggleOpen(); }, [toggleOpen]); const getIsExpanded = useCallback(() => isOpen, [isOpen]); // Handle switch value change const handleSwitchChange = useCallback( (value: boolean) => { if (!enabled) return; setIsOpen(value); onExpandedChange?.(value); }, [enabled, onExpandedChange], ); // Register these functions with the component API using useEffect useEffect(() => { registerComponentApi?.({ expand, collapse, toggle, isExpanded: getIsExpanded, }); }, [registerComponentApi, expand, collapse, toggle, getIsExpanded]); // Handler for clicking on the summary when using a switch const handleSummaryClick = useCallback(() => { if (!enabled || !withSwitch) return; const newValue = !isOpen; setIsOpen(newValue); onExpandedChange?.(newValue); }, [enabled, withSwitch, isOpen, onExpandedChange]); // Handle keyboard events for accessibility const handleKeyDown = useCallback((event: React.KeyboardEvent) => { if (!enabled) return; if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); if (withSwitch) { handleSwitchChange(!isOpen); } else { toggleOpen(); } } }, [enabled, withSwitch, isOpen, handleSwitchChange, toggleOpen]); return ( <div {...rest} className={classNames(styles.expandableItem, className, { [styles.open]: isOpen, [styles.disabled]: !enabled, [styles.withSwitch]: withSwitch, })} style={style} ref={ref as any} > <div className={classNames(styles.summary, { [styles.iconStart]: iconPosition === "start", [styles.iconEnd]: iconPosition === "end", })} onClick={enabled ? (withSwitch ? handleSummaryClick : toggleOpen) : undefined} onKeyDown={handleKeyDown} tabIndex={enabled ? 0 : undefined} role="button" aria-expanded={isOpen} aria-controls={contentId} aria-disabled={!enabled} id={summaryId} > <div className={withSwitch ? styles.switch : styles.icon} aria-hidden="true"> {withSwitch ? ( <Toggle variant="switch" value={isOpen} enabled={enabled} onDidChange={handleSwitchChange} /> ) : ( <Icon name={isOpen ? iconExpanded : iconCollapsed} fallback={isOpen ? "chevrondown" : "chevronright"} /> )} </div> <div className={styles.summaryContent}> {summary} </div> </div> {isOpen && ( <div className={styles.content} role="region" aria-labelledby={summaryId} id={contentId} > {children} </div> )} </div> ); }); ``` -------------------------------------------------------------------------------- /xmlui/tests/parsers/scripting/parser-regex.test.ts: -------------------------------------------------------------------------------- ```typescript import { describe, expect, it } from "vitest"; import { Parser } from "../../../src/parsers/scripting/Parser"; import { ConstStatement, Literal, T_CONST_STATEMENT, T_LITERAL, } from "../../../src/components-core/script-runner/ScriptingSourceTree"; describe("Parser - regex literals", () => { it("null", () => { // --- Arrange const wParser = new Parser("null"); // --- Act const expr = wParser.parseExpr(); // --- Assert expect(expr).not.equal(null); if (!expr) return; expect(expr.type).equal(T_LITERAL); }); const regExpCases = [ /\w+/g, /(-?\d*\.\d\w*)|([^`~!@#%^&*()\-=+\[{\]}\\|;:'",.<>\/?\s]+)/g, /^\s*\/\*\*(?!\/)([^\*]|\*(?!\/))*$/, /^\s*\*\/$/, /^(\t|(\ \ ))*\ \*(\ ([^\*]|\*(?!\/))*)?$/, /^(\t|(\ \ ))*\ \*\/\s*$/, /[=><!~?:&|+\-*\/\^%]+/, /\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/, /\d+(_+\d+)*/, /[0-7]+(_+[0-7]+)*/, /[0-1]+(_+[0-1]+)*/, /[[0-9a-fA-F]+(_+[0-9a-fA-F]+)*/, /[(){}\[\]\$\^|\-*+?\.]/, /\\(?:[bBdDfnrstvwWn0\\\/]|@regexpctl|c[A-Z]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4})/, /\d/, // Match any digit /\D/, // Match any non-digit character /\w/, // Match any word character /\W/, // Match any non-word character /\s/, // Match any whitespace character /\S/, // Match any non-whitespace character /a/, // Match a specific character /abc/, // Match a specific sequence of characters /./, // Match any character except newline /a*/, // Match zero or more occurrences of a character /a+/, // Match one or more occurrences of a character /a?/, // Match zero or one occurrence of a character /a{3}/, // Match a specific number of occurrences of a character /a{2,4}/, // Match a range of occurrences of a character /[abc]/, // Match any character in a character class /[^abc]/, // Match any character not in a character class /[a-z]/, // Match any character in a range /[^a-z]/, // Match any character not in a range /^/, // Match the start of a string /$/, // Match the end of a string /\b/, // Match the start of a word /\b\w/, // Match the end of a word /\B/, // Match a word boundary not at the start or end of a word /(abc)/, // Match a group of characters /(abc|def)/, // Match either of two patterns /.+?/, // Match any character except newline in a non-greedy way /.+/i, // Match any character except newline in a case-insensitive manner /a(?=b)/, // Match a character only if it is followed by another character /a(?!b)/, // Match a character only if it is not followed by another character /(?<=a)b/, // Match a character only if it is preceded by another character /(?<!a)b/, // Match a character only if it is not preceded by another character /\p{Sc}/u, // Match a character with a specific Unicode property /\P{Sc}/u, // Match a character without a specific Unicode property /\p{Script=Hiragana}/u, // Match a character with a specific Unicode script property /\P{Script=Hiragana}/u, // Match a character without a specific Unicode script property /\u{1F602}/u, // Match a Unicode character by its code point /\p{General_Category=Letter}/u, // Match a character with a specific Unicode property value /\P{General_Category=Letter}/u, // Match a character without a specific Unicode property value /[^abc]/i, // Match any character except those in a character class ignoring case /[^a-z]/i, // Match any character except those in a range ignoring case /\x41/, // Match a digit in hexadecimal format /\o101/, // Match a digit in octal format /\b01000001\b/, // Match a digit in binary format /\p{Dash}/u, // Match a character with a specific Unicode binary property value /\P{Dash}/u, // Match a character without a specific Unicode binary property value /\p{L}/u, // Match a character with a specific Unicode property value using shorthand /\P{L}/u, // Match a character without a specific Unicode property value using shorthand /[:digit:]/u, // Match a character with a specific Unicode property value using POSIX syntax /[^:digit:]/u, // Match a character without a specific Unicode property value using POSIX syntax ]; regExpCases.forEach((regExp) => { it(`RegExp: ${regExp}`, () => { const parser = new Parser(regExp.toString()); const result = parser.parseExpr()!; expect(result.type).toBe(T_LITERAL); const literal = result as Literal; expect(literal.value instanceof RegExp).equal(true); expect(literal.value).toStrictEqual(regExp); }); it(`RegExp in statement: ${regExp} #1`, () => { const parser = new Parser(`const a = ${regExp};`); const result = parser.parseStatements()!; expect(result.length).toBe(1); const stmt = result[0] as ConstStatement; expect(stmt.decls[0].expr!.type).equal(T_LITERAL); const literal = stmt.decls[0].expr as Literal; expect(literal.value instanceof RegExp).equal(true); expect(literal.value).toStrictEqual(regExp); }); it(`RegExp in statement: ${regExp} #2`, () => { const parser = new Parser(`const a = ''.match(${regExp}) + 120;`); const result = parser.parseStatements()!; expect(result.length).toBe(1); expect(result[0].type).toBe(T_CONST_STATEMENT); }); }); }); ``` -------------------------------------------------------------------------------- /xmlui/src/components/Pagination/Pagination.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); } // Define theme variables $backgroundColor-Pagination: createThemeVar("backgroundColor-Pagination"); $borderColor-Pagination: createThemeVar("borderColor-Pagination"); $borderRadius-selector-Pagination: createThemeVar("borderRadius-selector-Pagination"); $textColor-Pagination: createThemeVar("textColor-Pagination"); $backgroundColor-selector-Pagination: createThemeVar("backgroundColor-selector-Pagination"); $textColor-selector-Pagination: createThemeVar("textColor-selector-Pagination"); @layer components { .pagination { display: grid; align-items: center; padding: createThemeVar("padding-Pagination"); gap: t.$space-4; // Default 3-column layout grid-template-columns: 1fr 1fr 1fr; grid-template-areas: "a b c"; // Vertical layout (3 rows) &.paginationVertical { grid-template-columns: 1fr; grid-template-rows: auto auto auto; grid-template-areas: "a" "b" "c"; // 2-row layout when only start and end slots are present &:has(.startSlot):has(.endSlot):not(:has(.centerSlot)) { grid-template-rows: auto auto; grid-template-areas: "a" "c"; } // 1-row layout when only one slot is present &:has(.startSlot):not(:has(.centerSlot)):not(:has(.endSlot)) { grid-template-rows: auto; grid-template-areas: "a"; } &:has(.centerSlot):not(:has(.startSlot)):not(:has(.endSlot)) { grid-template-rows: auto; grid-template-areas: "b"; } &:has(.endSlot):not(:has(.startSlot)):not(:has(.centerSlot)) { grid-template-rows: auto; grid-template-areas: "c"; } // 2-row layout for start + center &:has(.startSlot):has(.centerSlot):not(:has(.endSlot)) { grid-template-rows: auto auto; grid-template-areas: "a" "b"; } // 2-row layout for center + end &:has(.centerSlot):has(.endSlot):not(:has(.startSlot)) { grid-template-rows: auto auto; grid-template-areas: "b" "c"; } } // Horizontal layout variations (keep existing) &:not(.paginationVertical) { // 2-column layout when only start and end slots are present &:has(.startSlot):has(.endSlot):not(:has(.centerSlot)) { grid-template-columns: 1fr 1fr; grid-template-areas: "a c"; } // 1-column layout when only one slot is present &:has(.startSlot):not(:has(.centerSlot)):not(:has(.endSlot)) { grid-template-columns: 1fr; grid-template-areas: "a"; } &:has(.centerSlot):not(:has(.startSlot)):not(:has(.endSlot)) { grid-template-columns: 1fr; grid-template-areas: "b"; } &:has(.endSlot):not(:has(.startSlot)):not(:has(.centerSlot)) { grid-template-columns: 1fr; grid-template-areas: "c"; } // 2-column layout for start + center &:has(.startSlot):has(.centerSlot):not(:has(.endSlot)) { grid-template-columns: 1fr 1fr; grid-template-areas: "a b"; } // 2-column layout for center + end &:has(.centerSlot):has(.endSlot):not(:has(.startSlot)) { grid-template-columns: 1fr 1fr; grid-template-areas: "b c"; } } } .slot { display: flex; flex-direction: row; align-items: center; gap: t.$space-6; flex-direction: row; .paginationVertical & { flex-direction: column; } } .startSlot { grid-area: a; justify-self: start; .paginationVertical & { justify-self: stretch; } } .centerSlot { grid-area: b; justify-self: center; .paginationVertical & { justify-self: stretch; } } .endSlot { grid-area: c; justify-self: end; .paginationVertical & { justify-self: stretch; } } .selectorContainer { display: flex; flex-direction: row; align-items: center; gap: t.$space-2; } .pageSizeLabel { color: $textColor-selector-Pagination; font-size: t.$fontSize-sm; } .pageSizeSelect { color: $textColor-selector-Pagination; background-color: $backgroundColor-selector-Pagination; border: 1px solid $borderColor-Pagination; border-radius: $borderRadius-selector-Pagination; padding: t.$space-1 t.$space-2; font-size: t.$fontSize-sm; .paginationVertical & { align-self: center; } &:focus { //outline: 2px solid t.$color-primary-400; outline-offset: 2px; } &:not([disabled]):hover { cursor: pointer; } &:disabled { opacity: 0.5; cursor: not-allowed; } } .buttonRow { list-style: none; padding: 0; display: flex; flex-direction: row; align-items: center; gap: createThemeVar("gap-buttonRow-Pagination"); li { margin: 0; } &.paginationListVertical { flex-direction: column; } } } // --- We export the theme variables to add them to the component renderer :export { themeVars: t.json-stringify($themeVars); } ``` -------------------------------------------------------------------------------- /xmlui/src/components/Charts/BarChart/BarChart.tsx: -------------------------------------------------------------------------------- ```typescript import { BarChart, defaultProps } from "./BarChartNative"; import { createComponentRenderer } from "../../../components-core/renderers"; import { createMetadata } from "../../metadata-helpers"; import { MemoizedItem } from "../../container-helpers"; const COMP = "BarChart"; export const BarChartMd = createMetadata({ status: "experimental", description: "`BarChart` displays data as horizontal or vertical bars, supporting both grouped " + "and stacked layouts. It's ideal for comparing values across categories, showing " + "revenue trends, or displaying any quantitative data over time or categories.", docFolder: "Charts/BarChart", props: { data: { description: `This property is used to provide the component with data to display.` + `The data needs to be an array of objects.`, }, yKeys: { description: "Specifies the key in the data objects that will be used to label the different data series.", valueType: "string", }, stacked: { description: `This property determines how the bars are laid out.` + `If set to \`true\`, bars with the same category will be stacked on top of each other rather than placed side by side.`, valueType: "boolean", defaultValue: defaultProps.stacked, }, orientation: { description: `This property determines the orientation of the bar chart. The \`vertical\` variant ` + `specifies the horizontal axis as the primary and lays out the bars from left to right. ` + `The \`horizontal\` variant specifies the vertical axis as the primary and has the bars ` + `spread from top to bottom.`, valueType: "string", availableValues: ["horizontal", "vertical"], defaultValue: defaultProps.layout, }, xKey: { description: "This property specifies the keys in the data objects that should be used for rendering the bars." + `E.g. 'id' or 'key'.`, valueType: "string", }, hideX: { description: "Determines whether the X-axis should be hidden. If set to `true`, the axis will not be rendered.", valueType: "boolean", defaultValue: defaultProps.hideX, }, hideY: { description: "Determines whether the Y-axis should be hidden. If set to `true`, the axis will not be rendered.", valueType: "boolean", defaultValue: defaultProps.hideY, }, hideTickX: { description: "Controls the visibility of the X-axis ticks. If set to `true`, tick labels on the X-axis will be hidden.", valueType: "boolean", defaultValue: defaultProps.hideTickX, }, hideTickY: { description: "Controls the visibility of the Y-axis ticks. If set to `true`, tick labels on the Y-axis will be hidden.", valueType: "boolean", defaultValue: defaultProps.hideTickY, }, tickFormatterX: { description: "A function that formats the tick labels on the X-axis. ", defaultValue: JSON.stringify(defaultProps.tickFormatterX), }, tickFormatterY: { description: "A function that formats the tick labels on the Y-axis. ", defaultValue: JSON.stringify(defaultProps.tickFormatterY), }, hideTooltip: { description: "Determines whether the tooltip should be hidden. If set to `true`, tooltips will not appear on hover.", valueType: "boolean", defaultValue: defaultProps.hideTooltip, }, showLegend: { description: "Determines whether the legend should be displayed.", valueType: "boolean", defaultValue: defaultProps.showLegend, }, tooltipTemplate: { description: "This property allows replacing the default template to display a tooltip.", }, }, }); export const barChartComponentRenderer = createComponentRenderer( COMP, BarChartMd, ({ extractValue, node, className, lookupSyncCallback, renderChild }: any) => { return ( <BarChart className={className} tickFormatterX={lookupSyncCallback(node.props?.tickFormatterX)} tickFormatterY={lookupSyncCallback(node.props?.tickFormatterY)} data={extractValue(node.props?.data)} layout={extractValue(node.props?.orientation)} nameKey={extractValue(node.props?.xKey)} dataKeys={extractValue(node.props?.yKeys)} stacked={extractValue.asOptionalBoolean(node.props?.stacked)} hideX={extractValue.asOptionalBoolean(node.props?.hideX)} hideY={extractValue.asOptionalBoolean(node.props?.hideY)} hideTickX={extractValue.asOptionalBoolean(node.props?.hideTickX)} hideTickY={extractValue.asOptionalBoolean(node.props?.hideTickY)} hideTooltip={extractValue.asOptionalBoolean(node.props?.hideTooltip)} showLegend={extractValue.asOptionalBoolean(node.props?.showLegend)} tooltipRenderer={ node.props.tooltipTemplate ? (tooltipData) => { return ( <MemoizedItem node={node.props.tooltipTemplate} item={tooltipData} contextVars={{ $tooltip: tooltipData, }} renderChild={renderChild} /> ); } : undefined } > {renderChild(node.children)} </BarChart> ); }, ); ``` -------------------------------------------------------------------------------- /xmlui/src/components/TextArea/TextArea.tsx: -------------------------------------------------------------------------------- ```typescript import styles from "./TextArea.module.scss"; import { type PropertyValueDescription } from "../../abstractions/ComponentDefs"; import { createComponentRenderer } from "../../components-core/renderers"; import { parseScssVar } from "../../components-core/theming/themeVars"; import { createMetadata, d, dAutoFocus, dDidChange, dEnabled, dGotFocus, dInitialValue, dLostFocus, dMaxLength, dPlaceholder, dReadonly, dRequired, dSetValueApi, dValidationStatus, } from "../metadata-helpers"; import { type ResizeOptions, TextArea, defaultProps } from "./TextAreaNative"; const COMP = "TextArea"; export const resizeOptionsMd: PropertyValueDescription[] = [ { value: "(undefined)", description: "No resizing" }, { value: "horizontal", description: "Can only resize horizontally" }, { value: "vertical", description: "Can only resize vertically" }, { value: "both", description: "Can resize in both dimensions" }, ]; export const TextAreaMd = createMetadata({ status: "stable", description: "`TextArea` provides a multiline text input area.", parts: { label: { description: "The label displayed for the text area.", }, startAdornment: { description: "The adornment displayed at the start of the text area.", }, endAdornment: { description: "The adornment displayed at the end of the text area.", }, input: { description: "The text area input.", }, }, props: { enterSubmits: { description: "This optional boolean property indicates whether pressing the \`Enter\` key on the " + "keyboard prompts the parent \`Form\` component to submit.", valueType: "boolean", defaultValue: defaultProps.enterSubmits, }, escResets: { description: `This boolean property indicates whether the ${COMP} contents should be reset when pressing ` + `the ESC key.`, valueType: "boolean", defaultValue: false, }, maxRows: d( `This optional property sets the maximum number of text rows the \`${COMP}\` ` + "can grow. If not set, the number of rows is unlimited.", ), minRows: d( `This optional property sets the minimum number of text rows the \`${COMP}\` can shrink. ` + `If not set, the minimum number of rows is 1.`, ), rows: { description: `Specifies the number of rows the component initially has.`, valueType: "number", defaultValue: defaultProps.rows, }, autoSize: { description: `If set to \`true\`, this boolean property enables the \`${COMP}\` to resize ` + `automatically based on the number of lines inside it.`, valueType: "boolean", defaultValue: false, }, placeholder: dPlaceholder(), initialValue: dInitialValue(), maxLength: dMaxLength(), autoFocus: dAutoFocus(), required: dRequired(), readOnly: dReadonly(), enabled: dEnabled(), validationStatus: dValidationStatus(), resize: { description: `This optional property specifies in which dimensions can the \`TextArea\` ` + `be resized by the user.`, availableValues: resizeOptionsMd, }, }, events: { gotFocus: dGotFocus(COMP), lostFocus: dLostFocus(COMP), didChange: dDidChange(COMP), }, apis: { focus: { description: `This method sets the focus on 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(): string | undefined", }, setValue: dSetValueApi(), }, themeVars: parseScssVar(styles.themeVars), defaultThemeVars: { [`paddingVertical-${COMP}`]: "$space-2", [`paddingHorizontal-${COMP}`]: "$space-2", }, }); export const textAreaComponentRenderer = createComponentRenderer( COMP, TextAreaMd, ({ node, extractValue, state, updateState, className, registerComponentApi, lookupEventHandler, }) => { return ( <TextArea value={state?.value} initialValue={extractValue(node.props.initialValue)} updateState={updateState} autoFocus={extractValue.asOptionalBoolean(node.props.autoFocus)} enabled={extractValue.asOptionalBoolean(node.props.enabled)} placeholder={extractValue(node.props.placeholder)} onDidChange={lookupEventHandler("didChange")} onFocus={lookupEventHandler("gotFocus")} onBlur={lookupEventHandler("lostFocus")} readOnly={extractValue.asOptionalBoolean(node.props.readOnly)} resize={node.props.resize as ResizeOptions} enterSubmits={extractValue.asOptionalBoolean(node.props.enterSubmits)} escResets={extractValue.asOptionalBoolean(node.props.escResets)} className={className} registerComponentApi={registerComponentApi} maxRows={extractValue.asOptionalNumber(node.props.maxRows)} minRows={extractValue.asOptionalNumber(node.props.minRows)} maxLength={extractValue.asOptionalNumber(node.props.maxLength)} rows={extractValue.asOptionalNumber(node.props.rows)} autoSize={extractValue.asOptionalBoolean(node.props.autoSize)} validationStatus={extractValue(node.props.validationStatus)} required={extractValue.asOptionalBoolean(node.props.required)} /> ); }, ); ``` -------------------------------------------------------------------------------- /xmlui/src/components/CodeBlock/CodeBlockNative.tsx: -------------------------------------------------------------------------------- ```typescript import type React from "react"; import styles from "./CodeBlock.module.scss"; import { Text } from "../Text/TextNative"; import { type CodeHighlighterMeta, CodeHighlighterMetaKeys, encodeToBase64, } from "./highlight-code"; import { Button } from "../Button/ButtonNative"; import Icon from "../Icon/IconNative"; import toast from "react-hot-toast"; import { visit } from "unist-util-visit"; import type { Node, Parent } from "unist"; import type { CSSProperties } from "react"; import classnames from "classnames"; type CodeBlockProps = { children?: React.ReactNode; textToCopy?: string; meta?: CodeHighlighterMeta; style?: CSSProperties; className?: string; }; export const defaultProps = { // No default props needed for this component currently }; export function CodeBlock({ children, meta, textToCopy, style, className, ...rest }: CodeBlockProps) { // 'global-codeBlock' class is there so we could apply styles if this codeblock is used inside a splitView nested app if (!meta) { return ( <div {...rest} className={classnames(className, styles.codeBlock, "global-codeBlock")} style={style} > <div className={styles.codeBlockContent}>{children}</div> </div> ); } return ( <div className={classnames(styles.codeBlock, "global-codeBlock")} style={style}> {meta.filename && ( <div className={styles.codeBlockHeader}> <Text variant="em">{meta.filename}</Text> </div> )} <div className={styles.codeBlockContent}> {children} {meta.copy !== false && ( <div className={styles.codeBlockCopyButton}> <Button variant="ghost" size="xs" className={styles.copyButton} icon={<Icon name={"copy"} aria-hidden />} onClick={() => { if (!textToCopy) return; void navigator.clipboard.writeText(textToCopy); toast.success("Code copied!"); }} ></Button> </div> )} </div> </div> ); } interface CodeNode extends Node { lang: string | null; meta: string | null; } export function markdownCodeBlockParser() { return function transformer(tree: Node) { visit(tree, "code", visitor); }; /** * This visitor visits each node in the mdast tree and adds all meta information to the code block html element * that we use later in the Markdown component. */ function visitor(node: CodeNode, _: number, parent: Parent | undefined) { const { lang, meta } = node; const nodeData = { hProperties: {} }; if (lang !== null) { nodeData.hProperties["dataLanguage"] = lang; node.data = nodeData; } if (!parent) return; if (!meta) return; const params = splitter(meta) ?.filter((s) => s !== "") .map((s) => s.trim()); if (!params) return; if (params.length === 0) return; const parsedMeta = params.reduce( (acc, item) => { item = item.trim(); if (item === "") return acc; if (item.toLocaleLowerCase().startsWith("filename=")) { const index = item.indexOf("="); acc[CodeHighlighterMetaKeys.filename.data] = item .substring(index + 1) .replace(/"(.+)"/, "$1") .replace(/'(.+)'/, "$1"); } if (item.startsWith("/") && item.endsWith("/")) { const unparsedSubstrings = acc[CodeHighlighterMetaKeys.highlightSubstrings.data]; const newItemBase64 = encodeToBase64(item.substring(1, item.length - 1)); if (!unparsedSubstrings) { acc[CodeHighlighterMetaKeys.highlightSubstrings.data] = newItemBase64; } else { acc[CodeHighlighterMetaKeys.highlightSubstrings.data] = `${unparsedSubstrings} ${newItemBase64}`; } } if (item.startsWith("!/") && item.endsWith("/")) { const unparsedSubstrings = acc[CodeHighlighterMetaKeys.highlightSubstringsEmphasized.data]; const newItemBase64 = encodeToBase64(item.substring(2, item.length - 1)); if (!unparsedSubstrings) { acc[CodeHighlighterMetaKeys.highlightSubstringsEmphasized.data] = newItemBase64; } else { acc[CodeHighlighterMetaKeys.highlightSubstringsEmphasized.data] = `${unparsedSubstrings} ${newItemBase64}`; } } if (item.startsWith("{") && item.endsWith("}")) { const unparsedRows = acc[CodeHighlighterMetaKeys.highlightRows.data]; const newItem = item.substring(1, item.length - 1); if (!unparsedRows) { acc[CodeHighlighterMetaKeys.highlightRows.data] = newItem; } else { acc[CodeHighlighterMetaKeys.highlightRows.data] = `${unparsedRows}, ${newItem}`; } } if (item === "copy") { acc[CodeHighlighterMetaKeys.copy.data] = "true"; } if (item === "rowNumbers") { acc[CodeHighlighterMetaKeys.rowNumbers.data] = "true"; } return acc; }, {} as Record<string, string>, ); nodeData.hProperties = { ...nodeData.hProperties, ...parsedMeta }; node.data = nodeData; } function splitter(str: string): string[] | null { return str.match(/(?:("|')[^"']*\1|\{[^{}]*\}|\/.*?\/|[^\s"'{}/])+/g); } } ``` -------------------------------------------------------------------------------- /xmlui/src/components/AppState/AppState.md: -------------------------------------------------------------------------------- ```markdown %-DESC-START **Key advantages over variables:** - **Global accessibility**: Any component can access the state by referencing the same `bucket` - **Automatic reactivity**: UI updates automatically when state changes, no manual prop passing required - **Cross-component coordination**: Perfect for user sessions, UI preferences, loading states, and shared data ## Using AppState Variables in xmlui are a straightforward tool for managing states. However, a variable's scope is the app's main file or the particular component file in which it is declared. To access the variable's value (the stored state), you must pass its value to components wanting to leverage it. ### Storing State in Variables In the following example, the main file of the app declares a variable, `enhancedMode`, which is toggled with a checkbox: ```xmlui-pg ---app copy display filename="Main.xmlui" <App var.enhancedMode="{false}"> <VStack gap="$space-4" padding="$space-4"> <Checkbox label="Enhanced mode" initialValue="{enhancedMode}" onDidChange="v => enhancedMode = v" /> <Component1 enhancedMode="{enhancedMode}" /> <Component2 enhancedMode="{enhancedMode}" /> </VStack> </App> ---desc Two components, `Component1` and `Component2`, use the value of `enhancedMode`. Because of the aforementioned scoping issue, the app must explicitly pass the variable's value to those components so that they can use it. These components utilize the value to render their UI: ---comp copy display filename="Component1.xmlui" <Component name="Component1"> <H3 when="{$props.enhancedMode}">I am in enhanced mode!</H3> <Text when="{!$props.enhancedMode}">Enhanced mode turned off.</Text> </Component> ---desc When you define an `AppState`, you can set its `initialValue` property to initialize the state value. ---comp copy display filename="Component2.xmlui" <Component name="Component2"> <Button enabled="{$props.enhancedMode}">Set enhanced options</Button> </Component> ---desc You can try how this app works: ``` ### Storing State in AppState What if `Component1` and `Component2` had nested components using `enhancedMode`? You must also pass them to the nested components (via properties). What if you have not only one but a dozen of similar properties and a long chain of nested components? The "use a variable" pattern soon becomes a state management nightmare. If the nested components want to change the state value, you must declare events and event handlers or component APIs to manage the state. It sounds pretty tedious! This situation is where `AppState` comes into the picture. With an `AppState` instance, you can define a state object that automatically conveys between parent and nested child component chains implicitly. Let's turn the previous example into one using `AppState`! The following code shows how we change the main app file: ```xmlui-pg ---app copy display filename="Main.xmlui" <App> <AppState id="appState" initialValue="{{ enhancedMode: false }}"/> <VStack gap="$space-4" padding="$space-4"> <Checkbox label="Enhanced mode" initialValue="{appState.value.enhancedMode}" onDidChange="v => appState.update({ enhancedMode: v})" /> <Component1 /> <Component2 /> </VStack> </App> ---desc When you define an `AppState`, you can set its `initialValue` property to initialize the state value. You must give an ID to the `AppState` instance to access it and use the `value` property to get the state. You must invoke the `AppState`'s `update` method when you intend to update the state. The components may use their own `AppState` object to access the state value: ---comp copy display filename="Component1.xmlui" <Component name="Component1"> <AppState id="state" /> <H3 when="{state.value.enhancedMode}">I am in enhanced mode!</H3> <Text when="{!state.value.enhancedMode}">Enhanced mode turned off.</Text> </Component> ---comp copy display filename="Component2.xmlui" <Component name="Component2"> <AppState id="state" /> <Button enabled="{state.value.enhancedMode}">Set enhanced options</Button> </Component> ---desc The modified app works the same way as the previous one (using variables): ``` ### State Buckets With the `AppState` component, you can use separate state objects. The `bucket` property of `AppState` is an identifier (using the "default" string by default). While multiple `AppState` objects use the same `bucket` property value, they refer to the same state object. If you want to run the sample with explicit state buckets (for example, with the `settings` bucket id), you should change the `AppState` declarations accordingly: ```xmlui /bucket="settings"/ <!-- Main.xmlui --> <AppState id="appState" bucket="settings" initialValue="{{ enhancedMode: false }}"/> <!-- Component1 --> <AppState id="state" bucket="settings" /> <!-- Component2 --> <AppState id="state" bucket="settings" /> ``` %-DESC-END %-API-START update If the argument is a hash object, it will be merged with the previous state value. Let's assume the previous state value was the following: ```json { "enhancedMode": false, "showHeader": true, "showFooter": true, "theme": "light" } ``` Now, update the state with this call: ```js appState.update({ enhancedMode: true, theme: "dark" }); ``` The new state value will be: ```json { "enhancedMode": true, "showHeader": true, "showFooter": true, "theme": "dark" } ``` %-API-END ``` -------------------------------------------------------------------------------- /xmlui/src/parsers/xmlui-parser/diagnostics.ts: -------------------------------------------------------------------------------- ```typescript export interface GeneralDiagnosticMessage { code: ErrCodes; category: DiagnosticCategory; message: string; } export enum DiagnosticCategory { Error = 1, Warning = 2, Information = 3, Hint = 4, } export enum ErrCodes { onlyOneElem = "U002", expTagOpen = "U003", expTagName = "U004", expCloseStart = "U005", expEndOrClose = "U006", tagNameMismatch = "U007", expEnd = "U008", expAttrName = "U009", expEq = "U010", expAttrValue = "U011", duplAttr = "U012", uppercaseAttr = "U013", expTagNameAfterNamespace = "U014", expCloseStartWithName = "U015", expAttrNameAfterNamespace = "U016", unexpectedCloseTag = "U017", expTagNameAfterCloseStart = "U019", expAttrNameBeforeEq = "U020", invalidChar = "W001", untermStr = "W002", untermComment = "W007", untermCData = "W008", untermScript = "W009", } export const DIAGS = { unexpectedCloseTag: { category: DiagnosticCategory.Error, code: ErrCodes.unexpectedCloseTag, message: "Read '</', but there's no opening tag to close.", }, expCloseStartWithName: function (openTagName: string) { return { category: DiagnosticCategory.Error, code: ErrCodes.expCloseStartWithName, message: `Opened tag has no closing pair. Expected to see '</${openTagName}>'.`, }; }, expCloseStart: { category: DiagnosticCategory.Error, code: ErrCodes.expCloseStart, message: "A '</' token expected.", }, uppercaseAttr: function (attrName: string) { return { category: DiagnosticCategory.Error, code: ErrCodes.uppercaseAttr, message: `Attribute name '${attrName}' cannot start with an uppercase letter.`, }; }, duplAttr: function (attrName: string) { return { category: DiagnosticCategory.Error, code: ErrCodes.duplAttr, message: `Duplicated attribute: '${attrName}'.`, }; }, tagNameMismatch: function (openTagName: string, closeTagName: string) { return { category: DiagnosticCategory.Error, code: ErrCodes.tagNameMismatch, message: `Opening and closing tag names should match. Opening tag has a name '${openTagName}', but the closing tag name is '${closeTagName}'.`, }; }, invalidChar: function (char: string) { return { category: DiagnosticCategory.Error, code: ErrCodes.invalidChar, message: `Invalid character '${char}'.`, }; }, expEnd: { category: DiagnosticCategory.Error, code: ErrCodes.expEnd, message: "A '>' token expected.", }, expTagName: { category: DiagnosticCategory.Error, code: ErrCodes.expTagName, message: "A tag name expected.", }, expAttrStr: { category: DiagnosticCategory.Error, code: ErrCodes.expAttrValue, message: `A string expected as an attribute value after '='.`, }, expEq: { category: DiagnosticCategory.Error, code: ErrCodes.expEq, message: "An '=' token expected.", }, expTagOpen: { category: DiagnosticCategory.Error, code: ErrCodes.expTagOpen, message: "A '<' token expected.", }, expEndOrClose: { category: DiagnosticCategory.Error, code: ErrCodes.expEndOrClose, message: `A '>' or '/>' token expected.`, }, expAttrName: { category: DiagnosticCategory.Error, code: ErrCodes.expAttrName, message: `An attribute name expected.`, }, expAttrNameAfterNamespace: function (namespaceName: string) { return { category: DiagnosticCategory.Error, code: ErrCodes.expAttrNameAfterNamespace, message: `An attribute name expected after namespace '${namespaceName}'.`, }; }, expTagNameAfterNamespace: function (namespaceName: string) { return { category: DiagnosticCategory.Error, code: ErrCodes.expTagNameAfterNamespace, message: `A tag name expected after namespace '${namespaceName}'.`, }; }, expTagNameAfterCloseStart: { category: DiagnosticCategory.Error, code: ErrCodes.expTagNameAfterCloseStart, message: "Expected tag name after '</'.", }, expAttrNameBeforeEq: { category: DiagnosticCategory.Error, code: ErrCodes.expAttrNameBeforeEq, message: "Expected attribute name before '='.", }, } as const; export function diagnosticCategoryName( d: { category: DiagnosticCategory }, lowerCase = true, ): string { const name = DiagnosticCategory[d.category]; return lowerCase ? name.toLowerCase() : name; } export const Diag_Invalid_Character = { code: ErrCodes.invalidChar, category: DiagnosticCategory.Error, message: "Invalid character.", } as const; export const Diag_Unterminated_String_Literal = { code: ErrCodes.untermStr, category: DiagnosticCategory.Error, message: "Unterminated string literal.", } as const; export const Diag_Unterminated_Comment = { code: ErrCodes.untermComment, category: DiagnosticCategory.Error, message: "Unterminated comment", } as const; export const Diag_Unterminated_CData = { code: ErrCodes.untermCData, category: DiagnosticCategory.Error, message: "Unterminated CDATA section", } as const; export const Diag_Unterminated_Script = { code: ErrCodes.untermScript, category: DiagnosticCategory.Error, message: "Unterminated script section", } as const; export type ScannerDiagnosticMessage = | typeof Diag_Invalid_Character | typeof Diag_Unterminated_String_Literal | typeof Diag_Unterminated_Comment | typeof Diag_Unterminated_CData | typeof Diag_Unterminated_Script; ``` -------------------------------------------------------------------------------- /xmlui/src/components/DataSource/DataSource.tsx: -------------------------------------------------------------------------------- ```typescript import { httpMethodNames } from "../abstractions"; import { createMetadata, d } from "../metadata-helpers"; // NOTE: Original component this is based on is the `Loader` component const COMP = "DataSource"; export const DataSourceMd = createMetadata({ status: "stable", description: "`DataSource` fetches and caches data from API endpoints, versus " + "[`APICall`](/components/APICall) which creates, updates or deletes data.", props: { method: { description: `Set the HTTP method.`, defaultValue: "get", availableValues: httpMethodNames, }, id: { description: `Set the ID used by other components to access the retrieved data in the \`value\`` + "property of a \`DataSource\`, or status info in the \`loaded\` and \`error\` properties." + "When no `id` is set, the component cannot be used programmatically.", isRequired: true, valueType: "string", }, url: { description: `Set the URL.`, isRequired: true, valueType: "string", }, body: { description: `Set the optional request body. The object you pass is serialized as a JSON string.`, valueType: "any", }, rawBody: { description: `Set the request body with no serialization. Use it to send a payload ` + `that has already been serialized to a JSON string.`, valueType: "string", }, queryParams: { description: `Append optional key-value pairs to the URL.`, valueType: "any", }, headers: { description: `Set request headers. Pass an object whose keys are header names and values are header values.`, valueType: "any", }, pollIntervalInSeconds: { description: "Set the interval for periodic data fetching. If the data changes on refresh, " + "XMLUI will re-render components that refer directly or indirectly to the \`DataSource\`. " + "If not set or set to zero, the component does not poll for data.", valueType: "number", }, inProgressNotificationMessage: { description: "Set the message to display when the data fetch is in progress. " + "If the property value is not set, no progress message is displayed.", valueType: "string", }, completedNotificationMessage: { description: "Set the message to display when the data fetch completes." + "If the property value is not set, no completion message is displayed.", valueType: "string", }, errorNotificationMessage: { description: "Set the message to display when the there is an error. " + "You can use the `$error` context value in an expression to " + "refer to the original error message.", valueType: "string", }, resultSelector: { description: "Set an optional object key to extract a subset of the response data. If this " + "value is not set, the entire response body is considered the result.", valueType: "string", }, transformResult: { description: "Set an optional function to perform a final transformation of the " + "response data. If this value is not set, the result is not transformed.", }, prevPageSelector: { description: `When using \`${COMP}\` with paging, the response may contain information about the ` + `previous and next page. This property defines the selector that extracts the ` + `previous page information from the response deserialized to an object.`, }, nextPageSelector: { description: `When using \`${COMP}\` with paging, the response may contain information about ` + `the previous and next page. This property defines the selector that extracts ` + `the next page information from the response deserialized to an object.`, }, structuralSharing: { description: "This property allows structural sharing. When turned on, `DataSource` will keep " + "the original reference if nothing has changed in the data. If a subset has " + "changed, `DataSource` will keep the unchanged parts and only replace the changed " + "parts. If you do not need this behavior, set this property to `false`.", defaultValue: "true", }, }, events: { loaded: d( "The component triggers this event when the fetch operation has been completed " + "and the data is loaded. The event has two arguments. The first is the data " + "loaded; the second indicates if the event is a result of a refetch.", ), error: d(`This event fires when a request results in an error.`), }, apis: { value: { description: `This property retrieves the data queried from the source after optional transformations.`, signature: "get value(): any", }, inProgress: { description: "This property indicates if the data is being fetched.", signature: "get inProgress(): boolean", }, isRefetching: { description: "This property indicates if the data is being re-fetched.", signature: "get isRefetching(): boolean", }, loaded: { description: "This property indicates if the data has been loaded.", signature: "get loaded(): boolean", }, refetch: { description: "This method requests the re-fetch of the data.", signature: "refetch(): void", }, }, }); ``` -------------------------------------------------------------------------------- /xmlui/src/index.ts: -------------------------------------------------------------------------------- ```typescript import type { StandaloneAppDescription, StandaloneJsonConfig, } from "./components-core/abstractions/standalone"; import type { ApiInterceptorDefinition } from "./components-core/interception/abstractions"; import StandaloneApp, { startApp } from "./components-core/StandaloneApp"; import type { ComponentDef, ComponentLike, ComponentMetadata, CompoundComponentDef, PropertyValueDescription, } from "./abstractions/ComponentDefs"; import { AppRoot } from "./components-core/rendering/AppRoot"; import { createComponentRenderer, createUserDefinedComponentRenderer, } from "./components-core/renderers"; import type { TreeNode } from "./components-core/abstractions/treeAbstractions"; import { Icon } from "./components/Icon/IconNative"; import { ErrorBoundary } from "./components-core/rendering/ErrorBoundary"; import { Stack } from "./components/Stack/StackNative"; import { Button } from "./components/Button/ButtonNative"; import { Splitter } from "./components/Splitter/SplitterNative"; import { useTheme, useThemes } from "./components-core/theming/ThemeContext"; import { toCssVar } from "./parsers/style-parser/StyleParser"; import { getColor } from "./components-core/utils/css-utils"; import { useColors } from "./components-core/utils/hooks"; import type { ComponentRendererDef, RegisterComponentApiFn, RendererContext, } from "./abstractions/RendererDefs"; import { parseScssVar } from "./components-core/theming/themeVars"; import StandaloneExtensionManager from "./components-core/StandaloneExtensionManager"; import type { ThemeDefinition, ThemeTone } from "./abstractions/ThemingDefs"; import { useDevTools } from "./components-core/InspectorContext"; import { useLogger } from "./logging/LoggerContext"; import { TabItemComponent } from "./components/Tabs/TabItemNative"; import { Tabs } from "./components/Tabs/TabsNative"; import { errReportComponent, xmlUiMarkupToComponent } from "./components-core/xmlui-parser"; import { ApiInterceptorProvider } from "./components-core/interception/ApiInterceptorProvider"; import { Spinner } from "./components/Spinner/SpinnerNative"; import type { XmlUiNode } from "./parsers/xmlui-parser"; import { XmlUiHelper } from "./parsers/xmlui-parser"; import { Text } from "./components/Text/TextNative"; import { TextBox } from "./components/TextBox/TextBoxNative"; import { NestedApp } from "./components/NestedApp/NestedAppNative"; import { builtInThemes } from "./components-core/theming/ThemeProvider"; import { VisuallyHidden } from "./components/VisuallyHidden"; import { LinkNative } from "./components/Link/LinkNative"; import { Breakout } from "./components/Breakout/BreakoutNative"; import { ToneChangerButton } from "./components/ToneChangerButton/ToneChangerButton"; import { Logo } from "./components/Logo/LogoNative"; import { Theme } from "./components/Theme/ThemeNative"; import { useSearchContextContent } from "./components/App/SearchContext"; import { useAppLayoutContext } from "./components/App/AppLayoutContext"; import { StyleProvider } from "./components-core/theming/StyleContext"; import { StyleRegistry } from "./components-core/theming/StyleRegistry"; import { useEvent } from "./components-core/utils/misc"; import { createMetadata, d, dComponent, dAutoFocus, dClick, dCollapse, dDidChange, dDidClose, dDidOpen, dEnabled, dFocus, dEndIcon, dEndText, dExpanded, dExpand, dGotFocus, dIndeterminate, dInitialValue, dInternal, dLabel, dLabelBreak, dLabelPosition, dLabelWidth, dLostFocus, dMaxLength, dMulti, dOrientation, dPlaceholder, dReadonly, dRequired, dStartIcon, dStartText, dSetValueApi, dTriggerTemplate, dValidationStatus, dValue, dValueApi, } from "./components/metadata-helpers"; import StandaloneComponent from "./components-core/rendering/StandaloneComponent"; export type { ThemeDefinition, ComponentDef, ComponentRendererDef, CompoundComponentDef, PropertyValueDescription, ComponentLike, StandaloneAppDescription, StandaloneJsonConfig, ApiInterceptorDefinition, RegisterComponentApiFn, //TODO review from here (added for playground) TreeNode, //TODO REMOVE RendererContext, ComponentMetadata, ThemeTone, XmlUiNode, }; export { StandaloneApp, StandaloneExtensionManager, createComponentRenderer, createUserDefinedComponentRenderer, createMetadata, d, dComponent, dAutoFocus, dClick, dCollapse, dDidChange, dDidClose, dDidOpen, dEnabled, dFocus, dEndIcon, dEndText, dExpanded, dExpand, dGotFocus, dIndeterminate, dInitialValue, dInternal, dLabel, dLabelBreak, dLabelPosition, dLabelWidth, dLostFocus, dMaxLength, dMulti, dOrientation, dPlaceholder, dReadonly, dRequired, dStartIcon, dStartText, dSetValueApi, dTriggerTemplate, dValidationStatus, dValue, dValueApi, parseScssVar, startApp, useTheme, AppRoot, ErrorBoundary, Icon, Stack, Button, Splitter, getColor, TabItemComponent as TabItem, Tabs, useColors, toCssVar, useDevTools, useLogger, errReportComponent, xmlUiMarkupToComponent, ApiInterceptorProvider, Spinner, useThemes, builtInThemes, XmlUiHelper, Text, TextBox, NestedApp, VisuallyHidden, LinkNative, ToneChangerButton, Logo, Breakout, useSearchContextContent, useAppLayoutContext, StyleProvider, StyleRegistry, useEvent, StandaloneComponent, Theme, }; ``` -------------------------------------------------------------------------------- /docs/content/components/Splitter.md: -------------------------------------------------------------------------------- ```markdown # Splitter [#splitter] `Splitter` component divides a container into two resizable sections. These are are identified by their names: primary and secondary. They have a draggable bar between them. Most properties of the component focus on the primary section (e.g. sizing). See also: [HSplitter](/components/HSplitter), [VSplitter](/components/VSplitter). ## Properties [#properties] ### `floating` (default: false) [#floating-default-false] Toggles whether the resizer is visible (`false`) or not (`true`) when not hovered or dragged. The default value is `false`, meaning the resizer is visible all the time. ```xmlui-pg copy display name="Example: floating" <App> <Splitter height="200px" floating="true"> <Stack backgroundColor="lightblue" height="100%" /> <Stack backgroundColor="darksalmon" height="100%" /> </Splitter> </App> ``` ### `initialPrimarySize` (default: "50%") [#initialprimarysize-default-50-] This optional number property sets the initial size of the primary section. The unit of the size value is in pixels or percentages. ```xmlui-pg copy display name="Example: initialPrimarySize" <App> <Splitter height="200px" initialPrimarySize="40%"> <Stack backgroundColor="lightblue" height="100%" /> <Stack backgroundColor="darksalmon" height="100%" /> </Splitter> </App> ``` ### `maxPrimarySize` (default: "100%") [#maxprimarysize-default-100-] This property sets the maximum size the primary section can have. The unit of the size value is in pixels or percentages. ```xmlui-pg copy display name="Example: maxPrimarySize" <App> <Splitter height="200px" maxPrimarySize="80%"> <Stack backgroundColor="lightblue" height="100%" /> <Stack backgroundColor="darksalmon" height="100%" /> </Splitter> </App> ``` ### `minPrimarySize` (default: "0%") [#minprimarysize-default-0-] This property sets the minimum size the primary section can have. The unit of the size value is in pixels or percentages. ```xmlui-pg copy display name="Example: minPrimarySize" <App> <Splitter height="200px" minPrimarySize="40px"> <Stack backgroundColor="lightblue" height="100%" /> <Stack backgroundColor="darksalmon" height="100%" /> </Splitter> </App> ``` ### `orientation` (default: "vertical") [#orientation-default-vertical] Sets whether the `Splitter` divides the container horizontally and lays out the section on top of each other (`vertical`), or vertically by placing the sections next to each other (`horizontal`). Available values: `horizontal`, `vertical` **(default)** ```xmlui-pg copy display name="Example: orientation" <App> <Splitter height="200px" orientation="horizontal"> <Stack backgroundColor="lightblue" height="100%" /> <Stack backgroundColor="darksalmon" height="100%" /> </Splitter> </App> ``` ### `splitterTemplate` [#splittertemplate] The divider can be customized using XMLUI components via this property. ```xmlui-pg copy {2-4} display name="Example: splitterTemplate" <App> <Splitter height="200px"> <property name="splitterTemplate"> <ContentSeparator backgroundColor="green" height="4px" /> </property> <Stack backgroundColor="lightblue" height="100%" /> <Stack backgroundColor="darksalmon" height="100%" /> </Splitter> </App> ``` ### `swapped` (default: false) [#swapped-default-false] This optional booelan property indicates whether the `Splitter` sections are layed out as primary and secondary (`false`) or secondary and primary (`true`) from left to right. ```xmlui-pg copy display name="Example: swapped" <App> <Splitter height="200px" swapped="true"> <Stack backgroundColor="lightblue" height="100%" /> <Stack backgroundColor="darksalmon" height="100%" /> </Splitter> </App> ``` ## Events [#events] ### `resize` [#resize] This event fires when the component is resized. ```xmlui-pg copy {2} display name="Example: resize" <App height="200px" var.counter="{0}"> <Splitter onResize="counter++"> <Stack backgroundColor="lightblue" height="100%"> <Text value="Resize event called {counter} number of times" /> </Stack> <Stack backgroundColor="darksalmon" height="100%" /> </Splitter> </App> ``` ## Exposed Methods [#exposed-methods] This component does not expose any methods. ## Styling [#styling] ### Theme Variables [#theme-variables] | Variable | Default Value (Light) | Default Value (Dark) | | --- | --- | --- | | [backgroundColor](../styles-and-themes/common-units/#color)-resizer-Splitter | $color-surface-100 | $color-surface-100 | | [backgroundColor](../styles-and-themes/common-units/#color)-Splitter | *none* | *none* | | [border](../styles-and-themes/common-units/#border)-Splitter | *none* | *none* | | [borderColor](../styles-and-themes/common-units/#color)-Splitter | *none* | *none* | | [borderRadius](../styles-and-themes/common-units/#border-rounding)-Splitter | *none* | *none* | | [borderStyle](../styles-and-themes/common-units/#border-style)-Splitter | *none* | *none* | | [borderWidth](../styles-and-themes/common-units/#size)-Splitter | *none* | *none* | | [boxShadow](../styles-and-themes/common-units/#boxShadow)-Splitter | *none* | *none* | | [cursor](../styles-and-themes/common-units/#cursor)-resizer-horizontal-Splitter | ew-resize | ew-resize | | [cursor](../styles-and-themes/common-units/#cursor)-resizer-vertical-Splitter | ns-resize | ns-resize | | [thickness](../styles-and-themes/common-units/#size)-resizer-Splitter | 5px | 5px | ``` -------------------------------------------------------------------------------- /docs/content/components/APICall.md: -------------------------------------------------------------------------------- ```markdown # APICall [#apicall] `APICall` creates, updates or deletes data on the backend, versus [`DataSource`](/components/DataSource) which fetches data. Unlike DataSource, APICall doesn't automatically execute - you must trigger it manually with the `execute()` method, typically from form submissions or button clicks. **Key characteristics:** - **Manual execution**: Call `execute()` method to trigger the API request - **Form integration**: Commonly used in `<event name="submit">` handlers for forms - **Parameter passing**: Pass data to the API call via `execute()` parameters - **Built-in notifications**: Supports automatic progress, success, and error messages **Context variables available during execution:** - `$error`: Error details (available in `errorNotificationMessage`) - `$param`: The first parameter passed to `execute()` method - `$params`: Array of all parameters passed to `execute()` method (access with `$params[0]`, `$params[1]`, etc.) - `$result`: Response data (available in `completedNotificationMessage`) ## Properties [#properties] ### `body` [#body] This optional property sets the request body. Use to pass an object that will be serialized as a JSON string. If you have an object that is already serialized as a JSON string, use `rawBody` instead. ### `completedNotificationMessage` [#completednotificationmessage] This property defines the message to display automatically when the operation has been completed. When this property is not defined, the completion does not display any message. This property customizes the success message displayed in a toast after the finished API invocation. The `$result` context variable can refer to the response body. For example, you can use the following code snippet to display the first 100 characters in the completed operation's response body: ```xmlui copy <APICall id="ds" method="post" url="/api/shopping-list" completedNotificationMessage="Result: {JSON.stringify($result).substring(0, 100)}" /> ``` ### `confirmButtonLabel` [#confirmbuttonlabel] This optional string property enables the customization of the submit button in the confirmation dialog that is displayed before the `APICall` is executed. ### `confirmMessage` [#confirmmessage] This optional string sets the message in the confirmation dialog that is displayed before the `APICall` is executed. ### `confirmTitle` [#confirmtitle] This optional string sets the title in the confirmation dialog that is displayed before the `APICall` is executed. ### `errorNotificationMessage` [#errornotificationmessage] This property defines the message to display automatically when the operation results in an error. You can use the `$error` context value in an expression to refer to the original error message. This property customizes the message displayed in a toast when the API invocation results in an error. The `$error.statusCode` context variable can refer to the response's status code, while `$error. details` to the response body. For example, you can use the following code snippet to display the status code and the details: ```xmlui copy <APICall id="ds" method="post" url="/api/shopping-list" errorNotificationMessage="${error.statusCode}, {JSON.stringify($error.details)}" /> ``` ### `headers` [#headers] You can optionally define request header values as key-value pairs, where the key is the ID of the particular header and the value is that header's corresponding value. ### `inProgressNotificationMessage` [#inprogressnotificationmessage] This property customizes the message that is displayed in a toast while the API operation is in progress. If not defined, no progress message is displayed. ### `method` (default: "get") [#method-default-get] The method of data manipulation can be done via setting this property. Available values: `get` **(default)**, `post`, `put`, `delete`, `patch`, `head`, `options`, `trace`, `connect` ### `queryParams` [#queryparams] This optional property sets the query parameters for the request. The object you pass here will be serialized to a query string and appended to the request URL. You can specify key and value pairs where the key is the name of a particular query parameter and the value is that parameter's value. ### `rawBody` [#rawbody] This optional property sets the request body to the value provided here without any conversion. Use the * `body` property if you want the object sent in JSON. When you define `body` and `rawBody`, the latest one prevails. ### `url` (required) [#url-required] Use this property to set the URL to which data will be sent. If not provided, an empty URL is used. ## Events [#events] ### `beforeRequest` [#beforerequest] This event fires before the request is sent. Returning an explicit boolean`false` value will prevent the request from being sent. ### `error` [#error] This event fires when a request results in an error. ### `success` [#success] This event fires when a request results in a success. ## Exposed Methods [#exposed-methods] ### `execute` [#execute] This method triggers the invocation of the API. You can pass an arbitrary number of parameters to the method. In the `APICall` instance, you can access those with the `$param` and `$params` context values. **Signature**: `execute(...params: any[])` - `params`: An arbitrary number of parameters that can be used in the API call. ## Styling [#styling] This component does not have any styles. ```