This is page 18 of 181. Use http://codebase.md/xmlui-org/xmlui/mockApiDef.js?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .changeset │ ├── config.json │ └── cool-queens-look.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 │ │ │ │ ├── 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 │ └── 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/tests/components-core/container/buildProxy.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { describe, expect, it } from "vitest"; 2 | import { buildProxy , ProxyCallbackArgs } from "../../../src/components-core/rendering/buildProxy"; 3 | 4 | describe("proxy", () => { 5 | it("buildProxy keeps proxied reference on get", async () => { 6 | const testObject = { 7 | name: "John Doe", 8 | address: { 9 | city: "Budapest", 10 | street: { 11 | kind: "road", 12 | name: "Main", 13 | number: 1, 14 | }, 15 | }, 16 | }; 17 | 18 | const proxyObject = buildProxy(testObject, () => {}); 19 | 20 | expect(proxyObject.address).equal(proxyObject.address); 21 | expect(proxyObject.address).eql({ 22 | city: "Budapest", 23 | street: { 24 | kind: "road", 25 | name: "Main", 26 | number: 1, 27 | }, 28 | }); 29 | expect(proxyObject.name).equal(proxyObject.name); 30 | expect(proxyObject.name).equal("John Doe"); 31 | expect(proxyObject.address.street).equal(proxyObject.address.street); 32 | expect(proxyObject.address.street).eql({ 33 | kind: "road", 34 | name: "Main", 35 | number: 1, 36 | }); 37 | }); 38 | 39 | it("buildProxy observes change #1", async () => { 40 | const testObject = { 41 | name: "John Doe", 42 | address: { 43 | city: "Budapest", 44 | street: { 45 | kind: "road", 46 | name: "Main", 47 | number: 1, 48 | }, 49 | }, 50 | }; 51 | 52 | const changes: ProxyCallbackArgs[] = []; 53 | const proxyObject = buildProxy(testObject, (change) => {changes.push(change)}); 54 | 55 | proxyObject.name = "Jane Doe"; 56 | 57 | expect(changes.length).equal(1); 58 | const change = changes[0]; 59 | expect(change.action).equal("set"); 60 | expect(change.path).equal("name"); 61 | expect(change.pathArray).eql(["name"]); 62 | expect(change.newValue).equal("Jane Doe"); 63 | expect(change.previousValue).equal("John Doe"); 64 | }); 65 | 66 | it("buildProxy observes change #2", async () => { 67 | const testObject = { 68 | name: "John Doe", 69 | address: { 70 | city: "Budapest", 71 | street: { 72 | kind: "road", 73 | name: "Main", 74 | number: 1, 75 | }, 76 | }, 77 | }; 78 | 79 | const changes: ProxyCallbackArgs[] = []; 80 | const proxyObject = buildProxy(testObject, (change) => {changes.push(change)}); 81 | 82 | proxyObject.address.city = "Dunakeszi"; 83 | 84 | expect(changes.length).equal(1); 85 | const change = changes[0]; 86 | expect(change.action).equal("set"); 87 | expect(change.path).equal("address.city"); 88 | expect(change.pathArray).eql(["address", "city"]); 89 | expect(change.newValue).equal("Dunakeszi"); 90 | expect(change.previousValue).equal("Budapest"); 91 | }); 92 | 93 | it("buildProxy observes change #3", async () => { 94 | const testObject = { 95 | name: "John Doe", 96 | address: { 97 | city: "Budapest", 98 | street: { 99 | kind: "road", 100 | name: "Main", 101 | number: 1, 102 | }, 103 | }, 104 | }; 105 | 106 | const changes: ProxyCallbackArgs[] = []; 107 | const proxyObject = buildProxy(testObject, (change) => {changes.push(change)}); 108 | 109 | proxyObject.address.street.name = "Kossuth"; 110 | 111 | expect(changes.length).equal(1); 112 | const change = changes[0]; 113 | expect(change.action).equal("set"); 114 | expect(change.path).equal("address.street.name"); 115 | expect(change.pathArray).eql(["address", "street", "name"]); 116 | expect(change.newValue).equal("Kossuth"); 117 | expect(change.previousValue).equal("Main"); 118 | }); 119 | }); 120 | ``` -------------------------------------------------------------------------------- /xmlui/src/components-core/renderers.ts: -------------------------------------------------------------------------------- ```typescript 1 | import type { 2 | ComponentDef, 3 | ComponentMetadata, 4 | } from "../abstractions/ComponentDefs"; 5 | import type { 6 | ComponentRendererFn, 7 | ComponentRendererDef, 8 | CompoundComponentRendererInfo, 9 | } from "../abstractions/RendererDefs"; 10 | import type { LoaderRenderer, LoaderRendererDef } from "./abstractions/LoaderRenderer"; 11 | 12 | /** 13 | * This helper function creates a component renderer definition from its arguments. 14 | * @param type The unique identifier of the component definition 15 | * @param renderer The function that renders the component definition into a React node 16 | * @param metadata Optional hints to help fix the rendering errors coming from invalid component property definitions 17 | * @returns The view renderer definition composed of the arguments 18 | */ 19 | export function createComponentRenderer<TMd extends ComponentMetadata>( 20 | type: string, 21 | metadata: TMd, 22 | renderer: ComponentRendererFn<ComponentDef<any>>, 23 | ): ComponentRendererDef { 24 | return { 25 | type, 26 | renderer, 27 | metadata, 28 | }; 29 | } 30 | 31 | /** 32 | * Create a non-visual component used for encapsulating property values 33 | * @param type Component type 34 | * @param metadata Component metadata 35 | */ 36 | export function createPropHolderComponent<TMd extends ComponentMetadata>( 37 | type: string, 38 | metadata?: TMd, 39 | ) { 40 | return createComponentRenderer(type, metadata, () => { 41 | throw new Error("Prop holder component, shouldn't render"); 42 | }); 43 | } 44 | 45 | /** 46 | * AppEngine uses React as its UI framework to declare and render loaders. This type declares a function that renders 47 | * a particular loader definition into its React representation. 48 | * 49 | * The type parameter of the function refers to the loader definition the function is about to render. 50 | * @param type The unique ID of the loader 51 | * @param renderer The function that renders the loader 52 | * @param hints Optional hints to help fix the rendering errors coming from invalid component property definitions 53 | */ 54 | export function createLoaderRenderer<TMd extends ComponentMetadata>( 55 | type: string, 56 | renderer: LoaderRenderer<TMd>, 57 | hints?: TMd, 58 | ): LoaderRendererDef { 59 | return { 60 | type, 61 | renderer, 62 | hints, 63 | }; 64 | } 65 | 66 | /** 67 | * This helper function creates a user defined component renderer definition from its arguments. 68 | * @param metadata The metadata of the user-defined component 69 | * @param componentMarkup The XMLUI markup that defines the user-defined component 70 | * @param codeBehind Optional code-behind script that contains variable and function definitions 71 | * used by the component 72 | * @returns The view renderer definition composed of the arguments 73 | */ 74 | export function createUserDefinedComponentRenderer<TMd extends ComponentMetadata>( 75 | metadata: TMd, 76 | def: any, 77 | codeBehind?: any, 78 | ): CompoundComponentRendererInfo { 79 | // --- Parse the component definition from the markup 80 | // --- Parse the optional code-behind script 81 | const component = def.component.component;; 82 | if (codeBehind) { 83 | if (codeBehind.vars) { 84 | component.vars ??= {}; 85 | component.vars = { ...component.vars, ...codeBehind.vars }; 86 | } 87 | if (codeBehind.functions) { 88 | component.functions ??= {}; 89 | component.functions = { 90 | ...component.functions, 91 | ...codeBehind.functions, 92 | }; 93 | } 94 | } 95 | 96 | // --- Done. 97 | return { 98 | compoundComponentDef: def.component, 99 | metadata, 100 | }; 101 | } 102 | ``` -------------------------------------------------------------------------------- /xmlui/src/components/NavPanel/NavPanel.module.scss: -------------------------------------------------------------------------------- ```scss 1 | @use "../../components-core/theming/themes" as t; 2 | 3 | // --- This code snippet is required to collect the theme variables used in this module 4 | $component: "NavPanel"; 5 | $themeVars: (); 6 | @function createThemeVar($componentVariable) { 7 | $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global; 8 | @return t.getThemeVar($themeVars, $componentVariable); 9 | } 10 | 11 | $themeVars: t.composeBorderVars($themeVars, $component); 12 | $backgroundColor-NavPanel: createThemeVar("backgroundColor-#{$component}"); 13 | $backgroundColor-NavPanel-horizontal: createThemeVar("backgroundColor-#{$component}-horizontal"); 14 | $boxShadow-NavPanel: createThemeVar("boxShadow-#{$component}"); 15 | $themeVars: t.composePaddingVars($themeVars, $component); 16 | $themeVars: t.composePaddingVars($themeVars, "logo-#{$component}"); 17 | $marginBottom-logo-NavPanel: createThemeVar("marginBottom-logo-#{$component}"); 18 | 19 | $maxWidth-content-NavPanel: createThemeVar("maxWidth-content-App"); 20 | 21 | $height-NavPanel: createThemeVar("height-AppHeader"); 22 | $paddingVertical-AppHeader: createThemeVar("paddingVertical-AppHeader"); 23 | $alignment-content-AppHeader: createThemeVar("alignment-content-AppHeader"); 24 | 25 | 26 | @layer components { 27 | .wrapper { 28 | --footer-height: 0; //temp solution, it's because of callbackDrive, settings stickyBox to the bottom (we'll have to introduce a smarter stickyBox) 29 | height: 100%; 30 | width: 100%; 31 | flex-shrink: 0; 32 | box-shadow: $boxShadow-NavPanel; 33 | @include t.paddingVars($themeVars, $component); 34 | @include t.borderVars($themeVars, $component); 35 | 36 | overflow: auto; 37 | position: relative; 38 | display: flex; 39 | flex-direction: column; 40 | scrollbar-width: thin; 41 | 42 | &:not(.condensed){ 43 | background-color: $backgroundColor-NavPanel; 44 | } 45 | 46 | &.vertical { 47 | scrollbar-gutter: stable; 48 | } 49 | 50 | &.horizontal{ 51 | box-shadow: none; 52 | height: $height-NavPanel; 53 | background-color: $backgroundColor-NavPanel-horizontal; 54 | 55 | &:not(.condensed){ 56 | .wrapperInner{ 57 | padding-inline: createThemeVar("paddingHorizontal-#{$component}"); 58 | justify-content: $alignment-content-AppHeader; 59 | } 60 | } 61 | .wrapperInner{ 62 | height: 100%; 63 | flex-direction: row; 64 | max-width: $maxWidth-content-NavPanel; 65 | //align-items: center; 66 | width: 100%; 67 | margin: 0 auto; 68 | justify-content: $alignment-content-AppHeader; 69 | } 70 | } 71 | } 72 | 73 | .wrapperInner{ 74 | display: flex; 75 | flex-direction: column; 76 | justify-content: $alignment-content-AppHeader; 77 | } 78 | 79 | 80 | .logoWrapper{ 81 | &:not(:empty) { 82 | display: flex; 83 | flex-shrink: 0; 84 | height: $height-NavPanel; 85 | padding-top: calc(#{createThemeVar("paddingVertical-logo-#{$component}")} + #{$paddingVertical-AppHeader}); 86 | padding-bottom: calc(#{createThemeVar("paddingVertical-logo-#{$component}")} + #{$paddingVertical-AppHeader}); 87 | padding-inline: createThemeVar("paddingHorizontal-logo-#{$component}"); 88 | margin-bottom: $marginBottom-logo-NavPanel; 89 | justify-content: createThemeVar("horizontalAlignment-logo-#{$component}"); 90 | } 91 | 92 | &.inDrawer { //to make room for the close button 93 | min-height: 32px; 94 | } 95 | } 96 | } 97 | 98 | // --- We export the theme variables to add them to the component renderer 99 | :export{ 100 | themeVars: t.json-stringify($themeVars) 101 | } ``` -------------------------------------------------------------------------------- /xmlui/src/components/ResponsiveBar/ResponsiveBar.spec.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { expect, test } from "../../testing/fixtures"; 2 | 3 | test.describe("ResponsiveBar", () => { 4 | test("renders children in horizontal layout", async ({ initTestBed, page }) => { 5 | await initTestBed(` 6 | <ResponsiveBar testId="responsive-bar"> 7 | <Button testId="btn1" label="Button 1" /> 8 | <Button testId="btn2" label="Button 2" /> 9 | <Button testId="btn3" label="Button 3" /> 10 | </ResponsiveBar> 11 | `); 12 | 13 | const responsiveBar = page.getByTestId("responsive-bar"); 14 | await expect(responsiveBar).toBeVisible(); 15 | 16 | // All buttons should be visible initially 17 | await expect(responsiveBar.locator('[data-testid="btn1"]').first()).toBeVisible(); 18 | await expect(responsiveBar.locator('[data-testid="btn2"]').first()).toBeVisible(); 19 | await expect(responsiveBar.locator('[data-testid="btn3"]').first()).toBeVisible(); 20 | }); 21 | 22 | test("moves overflowing items to dropdown when container is too narrow", async ({ initTestBed, page }) => { 23 | await initTestBed(` 24 | <ResponsiveBar testId="responsive-bar" style="width: 200px;"> 25 | <Button testId="btn1" label="Very Long Button 1" /> 26 | <Button testId="btn2" label="Very Long Button 2" /> 27 | <Button testId="btn3" label="Very Long Button 3" /> 28 | <Button testId="btn4" label="Very Long Button 4" /> 29 | </ResponsiveBar> 30 | `); 31 | 32 | const responsiveBar = page.getByTestId("responsive-bar"); 33 | await expect(responsiveBar).toBeVisible(); 34 | 35 | // Wait for the component to finish measuring and laying out 36 | await page.waitForTimeout(100); 37 | 38 | // Check if overflow dropdown is present 39 | const overflowDropdown = responsiveBar.locator(".overflowDropdown").first(); 40 | 41 | // If there's an overflow dropdown, some items should be moved there 42 | if (await overflowDropdown.isVisible()) { 43 | // Click the overflow button to see the dropdown menu 44 | const overflowTrigger = overflowDropdown.locator("svg, button").first(); 45 | await overflowTrigger.click(); 46 | 47 | // Wait for dropdown to appear 48 | await page.waitForTimeout(100); 49 | 50 | // There should be some menu items in the dropdown 51 | const menuItems = page.locator('[role="menuitem"]'); 52 | const menuItemCount = await menuItems.count(); 53 | expect(menuItemCount).toBeGreaterThan(0); 54 | } 55 | }); 56 | 57 | test("responds to container resize", async ({ initTestBed, page }) => { 58 | await initTestBed(` 59 | <ResponsiveBar testId="responsive-bar" style="width: 400px; border: 1px solid red;"> 60 | <Button testId="btn1" label="Button 1" /> 61 | <Button testId="btn2" label="Button 2" /> 62 | <Button testId="btn3" label="Button 3" /> 63 | <Button testId="btn4" label="Button 4" /> 64 | </ResponsiveBar> 65 | `); 66 | 67 | const responsiveBar = page.getByTestId("responsive-bar"); 68 | await expect(responsiveBar).toBeVisible(); 69 | 70 | // Initially, check if all buttons are visible or some are in overflow 71 | await page.waitForTimeout(100); 72 | 73 | // Check the current state 74 | const btn1Visible = await responsiveBar.locator('[data-testid="btn1"]').first().isVisible(); 75 | const btn4Visible = await responsiveBar.locator('[data-testid="btn4"]').first().isVisible(); 76 | 77 | // Verify the component is working - at least the first button should be visible 78 | expect(btn1Visible).toBe(true); 79 | }); 80 | }); 81 | ``` -------------------------------------------------------------------------------- /xmlui/dev-docs/next/index.md: -------------------------------------------------------------------------------- ```markdown 1 | # XMLUI Developer's Guide 2 | 3 | Welcome to the XMLUI developer's guide. This documentation covers the technical architecture, build processes, and development workflows for the entire XMLUI monorepo ecosystem. 4 | 5 | This guide is specifically for developers working on the XMLUI framework, its extensions, and related tools. For user-facing documentation and component guides, visit the main XMLUI documentation website. 6 | 7 | ## Getting Started 8 | 9 | **Prerequisites:** 10 | - **Node.js**: Version 18.12.0 or higher 11 | - **npm**: Version 10.9.2 or higher (comes with Node.js) 12 | - **Git**: For version control 13 | 14 | 1. **Fork and Clone the Repository** 15 | ```bash 16 | # Fork the repository on GitHub first, then clone your fork 17 | git clone https://github.com/YOUR_USERNAME/xmlui.git 18 | cd xmlui 19 | ``` 20 | 21 | 2. **Install Dependencies** 22 | ```bash 23 | # Install all workspace dependencies 24 | npm install 25 | ``` 26 | 27 | 3. **Build Documentation** 28 | ```bash 29 | # Build documentation site 30 | npm run build-docs 31 | ``` 32 | 33 | 4. **Build the Framework** 34 | ```bash 35 | # Build core framework and all extension packages 36 | npm run build-xmlui 37 | ``` 38 | 39 | 5. **Run Tests** (Optional) 40 | ```bash 41 | # Run the complete test suite 42 | npm run test-xmlui 43 | 44 | # Or run just smoke tests for faster feedback 45 | npm run test-xmlui-smoke 46 | ``` 47 | 48 | 6. **Start Development** 49 | ```bash 50 | # Start the documentation site with live reload 51 | cd docs 52 | npm start 53 | ``` 54 | 55 | ## Project Artifacts 56 | 57 | XMLUI is organized as a monorepo containing **12+ buildable artifacts** across multiple workspaces. Each workspace has its own `package.json` and produces specific build outputs: 58 | 59 | ### Core Framework 60 | - **`xmlui`** (`xmlui/`) - Main framework library with components, parsers, and CLI tools 61 | 62 | ### Extension Packages (7 packages) 63 | - **`xmlui-animations`** (`packages/xmlui-animations/`) - Animation components with React Spring integration 64 | - **`xmlui-devtools`** (`packages/xmlui-devtools/`) - Developer tools and debugging utilities 65 | - **`xmlui-os-frames`** (`packages/xmlui-os-frames/`) - OS-specific window frames and chrome 66 | - **`xmlui-pdf`** (`packages/xmlui-pdf/`) - PDF generation and display components 67 | - **`xmlui-playground`** (`packages/xmlui-playground/`) - Interactive code playground and editor 68 | - **`xmlui-search`** (`packages/xmlui-search/`) - Search and filtering components 69 | - **`xmlui-spreadsheet`** (`packages/xmlui-spreadsheet/`) - Spreadsheet and data grid components 70 | 71 | ### Applications & Tools 72 | - **`xmlui-e2e`** (`tests/`) - End-to-end test suite and test bed application 73 | - **`create-xmlui-app`** (`tools/create-app/`) - CLI tool for scaffolding new projects 74 | - **`xmlui-vscode`** (`tools/vscode/`) - VS Code extension for language support 75 | 76 | ### Documentation 77 | - **`xmlui-docs`** (`docs/`) - User documentation website with interactive playground 78 | 79 | ## Documentation Structure 80 | 81 | - [**Project Structure**](./project-structure.md) - Developer documentation for monorepo structure and build artifacts 82 | - [**Working with Code**](./working-with-code.md) - Development workflow and common tasks for contributing to XMLUI 83 | - [**Project Build**](./project-build.md) - Build system documentation using Turborepo for monorepo orchestration 84 | - [**Generate Component Reference Documentation**](./generating-component-reference.md) - How to generate and maintain component reference documentation 85 | ``` -------------------------------------------------------------------------------- /xmlui/dev-docs/next/theme-context.md: -------------------------------------------------------------------------------- ```markdown 1 | # ThemeContext - XMLUI's Theme Management System 2 | 3 | ## What is ThemeContext? 4 | 5 | ThemeContext is XMLUI's **React Context-based theming system** that provides centralized theme management throughout your application. It's like a global state manager specifically for themes and visual appearance. 6 | 7 | ## Two Main Hooks 8 | 9 | ### `useTheme()` - Current Theme Information 10 | 11 | ```typescript 12 | import { useTheme } from "../../components-core/theming/ThemeContext"; 13 | 14 | function MyComponent() { 15 | const theme = useTheme(); 16 | 17 | // Access current theme properties: 18 | console.log(theme.activeThemeTone); // "light" or "dark" 19 | console.log(theme.activeTheme); // Current theme object 20 | console.log(theme.getResourceUrl); // Function to get theme-specific resources 21 | } 22 | ``` 23 | 24 | ### `useThemes()` - Theme Control & Management 25 | 26 | ```typescript 27 | import { useThemes } from "../../components-core/theming/ThemeContext"; 28 | 29 | function ThemeController() { 30 | const { activeThemeTone, setActiveThemeTone, themes } = useThemes(); 31 | 32 | // Read current state: 33 | console.log('Current tone:', activeThemeTone); // "light" or "dark" 34 | 35 | // Change themes: 36 | setActiveThemeTone("dark"); // Switch to dark mode 37 | setActiveThemeTone("light"); // Switch to light mode 38 | 39 | // Access available themes: 40 | console.log('Available themes:', themes); 41 | } 42 | ``` 43 | 44 | ## Key Properties & Methods 45 | 46 | | Property | Type | Description | 47 | |----------|------|-------------| 48 | | `activeThemeTone` | `light` or `dark` | Current theme tone | 49 | | `setActiveThemeTone` | `function` | Function to change theme tone | 50 | | `activeTheme` | `object` | Current theme configuration | 51 | | `setActiveThemeId` | `function` | Function to change theme | 52 | | `themes` | `array` | Available theme definitions | 53 | | `getResourceUrl` | `function` | Get theme-specific resource URLs | 54 | 55 | ## How Components Use It 56 | 57 | ### Reading Theme State 58 | 59 | ```typescript 60 | // ToneSwitch uses it to sync with theme state 61 | const { activeThemeTone } = useThemes(); 62 | const isDarkMode = activeThemeTone === "dark"; 63 | ``` 64 | 65 | ### Controlling Themes 66 | 67 | ```typescript 68 | // ToneChangerButton uses it to toggle themes 69 | const { activeThemeTone, setActiveThemeTone } = useThemes(); 70 | const toggleTheme = () => { 71 | setActiveThemeTone(activeThemeTone === "light" ? "dark" : "light"); 72 | }; 73 | ``` 74 | 75 | ### Theme-Aware Styling 76 | 77 | ```typescript 78 | // AppHeader uses it for theme-specific logos 79 | const { activeThemeTone } = useTheme(); 80 | const logoUrl = useResourceUrl(`resource:logo-${activeThemeTone}`); 81 | ``` 82 | 83 | ## Provider Setup 84 | 85 | The ThemeContext is typically provided at the app level through the `<App>` component or `<Theme>` components, making theme state available to all child components. 86 | 87 | ## Benefits 88 | 89 | - **Centralized theme management** - Single source of truth 90 | - **Reactive updates** - Components auto-update when theme changes 91 | - **Type-safe** - TypeScript support for theme properties 92 | - **Resource management** - Theme-specific assets and URLs 93 | - **Cross-component sync** - All components stay in sync automatically 94 | 95 | A themed component doesn't need to manage its own state. It simply: 96 | 97 | 1. **Reads** the current theme from context 98 | 2. **Displays** the appropriate visual state 99 | 3. **Updates** the global theme when clicked 100 | 4. **Automatically re-renders** when theme changes elsewhere 101 | 102 | The ThemeContext is the "invisible glue" that makes XMLUI's theming system work seamlessly across all components. 103 | ``` -------------------------------------------------------------------------------- /xmlui/src/components/AppHeader/AppHeader.module.scss: -------------------------------------------------------------------------------- ```scss 1 | @use "../../components-core/theming/themes" as t; 2 | 3 | // --- This code snippet is required to collect the theme variables used in this module 4 | $component: "AppHeader"; 5 | $themeVars: (); 6 | @function createThemeVar($componentVariable) { 7 | $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global; 8 | @return t.getThemeVar($themeVars, $componentVariable); 9 | } 10 | 11 | // --- Theme vars for paddings and borders 12 | $themeVars: t.composePaddingVars($themeVars, $component); 13 | $themeVars: t.composePaddingVars($themeVars, "logo-#{$component}"); 14 | $themeVars: t.composeBorderVars($themeVars, $component); 15 | 16 | // --- Other 17 | $width-logo-AppHeader: createThemeVar("width-logo-#{$component}"); 18 | $alignment-content-AppHeader: createThemeVar("alignment-content-#{$component}"); 19 | 20 | 21 | @layer components { 22 | .header { 23 | position: relative; 24 | height: createThemeVar("height-#{$component}"); 25 | box-sizing: content-box; 26 | background-color: createThemeVar("backgroundColor-#{$component}"); 27 | @include t.borderVars($themeVars, $component); 28 | } 29 | 30 | .headerInner { 31 | height: 100%; 32 | flex: 1; 33 | gap: t.$space-4; 34 | flex-shrink: 0; 35 | display: flex; 36 | flex-direction: row; 37 | align-items: center; 38 | max-width: createThemeVar("maxWidth-content-#{$component}"); 39 | padding-inline: t.getThemeVar($themeVars, "paddingHorizontal-#{$component}"); 40 | @include t.paddingVars($themeVars, $component); 41 | 42 | &.full { 43 | max-width: createThemeVar("maxWidth-#{$component}"); 44 | } 45 | width: 100%; 46 | margin: 0 auto; 47 | } 48 | 49 | .childrenWrapper { 50 | --stack-gap-default: #{t.$space-2}; 51 | display: flex; 52 | flex-direction: row; 53 | flex: 1; 54 | min-width: 0; 55 | height: 100%; 56 | align-items: center; 57 | gap: var(--stack-gap-default); 58 | justify-content: $alignment-content-AppHeader; 59 | } 60 | 61 | .subNavPanelSlot{ 62 | display: flex; 63 | flex-direction: row; 64 | } 65 | 66 | .logoAndTitle { 67 | display: flex; 68 | align-items: center; 69 | gap: t.$space-4; 70 | height: 100%; 71 | 72 | &:not(:empty) { 73 | padding-right: t.$space-2; 74 | } 75 | 76 | &:empty { 77 | display: none; 78 | } 79 | } 80 | 81 | .logoContainer:not(:empty) { 82 | flex-shrink: 0; 83 | display: flex; 84 | width: $width-logo-AppHeader; 85 | height: 100%; 86 | align-items: center; 87 | @include t.paddingVars($themeVars, "logo-#{$component}"); 88 | } 89 | 90 | .customLogoContainer { 91 | display: flex; 92 | height: 100%; 93 | align-items: center; 94 | 95 | & > img { 96 | height: 100%; 97 | } 98 | } 99 | 100 | .rightItems { 101 | --stack-gap-default: #{t.$space-2}; 102 | gap: var(--stack-gap-default); 103 | height: 100%; 104 | 105 | &:not(:empty) { 106 | padding-left: t.$space-4; 107 | } 108 | 109 | display: flex; 110 | flex-direction: row; 111 | align-items: center; 112 | } 113 | 114 | .appHub { 115 | text-decoration: none; 116 | margin-right: t.$space-4; 117 | width: 40px; 118 | padding: t.$space-2; 119 | color: t.$textColor-subtitle; 120 | cursor: pointer; 121 | 122 | &:hover { 123 | color: t.$textColor-secondary; 124 | } 125 | 126 | svg { 127 | width: 100%; 128 | height: 100%; 129 | } 130 | } 131 | 132 | .drawerToggle.drawerToggle{ 133 | padding: createThemeVar("padding-drawerToggle-#{$component}") !important; 134 | display: none; 135 | @include t.withMaxScreenSize(2) { 136 | display: block; 137 | } 138 | } 139 | 140 | .logoLink{ 141 | padding: 0; 142 | height: 100%; 143 | &>:first-child { 144 | height: 100%; 145 | } 146 | } 147 | } 148 | 149 | // --- We export the theme variables to add them to the component renderer 150 | :export { 151 | themeVars: t.json-stringify($themeVars); 152 | } 153 | ``` -------------------------------------------------------------------------------- /xmlui/src/components/SelectionStore/SelectionStoreNative.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import React, { 2 | type ReactNode, 3 | useLayoutEffect, 4 | useMemo, 5 | useState, 6 | useContext, 7 | useRef, 8 | } from "react"; 9 | import { isEqual, noop } from "lodash-es"; 10 | 11 | import type { RegisterComponentApiFn, UpdateStateFn } from "../../abstractions/RendererDefs"; 12 | import { useEvent } from "../../components-core/utils/misc"; 13 | import { EMPTY_ARRAY } from "../../components-core/constants"; 14 | 15 | export const defaultProps = { 16 | idKey: "id", 17 | selectedItems: EMPTY_ARRAY, 18 | }; 19 | 20 | type SelectionStoreProps = { 21 | idKey?: string; 22 | updateState: UpdateStateFn; 23 | children: ReactNode; 24 | selectedItems: any[]; 25 | registerComponentApi?: RegisterComponentApiFn; 26 | }; 27 | 28 | const EMPTY_SELECTION_STATE = { 29 | value: EMPTY_ARRAY, 30 | }; 31 | export const StandaloneSelectionStore = ({ children }) => { 32 | const [selection, setSelection] = useState(EMPTY_SELECTION_STATE); 33 | return ( 34 | <SelectionStore updateState={setSelection} selectedItems={selection.value}> 35 | {children} 36 | </SelectionStore> 37 | ); 38 | }; 39 | 40 | export const SelectionStore = ({ 41 | updateState = noop, 42 | idKey = defaultProps.idKey, 43 | children, 44 | selectedItems = defaultProps.selectedItems, 45 | registerComponentApi = noop, 46 | }: SelectionStoreProps) => { 47 | const [items, setItems] = useState<any[]>(selectedItems); 48 | const valueInitializedRef = useRef(false); 49 | const currentItemsRef = useRef<any[]>(selectedItems); 50 | 51 | const refreshSelection = useEvent((allItems: any[] = EMPTY_ARRAY) => { 52 | const safeAllItems = allItems || EMPTY_ARRAY; 53 | const safeSelectedItems = selectedItems || EMPTY_ARRAY; 54 | setItems(safeAllItems); 55 | currentItemsRef.current = safeAllItems; // Update the ref synchronously 56 | let value = safeAllItems.filter( 57 | (item) => !!safeSelectedItems.find((si) => si && item && si[idKey] === item[idKey]), 58 | ); 59 | if (!isEqual(safeSelectedItems, value) || !valueInitializedRef.current) { 60 | valueInitializedRef.current = true; 61 | updateState({ 62 | value, 63 | }); 64 | } 65 | }); 66 | 67 | const setSelectedRowIds = useEvent((rowIds: any) => { 68 | const safeItems = currentItemsRef.current || EMPTY_ARRAY; // Use ref instead of state 69 | updateState({ value: safeItems.filter((item) => rowIds.includes(item[idKey])) }); 70 | }); 71 | 72 | const clearSelection = useEvent(() => { 73 | setSelectedRowIds(EMPTY_ARRAY); 74 | }); 75 | 76 | useLayoutEffect(() => { 77 | registerComponentApi({ 78 | clearSelection, 79 | setSelectedRowIds, 80 | refreshSelection, 81 | }); 82 | }, [clearSelection, setSelectedRowIds, registerComponentApi, refreshSelection]); 83 | 84 | // --- Pass this selection context to the provider 85 | const contextValue = useMemo(() => { 86 | return { 87 | selectedItems, 88 | setSelectedRowIds, 89 | refreshSelection, 90 | idKey, 91 | }; 92 | }, [refreshSelection, selectedItems, setSelectedRowIds, idKey]); 93 | 94 | return <SelectionContext.Provider value={contextValue}>{children}</SelectionContext.Provider>; 95 | }; 96 | 97 | // Defines the elements of the current selection tracking 98 | interface SelectionState { 99 | selectedItems: any[]; 100 | setSelectedRowIds: React.Dispatch<React.SetStateAction<string[]>>; 101 | refreshSelection: (allItems: any[]) => void; 102 | idKey: string; 103 | } 104 | 105 | // Represents the default selection context 106 | const SelectionContext = React.createContext<SelectionState>(null); 107 | 108 | // This React hook takes care of retrieving the current selection context 109 | export function useSelectionContext() { 110 | return useContext(SelectionContext); 111 | } 112 | ``` -------------------------------------------------------------------------------- /packages/xmlui-animations/src/AnimationNative.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import { animated, useSpring, useInView } from "@react-spring/web"; 2 | import React, { Children, forwardRef, useEffect, useMemo, useState } from "react"; 3 | import { useCallback } from "react"; 4 | import type { RegisterComponentApiFn } from "xmlui"; 5 | 6 | export type AnimationProps = { 7 | children?: React.ReactNode; 8 | animation: any; 9 | registerComponentApi?: RegisterComponentApiFn; 10 | onStop?: () => void; 11 | onStart?: () => void; 12 | animateWhenInView?: boolean; 13 | duration?: number; 14 | reverse?: boolean; 15 | loop?: boolean; 16 | once?: boolean; 17 | delay?: number; 18 | }; 19 | 20 | const AnimatedComponent = animated( 21 | forwardRef(function InlineComponentDef(props: any, ref) { 22 | const { children, ...rest } = props; 23 | return React.cloneElement(children, { ...rest, ref }); 24 | }), 25 | ); 26 | 27 | export const defaultProps: Pick<AnimationProps, "delay" | "animateWhenInView" |"reverse" | "loop" | "once"> = { 28 | delay: 0, 29 | animateWhenInView: false, 30 | reverse: false, 31 | loop: false, 32 | once: false 33 | }; 34 | 35 | export const Animation = ({ 36 | children, 37 | registerComponentApi, 38 | animation, 39 | duration, 40 | onStop, 41 | onStart, 42 | delay = defaultProps.delay, 43 | animateWhenInView = defaultProps.animateWhenInView, 44 | reverse = defaultProps.reverse, 45 | loop = defaultProps.loop, 46 | once = defaultProps.once, 47 | }: AnimationProps) => { 48 | const [_animation] = useState(animation); 49 | const [toggle, setToggle] = useState(false); 50 | const [reset, setReset] = useState(false); 51 | const [count, setCount] = useState(0); 52 | const times = 1; 53 | const animationSettings = useMemo<any>( 54 | () => ({ 55 | from: _animation.from, 56 | to: _animation.to, 57 | config: { 58 | ..._animation.config, 59 | duration, 60 | }, 61 | delay, 62 | loop, 63 | reset, 64 | reverse: toggle, 65 | onRest: () => { 66 | onStop?.(); 67 | if (loop) { 68 | if (reverse) { 69 | setCount(count + 1); 70 | setToggle(!toggle); 71 | } 72 | setReset(true); 73 | } else { 74 | if (reverse && count < times) { 75 | setCount(count + 1); 76 | setToggle(!toggle); 77 | } 78 | } 79 | }, 80 | onStart: () => onStart?.(), 81 | }), 82 | [ 83 | _animation.config, 84 | _animation.from, 85 | _animation.to, 86 | count, 87 | delay, 88 | duration, 89 | loop, 90 | onStart, 91 | onStop, 92 | reset, 93 | reverse, 94 | toggle, 95 | ], 96 | ); 97 | 98 | const [springs, api] = useSpring( 99 | () => ({ 100 | ...animationSettings, 101 | }), 102 | [animationSettings], 103 | ); 104 | 105 | const [ref, animationStyles] = useInView(() => animationSettings, { 106 | once, 107 | }); 108 | 109 | const startAnimation = useCallback(() => { 110 | api.start(_animation); 111 | return () => { 112 | api.stop(); 113 | }; 114 | }, [_animation, api]); 115 | 116 | const stopAnimation = useCallback(() => { 117 | api.stop(); 118 | }, [api]); 119 | 120 | useEffect(() => { 121 | registerComponentApi?.({ 122 | start: startAnimation, 123 | stop: stopAnimation, 124 | }); 125 | }, [registerComponentApi, startAnimation, stopAnimation]); 126 | 127 | const content = useMemo(() => { 128 | return Children.map(children, (child, index) => 129 | animateWhenInView ? ( 130 | <AnimatedComponent style={animationStyles} key={index} ref={ref}> 131 | {child} 132 | </AnimatedComponent> 133 | ) : ( 134 | <AnimatedComponent style={springs} key={index}> 135 | {child} 136 | </AnimatedComponent> 137 | ), 138 | ); 139 | }, [animateWhenInView, animationStyles, children, ref, springs]); 140 | 141 | return content; 142 | }; 143 | ``` -------------------------------------------------------------------------------- /docs/public/pages/tutorial-07.md: -------------------------------------------------------------------------------- ```markdown 1 | # Invoices 2 | 3 | Here is the app's `Invoices` page, with a cached subset of data. To view the table full-width, and optionally make changes, use the  icon to pop out to the full XMLUI playground. 4 | 5 | 6 | ```xmlui-pg 7 | ---app display 8 | <App> 9 | <Invoices /> 10 | </App> 11 | ---comp 12 | <Component 13 | name="Invoices" 14 | > 15 | <DataSource 16 | id="invoices" 17 | url="/resources/files/invoices.json" 18 | transformResult="{(data) => data.slice(0, 10)}" 19 | method="GET" 20 | /> 21 | 22 | <Theme maxWidth-ModalDialog="50%"> 23 | <ModalDialog id="detailsDialog"> 24 | <InvoiceDetails details="{$params[0]}" /> 25 | </ModalDialog> 26 | </Theme> 27 | 28 | <HStack> 29 | <H1>Invoices</H1> 30 | <SpaceFiller /> 31 | <Button enabled="{false}" label="Create Invoice" onClick="navigate('/invoices/new')" /> 32 | </HStack> 33 | 34 | <Table data="{invoices}"> 35 | <Column canSort="true" bindTo="invoice_number" /> 36 | <Column canSort="true" bindTo="client" /> 37 | <Column canSort="true" bindTo="issue_date" /> 38 | <Column canSort="true" bindTo="due_date" /> 39 | <Column canSort="true" bindTo="paid_date" /> 40 | <Column canSort="true" header="total"> 41 | ${$item.total} 42 | </Column> 43 | <Column canSort="true" header="Status"> 44 | <StatusBadge status="{$item.status}" /> 45 | </Column> 46 | <Column header="Details"> 47 | <Icon name="doc-outline" /> 48 | </Column> 49 | </Table> 50 | 51 | </Component> 52 | ---comp 53 | <Component 54 | name="StatusBadge" 55 | var.statusColors="{{ 56 | draft: { background: '#f59e0b', label: 'white' }, 57 | sent: { background: '#3b82f6', label: 'white' }, 58 | paid: { background: '#10b981', label: 'white' } 59 | }}" 60 | > 61 | <Badge 62 | enabled="{$props.enabled}" 63 | value="{$props.status}" 64 | colorMap="{statusColors}" 65 | variant="pill" 66 | /> 67 | </Component> 68 | ``` 69 | 70 | The `Create Invoice` button is disabled for this part of the demo. 71 | 72 | Here is the `Invoices` component. 73 | 74 | ```xmlui /detailsDialog/ 75 | <Component name="Invoices"> 76 | 77 | <HStack> 78 | <H1>Invoices</H1> 79 | <SpaceFiller/> 80 | <Button label="Create Invoice" onClick="navigate('/invoices/new')"/> 81 | </HStack> 82 | 83 | <Table data="/api/invoices"> 84 | <Column bindTo="invoice_number"/> 85 | <Column bindTo="client"/> 86 | <Column bindTo="issue_date"/> 87 | <Column bindTo="due_date"/> 88 | <Column bindTo="paid_date"/> 89 | <Column bindTo="total"> 90 | ${$item.total} 91 | </Column> 92 | <Column bindTo="status"> 93 | <StatusBadge status="{$item.status}"/> 94 | </Column> 95 | <Column canSort="{false}" header="Details"> 96 | <Icon name="doc-outline" onClick="detailsDialog.open($item)"/> 97 | </Column> 98 | </Table> 99 | 100 | <ModalDialog id="detailsDialog" maxWidth="50%"> 101 | <InvoiceDetails details="{$param}"/> 102 | </ModalDialog> 103 | 104 | </Component> 105 | ``` 106 | 107 | ## A ModalDialog 108 | 109 | The id attribute of the [ModalDialog](/components/ModalDialog) maps to the `onClick` handler of the `Details` column. We'll see later how, when clicked, it loads the `InvoiceDetails` component into a `ModalDialog`. 110 | 111 | When enabled, the `CreateInvoice` button uses the global function `navigate` to go to the page defined by the `CreateInvoice` component. 112 | 113 | Two of the columns in the table, `Status` and `Details`, show how a [Column](/components/Column) can contain XMLUI markup that may include user-defined (`StatusBadge`) and/or built-in (`Icon`) components. 114 | 115 | 116 | ``` -------------------------------------------------------------------------------- /xmlui/src/components/TableOfContents/TableOfContents.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import styles from "./TableOfContents.module.scss"; 2 | 3 | import { createComponentRenderer } from "../../components-core/renderers"; 4 | import { parseScssVar } from "../../components-core/theming/themeVars"; 5 | import { TableOfContents, defaultProps } from "./TableOfContentsNative"; 6 | import { useIndexerContext } from "../App/IndexerContext"; 7 | import { createMetadata } from "../metadata-helpers"; 8 | 9 | const COMP = "TableOfContents"; 10 | const COMP_CHILD = "TableOfContentsItem"; 11 | 12 | export const TableOfContentsMd = createMetadata({ 13 | status: "stable", 14 | description: 15 | "`TableOfContents` component collects [Heading](/components/Heading) and " + 16 | "[Bookmark](/components/Bookmark) within the current page and displays them in a navigable tree.", 17 | props: { 18 | smoothScrolling: { 19 | description: 20 | `This property indicates that smooth scrolling is used while scrolling the selected table ` + 21 | `of contents items into view.`, 22 | valueType: "boolean", 23 | defaultValue: defaultProps.smoothScrolling, 24 | }, 25 | maxHeadingLevel: { 26 | description: 27 | "Defines the maximum heading level (1 to 6) to include in the table of contents. " + 28 | "For example, if it is 2, then `H1` and `H2` are displayed, but lower levels " + 29 | "(`H3` to `H6`) are not.", 30 | valueType: "number", 31 | defaultValue: defaultProps.maxHeadingLevel, 32 | }, 33 | omitH1: { 34 | description: 35 | "If true, the `H1` heading is not included in the table of contents. " + 36 | "This is useful when the `H1` is used for the page title and you want to avoid duplication.", 37 | valueType: "boolean", 38 | defaultValue: false, 39 | }, 40 | }, 41 | themeVars: parseScssVar(styles.themeVars), 42 | defaultThemeVars: { 43 | [`padding-${COMP}`]: "$space-2", 44 | [`textColor-${COMP_CHILD}`]: "$color-secondary-500", 45 | [`textColor-${COMP_CHILD}--hover`]: "$textColor-primary", 46 | [`fontSize-${COMP_CHILD}`]: "$fontSize-sm", 47 | [`wordWrap-${COMP_CHILD}`]: "break-word", 48 | 49 | [`paddingVertical-${COMP_CHILD}`]: "$space-1", 50 | [`paddingLeft-${COMP_CHILD}`]: "$space-1", 51 | [`paddingLeft-${COMP_CHILD}-level-2`]: "$space-3", 52 | [`paddingLeft-${COMP_CHILD}-level-3`]: "$space-5", 53 | [`paddingLeft-${COMP_CHILD}-level-4`]: "$space-6", 54 | [`paddingLeft-${COMP_CHILD}-level-5`]: "$space-6", 55 | [`paddingLeft-${COMP_CHILD}-level-6`]: "$space-6", 56 | [`fontWeight-${COMP_CHILD}`]: "$fontWeight-bold", 57 | [`fontWeight-${COMP_CHILD}-level-3`]: "normal", 58 | [`fontWeight-${COMP_CHILD}-level-4`]: "normal", 59 | [`fontWeight-${COMP_CHILD}-level-5`]: "normal", 60 | [`fontWeight-${COMP_CHILD}-level-6`]: "normal", 61 | 62 | [`fontStyle-${COMP_CHILD}-level-6`]: "italic", 63 | 64 | [`color-${COMP_CHILD}--active`]: "$color-primary-500", 65 | }, 66 | }); 67 | 68 | function IndexAwareTableOfContents(props) { 69 | const { indexing } = useIndexerContext(); 70 | if (indexing) { 71 | return null; 72 | } 73 | return <TableOfContents {...props} />; 74 | } 75 | 76 | export const tableOfContentsRenderer = createComponentRenderer( 77 | COMP, 78 | TableOfContentsMd, 79 | ({ className, node, extractValue }) => { 80 | return ( 81 | <IndexAwareTableOfContents 82 | className={className} 83 | smoothScrolling={extractValue.asOptionalBoolean(node.props?.smoothScrolling)} 84 | maxHeadingLevel={extractValue.asOptionalNumber(node.props?.maxHeadingLevel)} 85 | omitH1={extractValue.asOptionalBoolean(node.props?.omitH1)} 86 | /> 87 | ); 88 | }, 89 | ); 90 | ``` -------------------------------------------------------------------------------- /xmlui/tests/parsers/parameter-parser.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { describe, expect, it } from "vitest"; 2 | import { parseParameterString } from "../../src/components-core/script-runner/ParameterParser"; 3 | import { Expression } from "../../src/components-core/script-runner/ScriptingSourceTree"; 4 | import { T_BINARY_EXPRESSION, T_LITERAL } from "../../src/parsers/scripting/ScriptingNodeTypes"; 5 | 6 | describe("parseParameterString", () => { 7 | it("Empty string works", () => { 8 | // --- Act 9 | const result = parseParameterString(""); 10 | 11 | // --- Assert 12 | expect(result.length).toBe(0); 13 | }); 14 | 15 | it("String literal works", () => { 16 | // --- Act 17 | const result = parseParameterString("abc"); 18 | 19 | // --- Assert 20 | expect(result.length).toBe(1); 21 | expect(result[0].type).toBe("literal"); 22 | expect(result[0].value).toBe("abc"); 23 | }); 24 | 25 | it("Single expression works", () => { 26 | // --- Act 27 | const result = parseParameterString("{a+b}"); 28 | 29 | // --- Assert 30 | expect(result.length).toBe(1); 31 | expect(result[0].type).toBe("expression"); 32 | expect((result[0].value as Expression).type).toBe(T_BINARY_EXPRESSION); 33 | }); 34 | 35 | it("Combination works #1", () => { 36 | // --- Act 37 | const result = parseParameterString("hello{a+b}"); 38 | 39 | // --- Assert 40 | expect(result.length).toBe(2); 41 | expect(result[0].type).toBe("literal"); 42 | expect(result[0].value).toBe("hello"); 43 | expect(result[1].type).toBe("expression"); 44 | expect((result[1].value as Expression).type).toBe(T_BINARY_EXPRESSION); 45 | }); 46 | 47 | it("Combination works #2", () => { 48 | // --- Act 49 | const result = parseParameterString("{a+b}world"); 50 | 51 | // --- Assert 52 | expect(result.length).toBe(2); 53 | expect(result[0].type).toBe("expression"); 54 | expect((result[0].value as Expression).type).toBe(T_BINARY_EXPRESSION); 55 | expect(result[1].type).toBe("literal"); 56 | expect(result[1].value).toBe("world"); 57 | }); 58 | 59 | it("Combination works #3", () => { 60 | // --- Act 61 | const result = parseParameterString("hello{a+b}world"); 62 | 63 | // --- Assert 64 | expect(result.length).toBe(3); 65 | expect(result[0].type).toBe("literal"); 66 | expect(result[0].value).toBe("hello"); 67 | expect(result[1].type).toBe("expression"); 68 | expect((result[1].value as Expression).type).toBe(T_BINARY_EXPRESSION); 69 | expect(result[2].type).toBe("literal"); 70 | expect(result[2].value).toBe("world"); 71 | }); 72 | 73 | it("Single escape works #1", () => { 74 | // --- Act 75 | const result = parseParameterString("\\{a+b}"); 76 | 77 | // --- Assert 78 | expect(result.length).toBe(1); 79 | expect(result[0].type).toBe("literal"); 80 | expect(result[0].value).toBe("{a+b}"); 81 | }); 82 | 83 | it("Single escape works #2", () => { 84 | // --- Act 85 | const result = parseParameterString("\\{{a+b}"); 86 | 87 | // --- Assert 88 | expect(result.length).toBe(2); 89 | expect(result[0].type).toBe("literal"); 90 | expect(result[0].value).toBe("{"); 91 | expect(result[1].type).toBe("expression"); 92 | expect((result[1].value as Expression).type).toBe(T_BINARY_EXPRESSION); 93 | }); 94 | 95 | it("Single escape works #3", () => { 96 | // --- Act 97 | const result = parseParameterString("/\\{{3}$/"); 98 | 99 | // --- Assert 100 | expect(result.length).toBe(3); 101 | expect(result[0].type).toBe("literal"); 102 | expect(result[0].value).toBe("/{"); 103 | expect(result[1].type).toBe("expression"); 104 | expect((result[1].value as Expression).type).toBe(T_LITERAL); 105 | expect(result[2].type).toBe("literal"); 106 | expect(result[2].value).toBe("$/"); 107 | }); 108 | }); 109 | ``` -------------------------------------------------------------------------------- /xmlui/tests/parsers/paremeter-parser.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { describe, expect, it } from "vitest"; 2 | import { parseParameterString } from "../../src/components-core/script-runner/ParameterParser"; 3 | import { Expression } from "../../src/components-core/script-runner/ScriptingSourceTree"; 4 | import { T_BINARY_EXPRESSION, T_LITERAL } from "../../src/parsers/scripting/ScriptingNodeTypes"; 5 | 6 | describe("parseParameterString", () => { 7 | it("Empty string works", () => { 8 | // --- Act 9 | const result = parseParameterString(""); 10 | 11 | // --- Assert 12 | expect(result.length).toBe(0); 13 | }); 14 | 15 | it("String literal works", () => { 16 | // --- Act 17 | const result = parseParameterString("abc"); 18 | 19 | // --- Assert 20 | expect(result.length).toBe(1); 21 | expect(result[0].type).toBe("literal"); 22 | expect(result[0].value).toBe("abc"); 23 | }); 24 | 25 | it("Single expression works", () => { 26 | // --- Act 27 | const result = parseParameterString("{a+b}"); 28 | 29 | // --- Assert 30 | expect(result.length).toBe(1); 31 | expect(result[0].type).toBe("expression"); 32 | expect((result[0].value as Expression).type).toBe(T_BINARY_EXPRESSION); 33 | }); 34 | 35 | it("Combination works #1", () => { 36 | // --- Act 37 | const result = parseParameterString("hello{a+b}"); 38 | 39 | // --- Assert 40 | expect(result.length).toBe(2); 41 | expect(result[0].type).toBe("literal"); 42 | expect(result[0].value).toBe("hello"); 43 | expect(result[1].type).toBe("expression"); 44 | expect((result[1].value as Expression).type).toBe(T_BINARY_EXPRESSION); 45 | }); 46 | 47 | it("Combination works #2", () => { 48 | // --- Act 49 | const result = parseParameterString("{a+b}world"); 50 | 51 | // --- Assert 52 | expect(result.length).toBe(2); 53 | expect(result[0].type).toBe("expression"); 54 | expect((result[0].value as Expression).type).toBe(T_BINARY_EXPRESSION); 55 | expect(result[1].type).toBe("literal"); 56 | expect(result[1].value).toBe("world"); 57 | }); 58 | 59 | it("Combination works #3", () => { 60 | // --- Act 61 | const result = parseParameterString("hello{a+b}world"); 62 | 63 | // --- Assert 64 | expect(result.length).toBe(3); 65 | expect(result[0].type).toBe("literal"); 66 | expect(result[0].value).toBe("hello"); 67 | expect(result[1].type).toBe("expression"); 68 | expect((result[1].value as Expression).type).toBe(T_BINARY_EXPRESSION); 69 | expect(result[2].type).toBe("literal"); 70 | expect(result[2].value).toBe("world"); 71 | }); 72 | 73 | it("Single escape works #1", () => { 74 | // --- Act 75 | const result = parseParameterString("\\{a+b}"); 76 | 77 | // --- Assert 78 | expect(result.length).toBe(1); 79 | expect(result[0].type).toBe("literal"); 80 | expect(result[0].value).toBe("{a+b}"); 81 | }); 82 | 83 | it("Single escape works #2", () => { 84 | // --- Act 85 | const result = parseParameterString("\\{{a+b}"); 86 | 87 | // --- Assert 88 | expect(result.length).toBe(2); 89 | expect(result[0].type).toBe("literal"); 90 | expect(result[0].value).toBe("{"); 91 | expect(result[1].type).toBe("expression"); 92 | expect((result[1].value as Expression).type).toBe(T_BINARY_EXPRESSION); 93 | }); 94 | 95 | it("Single escape works #3", () => { 96 | // --- Act 97 | const result = parseParameterString("/\\{{3}$/"); 98 | 99 | // --- Assert 100 | expect(result.length).toBe(3); 101 | expect(result[0].type).toBe("literal"); 102 | expect(result[0].value).toBe("/{"); 103 | expect(result[1].type).toBe("expression"); 104 | expect((result[1].value as Expression).type).toBe(T_LITERAL); 105 | expect(result[2].type).toBe("literal"); 106 | expect(result[2].value).toBe("$/"); 107 | }); 108 | }); 109 | ``` -------------------------------------------------------------------------------- /packages/xmlui-playground/src/playground/Select.module.scss: -------------------------------------------------------------------------------- ```scss 1 | @use "xmlui/themes.scss" as themes; 2 | 3 | .RadixMenuContent { 4 | display: flex; 5 | flex-direction: column; 6 | min-width: 140px; 7 | width: fit-content; 8 | background-color: themes.$backgroundColor; 9 | border-radius: 6px; 10 | padding: 6px; 11 | gap: 5rem; 12 | box-shadow: 0px 10px 38px -10px rgba(22, 23, 24, 0.35), 0px 10px 20px -15px rgba(22, 23, 24, 0.2); 13 | animation-duration: 400ms; 14 | animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); 15 | will-change: transform, opacity; 16 | } 17 | 18 | :is(html[class~=dark] .RadixMenuContent) { 19 | background-color: rgba(17,17,17,var(--tw-bg-opacity)); 20 | } 21 | 22 | .SelectViewport { 23 | padding: 5px; 24 | } 25 | 26 | :is(html[class~=dark] .SelectViewport) { 27 | --tw-bg-opacity: 1; 28 | background-color: hsl(var(--nextra-primary-hue)var(--nextra-primary-saturation)77%/.1); 29 | } 30 | 31 | .RadixMenuItem { 32 | font-size: 1rem; 33 | line-height: 1; 34 | --tw-text-opacity: 1; 35 | color: rgba(107,114,128,var(--tw-text-opacity)); 36 | border-radius: 3px; 37 | display: flex; 38 | align-items: center; 39 | padding: .5rem; 40 | position: relative; 41 | user-select: none; 42 | cursor: pointer; 43 | } 44 | 45 | :is(html[class~=dark] .RadixMenuItem) { 46 | --tw-text-opacity: 1; 47 | color: rgba(163,163,163,var(--tw-text-opacity)); 48 | } 49 | 50 | .RadixMenuItem[data-highlighted] { 51 | outline: none; 52 | background-color: rgba(243,244,246,var(--tw-bg-opacity)); 53 | color: rgba(51,65,85,var(--tw-text-opacity)); 54 | } 55 | 56 | :is(html[class~=dark] .RadixMenuItem[data-highlighted]) { 57 | background-color: hsl(var(--nextra-primary-hue)var(--nextra-primary-saturation)94%/.05); 58 | } 59 | 60 | .RadixMenuItem[data-state=checked] { 61 | background-color: hsl(var(--nextra-primary-hue)var(--nextra-primary-saturation)94%/var(--tw-bg-opacity)); 62 | --tw-text-opacity: 1; 63 | color: hsl(var(--nextra-primary-hue)var(--nextra-primary-saturation)32%/var(--tw-text-opacity)); 64 | } 65 | 66 | :is(html[class~=dark] .RadixMenuItem[data-state=checked]) { 67 | --tw-text-opacity: 1; 68 | color: hsl(var(--nextra-primary-hue)var(--nextra-primary-saturation)45%/var(--tw-text-opacity)); 69 | background-color: hsl(var(--nextra-primary-hue)var(--nextra-primary-saturation)66%/.1); 70 | } 71 | 72 | .SelectLabel { 73 | width: 100%; 74 | padding: 1rem .5rem .2rem; 75 | font-size: 0.875rem; 76 | font-weight: 500; 77 | line-height: 25px; 78 | color: rgba(107,114,128,var(--tw-text-opacity)); 79 | } 80 | 81 | :is(html[class~=dark] .SelectLabel) { 82 | --tw-text-opacity: 1; 83 | color: rgba(163,163,163,var(--tw-text-opacity)); 84 | } 85 | 86 | .RadixMenuItemIndicator { 87 | position: absolute; 88 | left: 0; 89 | width: 25px; 90 | display: inline-flex; 91 | align-items: center; 92 | justify-content: center; 93 | } 94 | 95 | .RadixMenuContent[data-side='top'] { 96 | animation-name: slideDownAndFade; 97 | } 98 | 99 | .RadixMenuContent[data-side='right'] { 100 | animation-name: slideLeftAndFade; 101 | } 102 | 103 | .RadixMenuContent[data-side='bottom'] { 104 | animation-name: slideUpAndFade; 105 | } 106 | 107 | .RadixMenuContent[data-side='left'] { 108 | animation-name: slideRightAndFade; 109 | } 110 | 111 | @keyframes slideUpAndFade { 112 | from { 113 | opacity: 0; 114 | transform: translateY(2px); 115 | } 116 | to { 117 | opacity: 1; 118 | transform: translateY(0); 119 | } 120 | } 121 | 122 | @keyframes slideRightAndFade { 123 | from { 124 | opacity: 0; 125 | transform: translateX(-2px); 126 | } 127 | to { 128 | opacity: 1; 129 | transform: translateX(0); 130 | } 131 | } 132 | 133 | @keyframes slideDownAndFade { 134 | from { 135 | opacity: 0; 136 | transform: translateY(-2px); 137 | } 138 | to { 139 | opacity: 1; 140 | transform: translateY(0); 141 | } 142 | } 143 | 144 | @keyframes slideLeftAndFade { 145 | from { 146 | opacity: 0; 147 | transform: translateX(2px); 148 | } 149 | to { 150 | opacity: 1; 151 | transform: translateX(0); 152 | } 153 | } 154 | ``` -------------------------------------------------------------------------------- /xmlui/src/components/App/Sheet.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import * as React from "react"; 2 | import * as SheetPrimitive from "@radix-ui/react-dialog"; 3 | import classnames from "classnames"; 4 | import { VisuallyHidden } from "@radix-ui/react-visually-hidden"; 5 | 6 | import styles from "./Sheet.module.scss"; 7 | 8 | import { useTheme } from "../../components-core/theming/ThemeContext"; 9 | import { Icon } from "../../components/Icon/IconNative"; 10 | 11 | //based on this: https://ui.shadcn.com/docs/components/sheet 12 | 13 | const Sheet = SheetPrimitive.Root; 14 | 15 | const SheetPortal = SheetPrimitive.Portal; 16 | 17 | const SheetOverlay = React.forwardRef< 18 | React.ElementRef<typeof SheetPrimitive.Overlay>, 19 | React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay> 20 | >(({ className, ...props }, ref) => ( 21 | <SheetPrimitive.Overlay className={classnames(styles.overlay, className)} {...props} ref={ref} /> 22 | )); 23 | SheetOverlay.displayName = SheetPrimitive.Overlay.displayName; 24 | 25 | interface SheetContentProps extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content> { 26 | side: "top" | "bottom" | "left" | "right"; 27 | } 28 | 29 | const SheetContent = React.forwardRef<React.ElementRef<typeof SheetPrimitive.Content>, SheetContentProps>( 30 | ({ side = "left", className, children, ...props }, ref) => { 31 | const { root } = useTheme(); 32 | return ( 33 | <SheetPortal container={root}> 34 | <SheetOverlay /> 35 | <SheetPrimitive.Content 36 | forceMount={true} 37 | ref={ref} 38 | className={classnames( 39 | styles.sheetContent, 40 | { 41 | [styles.top]: side === "top", 42 | [styles.bottom]: side === "bottom", 43 | [styles.left]: side === "left", 44 | [styles.right]: side === "right", 45 | }, 46 | className 47 | )} 48 | {...props} 49 | > 50 | {children} 51 | <SheetPrimitive.Close className={styles.close}> 52 | <Icon name={"close"} /> 53 | <VisuallyHidden>Close</VisuallyHidden> 54 | </SheetPrimitive.Close> 55 | </SheetPrimitive.Content> 56 | </SheetPortal> 57 | ); 58 | } 59 | ); 60 | SheetContent.displayName = SheetPrimitive.Content.displayName; 61 | 62 | const SheetHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => ( 63 | <div className={classnames("flex flex-col space-y-2 text-center sm:text-left", className)} {...props} /> 64 | ); 65 | SheetHeader.displayName = "SheetHeader"; 66 | 67 | const SheetFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => ( 68 | <div className={classnames("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)} {...props} /> 69 | ); 70 | SheetFooter.displayName = "SheetFooter"; 71 | 72 | const SheetTitle = React.forwardRef< 73 | React.ElementRef<typeof SheetPrimitive.Title>, 74 | React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title> 75 | >(({ className, ...props }, ref) => ( 76 | <SheetPrimitive.Title 77 | ref={ref} 78 | className={classnames("text-lg font-semibold text-foreground", className)} 79 | {...props} 80 | /> 81 | )); 82 | SheetTitle.displayName = SheetPrimitive.Title.displayName; 83 | 84 | const SheetDescription = React.forwardRef< 85 | React.ElementRef<typeof SheetPrimitive.Description>, 86 | React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description> 87 | >(({ className, ...props }, ref) => ( 88 | <SheetPrimitive.Description ref={ref} className={classnames("text-sm text-muted-foreground", className)} {...props} /> 89 | )); 90 | SheetDescription.displayName = SheetPrimitive.Description.displayName; 91 | 92 | export { 93 | Sheet, 94 | SheetContent, 95 | }; 96 | ``` -------------------------------------------------------------------------------- /xmlui/src/components-core/script-runner/AttributeValueParser.ts: -------------------------------------------------------------------------------- ```typescript 1 | import type { ParsedPropertyValue } from "../../abstractions/scripting/Compilation"; 2 | import type { Expression } from "./ScriptingSourceTree"; 3 | import { Parser } from "../../parsers/scripting/Parser"; 4 | 5 | let lastParseId = 0; 6 | 7 | /** 8 | * This function parses a parameter string and splits them into string literal and binding expression sections 9 | * @param source String to parse 10 | * @returns Parameter string sections 11 | */ 12 | export function parseAttributeValue(source: string): ParsedPropertyValue { 13 | const result: ParsedPropertyValue = { 14 | __PARSED: true, 15 | parseId: ++lastParseId, 16 | segments: [], 17 | }; 18 | if (source === undefined || source === null) return result; 19 | 20 | let phase = ParsePhase.StringLiteral; 21 | let section = ""; 22 | let escape = ""; 23 | for (let i = 0; i < source.length; i++) { 24 | const ch = source[i]; 25 | switch (phase) { 26 | case ParsePhase.StringLiteral: 27 | if (ch === "\\") { 28 | phase = ParsePhase.Escape; 29 | escape = "\\"; 30 | } else if (ch === "{") { 31 | // --- A new expression starts, close the previous string literal 32 | if (section !== "") { 33 | result.segments.push({ 34 | literal: section, 35 | }); 36 | } 37 | // --- Start a new section 38 | section = ""; 39 | phase = ParsePhase.ExprStart; 40 | } else { 41 | section += ch; 42 | } 43 | break; 44 | 45 | case ParsePhase.Escape: 46 | if (ch === "\\") { 47 | // --- Go on with escape 48 | escape += ch; 49 | break; 50 | } 51 | 52 | if (ch === "{") { 53 | // --- End escape as a literal section without the first "\" escape character 54 | section += escape.substring(1) + ch; 55 | } else { 56 | // --- End escape as a literal section with the full sequence 57 | section += escape + ch; 58 | } 59 | phase = ParsePhase.StringLiteral; 60 | break; 61 | 62 | case ParsePhase.ExprStart: 63 | const exprSource = source.substring(i); 64 | const parser = new Parser(source.substring(i)); 65 | let expr: Expression | null = null; 66 | try { 67 | expr = parser.parseExpr(); 68 | } catch (err) { 69 | throw new Error(`Cannot parse expression: '${exprSource}': ${err}`); 70 | } 71 | const tail = parser.getTail(); 72 | if (!tail || tail.trim().length < 1 || tail.trim()[0] !== "}") { 73 | // --- Unclosed expression, back to its beginning 74 | throw new Error(`Unclosed expression: '${source}'\n'${exprSource}'`); 75 | } else { 76 | // --- Successfully parsed expression, get dependencies 77 | result.segments.push({ 78 | expr, 79 | }); 80 | 81 | // --- Skip the parsed part of the expression, and start a new literal section 82 | i = source.length - tail.length; 83 | section = ""; 84 | } 85 | phase = ParsePhase.StringLiteral; 86 | break; 87 | } 88 | } 89 | 90 | // --- Process the last segment 91 | switch (phase) { 92 | case ParsePhase.StringLiteral: 93 | if (section !== "") { 94 | result.segments.push({ 95 | literal: section, 96 | }); 97 | } 98 | break; 99 | case ParsePhase.Escape: 100 | result.segments.push({ 101 | literal: section + escape, 102 | }); 103 | break; 104 | case ParsePhase.ExprStart: 105 | result.segments.push({ 106 | literal: section + "{", 107 | }); 108 | break; 109 | } 110 | 111 | // --- Done. 112 | return result; 113 | } 114 | 115 | enum ParsePhase { 116 | StringLiteral, 117 | Escape, 118 | ExprStart, 119 | } 120 | ``` -------------------------------------------------------------------------------- /xmlui/src/parsers/xmlui-parser/syntax-kind.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** tokens and nodes combined. Order is significant since the functions below use the numeric values of this enum to check for a range of possible values*/ 2 | export const enum SyntaxKind { 3 | Unknown = 0, 4 | EndOfFileToken = 1, 5 | 6 | // Trivia (skipped tokens) 7 | CommentTrivia = 2, 8 | NewLineTrivia = 3, 9 | WhitespaceTrivia = 4, 10 | 11 | // Effective tokens 12 | Identifier = 5, 13 | /** < */ 14 | OpenNodeStart = 6, 15 | /** </ */ 16 | CloseNodeStart = 7, 17 | /** > */ 18 | NodeEnd = 8, 19 | /** /> */ 20 | NodeClose = 9, 21 | /** : */ 22 | Colon = 10, 23 | /** = */ 24 | Equal = 11, 25 | /** string literal */ 26 | StringLiteral = 12, 27 | /** <![CDATA[ ... ]]> */ 28 | CData = 13, 29 | /** <script>...</script> */ 30 | Script = 14, 31 | 32 | // A token created by the parser which contains arbitrary text, but not the '<' character 33 | TextNode = 15, 34 | 35 | // XMLUI entities 36 | AmpersandEntity = 16, 37 | LessThanEntity = 17, 38 | GreaterThanEntity = 18, 39 | SingleQuoteEntity = 19, 40 | DoubleQuoteEntity = 20, 41 | 42 | // Syntax node types 43 | ElementNode = 21, 44 | AttributeNode = 22, 45 | AttributeKeyNode = 23, 46 | ContentListNode = 24, 47 | AttributeListNode = 25, 48 | TagNameNode = 26, 49 | // should be the last syntax node type 50 | ErrorNode = 27, 51 | } 52 | 53 | export function isTrivia(token: SyntaxKind): boolean { 54 | return token >= SyntaxKind.CommentTrivia && token <= SyntaxKind.WhitespaceTrivia; 55 | } 56 | 57 | export function isInnerNode(token: SyntaxKind): boolean { 58 | return token >= SyntaxKind.ElementNode && token <= SyntaxKind.ErrorNode; 59 | } 60 | 61 | export function getSyntaxKindStrRepr(kind: SyntaxKind): string { 62 | switch (kind) { 63 | case SyntaxKind.Unknown: 64 | return "Unknown"; 65 | case SyntaxKind.EndOfFileToken: 66 | return "EndOfFileToken"; 67 | case SyntaxKind.CommentTrivia: 68 | return "CommentTrivia"; 69 | case SyntaxKind.NewLineTrivia: 70 | return "NewLineTrivia"; 71 | case SyntaxKind.WhitespaceTrivia: 72 | return "WhitespaceTrivia"; 73 | case SyntaxKind.Identifier: 74 | return "Identifier"; 75 | case SyntaxKind.OpenNodeStart: 76 | return "OpenNodeStart"; 77 | case SyntaxKind.CloseNodeStart: 78 | return "CloseNodeStart"; 79 | case SyntaxKind.NodeEnd: 80 | return "NodeEnd"; 81 | case SyntaxKind.NodeClose: 82 | return "NodeClose"; 83 | case SyntaxKind.Colon: 84 | return "Colon"; 85 | case SyntaxKind.Equal: 86 | return "Equal"; 87 | case SyntaxKind.StringLiteral: 88 | return "StringLiteral"; 89 | case SyntaxKind.CData: 90 | return "CData"; 91 | case SyntaxKind.Script: 92 | return "Script"; 93 | case SyntaxKind.AmpersandEntity: 94 | return "AmpersandEntity"; 95 | case SyntaxKind.LessThanEntity: 96 | return "LessThanEntity"; 97 | case SyntaxKind.GreaterThanEntity: 98 | return "GreaterThanEntity"; 99 | case SyntaxKind.SingleQuoteEntity: 100 | return "SingleQuoteEntity"; 101 | case SyntaxKind.DoubleQuoteEntity: 102 | return "DoubleQuoteEntity"; 103 | case SyntaxKind.ElementNode: 104 | return "ElementNode"; 105 | case SyntaxKind.AttributeNode: 106 | return "AttributeNode"; 107 | case SyntaxKind.TextNode: 108 | return "TextNode"; 109 | case SyntaxKind.ContentListNode: 110 | return "ContentListNode"; 111 | case SyntaxKind.AttributeListNode: 112 | return "AttributeListNode"; 113 | case SyntaxKind.TagNameNode: 114 | return "TagNameNode"; 115 | case SyntaxKind.ErrorNode: 116 | return "ErrorNode"; 117 | case SyntaxKind.AttributeKeyNode: 118 | return "AttributeKeyNode"; 119 | } 120 | return assertUnreachable(kind); 121 | } 122 | 123 | function assertUnreachable(x: never): never { 124 | throw new Error("Didn't expect to get here"); 125 | } 126 | ``` -------------------------------------------------------------------------------- /docs/content/components/Option.md: -------------------------------------------------------------------------------- ```markdown 1 | # Option [#option] 2 | 3 | `Option` defines selectable items for choice-based components, providing both the underlying value and display text for selection interfaces. It serves as a non-visual data structure that describes individual choices within [Select](/components/Select), [AutoComplete](/components/AutoComplete), and other selection components. 4 | 5 | **Key features:** 6 | - **Value and label separation**: Define what gets stored (value) separately from what users see (label) 7 | - **Automatic fallbacks**: Uses label as value or value as label when only one is provided 8 | - **Custom templates**: Support for rich content via optionTemplate property or child components 9 | - **State management**: Built-in enabled/disabled states for individual options 10 | - **Data integration**: Works seamlessly with Items components for dynamic option lists 11 | 12 | ## Using `Option` [#using-option] 13 | 14 | ### With `AutoComplete` [#with-autocomplete] 15 | 16 | ```xmlui-pg copy {4-6} display name="Example: Option in a AutoComplete" height="275px" 17 | <App> 18 | <Text value="Selected ID: {myComp.value}"/> 19 | <AutoComplete id="myComp"> 20 | <Option label="John, Smith" value="john" /> 21 | <Option label="Jane, Clint" value="jane" disabled="true" /> 22 | <Option label="Herbert, Frank" value="herbert" /> 23 | </AutoComplete> 24 | </App> 25 | ``` 26 | 27 | ### With `Select` [#with-select] 28 | 29 | ```xmlui-pg copy {4-6} display name="Example: Option in a Select" height="275px" 30 | <App> 31 | <Text value="Selected ID: {mySelect.value}"/> 32 | <Select id="mySelect"> 33 | <Option label="John, Smith" value="john" /> 34 | <Option label="Jane, Clint" value="jane" /> 35 | <Option label="Herbert, Frank" value="herbert" /> 36 | </Select> 37 | </App> 38 | ``` 39 | 40 | ## Properties [#properties] 41 | 42 | ### `enabled` (default: true) [#enabled-default-true] 43 | 44 | This boolean property indicates whether the option is enabled or disabled. 45 | 46 | ### `keywords` [#keywords] 47 | 48 | An array of keywords that can be used for searching and filtering the option. These keywords are not displayed but help users find the option through search. 49 | 50 | ### `label` [#label] 51 | 52 | This property defines the text to display for the option. If `label` is not defined, `Option` will use the `value` as the label. 53 | 54 | >[!INFO] 55 | > If `Option` does not define any of the `label` or `value` properties, the option will not be rendered. 56 | 57 | ```xmlui-pg copy display name="Example: Using label" height="275px" 58 | <App> 59 | <Text value="Selected ID: {mySelect.value}"/> 60 | <Select id="mySelect"> 61 | <Option /> 62 | <Option label="Vanilla" value="van"/> 63 | <Option label="Chocolate" value="choc" /> 64 | <Option value="pist" /> 65 | </Select> 66 | </App> 67 | ``` 68 | 69 | ### `value` [#value] 70 | 71 | This property defines the value of the option. If `value` is not defined, `Option` will use the `label` as the value. If neither is defined, the option is not displayed. 72 | 73 | >[!INFO] 74 | > If `Option` does not define any of the `label` or `value` properties, the option will not be rendered. 75 | 76 | ```xmlui-pg copy display name="Example: Using value" height="275px" 77 | <App> 78 | <Text value="Selected ID: {mySelect.value}"/> 79 | <Select id="mySelect"> 80 | <Option /> 81 | <Option label="Vanilla" /> 82 | <Option label="Chocolate" value="chocolate" /> 83 | <Option label="Pistachio" value="pistachio" /> 84 | </Select> 85 | </App> 86 | ``` 87 | 88 | ## Events [#events] 89 | 90 | This component does not have any events. 91 | 92 | ## Exposed Methods [#exposed-methods] 93 | 94 | This component does not expose any methods. 95 | 96 | ## Styling [#styling] 97 | 98 | This component does not have any styles. 99 | ``` -------------------------------------------------------------------------------- /docs/public/pages/howto/handle-background-operations.md: -------------------------------------------------------------------------------- ```markdown 1 | # Handle background operations 2 | 3 | Use the Queue component for async processing that doesn't block user interaction. 4 | 5 | ```xmlui-pg copy display name="Background file processing with progress feedback" 6 | ---comp display {21-30} /uploadQueue/ 7 | <Component name="BackgroundProcessor" var.items="{[]}" var.processedCount="{0}" var.errorCount="{0}" var.completed="{false}"> 8 | <VStack gap="$space-4"> 9 | <!-- Single action button --> 10 | <Button 11 | label="Upload 5 Files" 12 | onClick="items = [ 13 | { id: 1, filename: 'document.pdf', size: 2048576, type: 'application/pdf' }, 14 | { id: 2, filename: 'image.jpg', size: 1024000, type: 'image/jpeg' }, 15 | { id: 3, filename: 'corrupted-file.txt', size: 512, type: 'text/plain' }, 16 | { id: 4, filename: 'data.csv', size: 4096000, type: 'text/csv' }, 17 | { id: 5, filename: 'presentation.pptx', size: 8192000, type: 'application/vnd.openxmlformats-officedocument.presentationml.presentation' } 18 | ]; uploadQueue.enqueueItems(items)" 19 | enabled="{!completed}" 20 | themeColor="primary" 21 | /> 22 | 23 | <!-- Background upload queue --> 24 | <Queue 25 | id="uploadQueue" 26 | clearAfterFinish="true" 27 | onProcess="processing => { 28 | // Update progress 29 | processing.reportProgress(processedCount + 1); 30 | // Make API call to upload objects (delay happens server-side) 31 | const result = Actions.callApi({ 32 | url: '/api/objects', 33 | method: 'POST', 34 | body: processing.item 35 | }); 36 | processedCount++; 37 | 38 | return result; 39 | }" 40 | onProcessError="(error, processing) => { 41 | errorCount++; 42 | console.error('Processing failed:', error.message, processing.item); 43 | // Return true to show default error display 44 | return true; 45 | }" 46 | onComplete="() => { 47 | console.log('All files processed'); 48 | completed = true; 49 | }"> 50 | 51 | <property name="progressFeedback"> 52 | <HStack> 53 | <Spinner size="sm" /> 54 | <Text>Processing item {processedCount + 1}...</Text> 55 | </HStack> 56 | </property> 57 | 58 | <property name="resultFeedback"> 59 | <HStack> 60 | <Icon name="checkmark"/> 61 | <Text> 62 | All {processedCount} items processed successfully! 63 | </Text> 64 | </HStack> 65 | </property> 66 | </Queue> 67 | 68 | <!-- Status display --> 69 | <Card when="{uploadQueue.getQueueLength() > 0 || processedCount > 0}"> 70 | <VStack> 71 | <HStack> 72 | <Text>Queue length: {uploadQueue.getQueueLength()}</Text> 73 | <Text>Processed: {processedCount}</Text> 74 | </HStack> 75 | </VStack> 76 | </Card> 77 | 78 | <!-- Interactive element while processing --> 79 | <Card when="{uploadQueue.getQueueLength() > 0}"> 80 | <VStack> 81 | <Slider 82 | label="Adjust slider while uploads are running" 83 | minValue="1" 84 | maxValue="10" 85 | initialValue="5" 86 | step="1" 87 | /> 88 | </VStack> 89 | </Card> 90 | </VStack> 91 | </Component> 92 | ---app display 93 | <App> 94 | <BackgroundProcessor /> 95 | </App> 96 | ---api 97 | { 98 | "apiUrl": "/api", 99 | "initialize": "$state.files = []", 100 | "operations": { 101 | "process-file": { 102 | "url": "/objects", 103 | "method": "post", 104 | "bodyParamTypes": { 105 | "filename": "string", 106 | "size": "number", 107 | "type": "string" 108 | }, 109 | "handler": "delay(3000); return { success: true, message: 'File processed successfully' };" 110 | } 111 | } 112 | } 113 | ``` 114 | 115 | ``` -------------------------------------------------------------------------------- /xmlui/src/components-core/interception/Errors.ts: -------------------------------------------------------------------------------- ```typescript 1 | //stolen from axios 2 | export enum HttpStatusCode { 3 | Continue = 100, 4 | SwitchingProtocols = 101, 5 | Processing = 102, 6 | EarlyHints = 103, 7 | Ok = 200, 8 | Created = 201, 9 | Accepted = 202, 10 | NonAuthoritativeInformation = 203, 11 | NoContent = 204, 12 | ResetContent = 205, 13 | PartialContent = 206, 14 | MultiStatus = 207, 15 | AlreadyReported = 208, 16 | ImUsed = 226, 17 | MultipleChoices = 300, 18 | MovedPermanently = 301, 19 | Found = 302, 20 | SeeOther = 303, 21 | NotModified = 304, 22 | UseProxy = 305, 23 | Unused = 306, 24 | TemporaryRedirect = 307, 25 | PermanentRedirect = 308, 26 | BadRequest = 400, 27 | Unauthorized = 401, 28 | PaymentRequired = 402, 29 | Forbidden = 403, 30 | NotFound = 404, 31 | MethodNotAllowed = 405, 32 | NotAcceptable = 406, 33 | ProxyAuthenticationRequired = 407, 34 | RequestTimeout = 408, 35 | Conflict = 409, 36 | Gone = 410, 37 | LengthRequired = 411, 38 | PreconditionFailed = 412, 39 | PayloadTooLarge = 413, 40 | UriTooLong = 414, 41 | UnsupportedMediaType = 415, 42 | RangeNotSatisfiable = 416, 43 | ExpectationFailed = 417, 44 | ImATeapot = 418, 45 | MisdirectedRequest = 421, 46 | UnprocessableEntity = 422, 47 | Locked = 423, 48 | FailedDependency = 424, 49 | TooEarly = 425, 50 | UpgradeRequired = 426, 51 | PreconditionRequired = 428, 52 | TooManyRequests = 429, 53 | RequestHeaderFieldsTooLarge = 431, 54 | UnavailableForLegalReasons = 451, 55 | InternalServerError = 500, 56 | NotImplemented = 501, 57 | BadGateway = 502, 58 | ServiceUnavailable = 503, 59 | GatewayTimeout = 504, 60 | HttpVersionNotSupported = 505, 61 | VariantAlsoNegotiates = 506, 62 | InsufficientStorage = 507, 63 | LoopDetected = 508, 64 | NotExtended = 510, 65 | NetworkAuthenticationRequired = 511, 66 | } 67 | 68 | export class HttpError extends Error { 69 | status; 70 | details; 71 | 72 | constructor(status: number, details?: Record<string, any>) { 73 | super(details?.message || "Not found"); 74 | this.details = details; 75 | this.status = status; 76 | 77 | Object.setPrototypeOf(this, HttpError.prototype); 78 | } 79 | } 80 | 81 | export class NotFoundError extends HttpError { 82 | constructor(details?: Record<string, any>) { 83 | super(HttpStatusCode.NotFound, details); 84 | Object.setPrototypeOf(this, NotFoundError.prototype); 85 | } 86 | } 87 | 88 | export class UnauthorizedError extends HttpError { 89 | constructor(details?: Record<string, any>) { 90 | super(HttpStatusCode.Unauthorized, details); 91 | Object.setPrototypeOf(this, UnauthorizedError.prototype); 92 | } 93 | } 94 | 95 | export class ConflictError extends HttpError { 96 | constructor(details?: Record<string, any>) { 97 | super(HttpStatusCode.Conflict, details); 98 | Object.setPrototypeOf(this, ConflictError.prototype); 99 | } 100 | } 101 | 102 | function convertErrorDetails(messageOrDetails: string | Record<string, any>) { 103 | let details; 104 | if (messageOrDetails) { 105 | if (typeof messageOrDetails === "string") { 106 | details = { 107 | message: messageOrDetails, 108 | }; 109 | } else { 110 | details = messageOrDetails; 111 | } 112 | } 113 | return details; 114 | } 115 | 116 | const Errors = { 117 | NotFound404: (messageOrDetails: string | Record<string, any>) => { 118 | return new NotFoundError(convertErrorDetails(messageOrDetails)); 119 | }, 120 | Unauthorized401: (messageOrDetails: string | Record<string, any>) => { 121 | return new UnauthorizedError(convertErrorDetails(messageOrDetails)); 122 | }, 123 | HttpError: (errorCode: number, messageOrDetails: string | Record<string, any>) => { 124 | return new HttpError(errorCode, convertErrorDetails(messageOrDetails)); 125 | }, 126 | Conflict409: (messageOrDetails: string | Record<string, any>) => { 127 | return new ConflictError(convertErrorDetails(messageOrDetails)); 128 | } 129 | }; 130 | 131 | export default Errors; 132 | ```