This is page 19 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 -------------------------------------------------------------------------------- /docs/public/pages/modal-dialogs.md: -------------------------------------------------------------------------------- ```markdown 1 | # Modal Dialogs 2 | 3 | A `ModalDialog` can be invoked **declaratively** in markup or **imperatively** from code. 4 | 5 | This is the declarative method. You don't need to invoke the `ModalDialog`'s `open()` and `close()` functions directly. The `when` attribute controls opening and closing. 6 | 7 | ```xmlui-pg display {2, 3, 19} 8 | <App> 9 | <variable name="isDialogShown" value="{false}"/> 10 | <ModalDialog 11 | when="{isDialogShown}" 12 | onClose="{ isDialogShown = false }"> 13 | Leslie is always number one to the coffee machine. 14 | He has a competitive personality but gets along with a lot people. 15 | </ModalDialog> 16 | <NavPanel> 17 | <NavLink label="Users" to="/" icon="user" /> 18 | </NavPanel> 19 | <Pages> 20 | <Page url="/"> 21 | <Card 22 | avatarUrl="https://i.pravatar.cc/100" 23 | title="Leslie Peters" 24 | subtitle="Executive Manager"> 25 | Leslie is pretty smart when it comes to business. 26 | <Button label="Details" onClick="isDialogShown = true" /> 27 | </Card> 28 | </Page> 29 | </Pages> 30 | </App> 31 | ``` 32 | 33 | This is the imperative method. You invoke `ModalDialog`'s `open()` and `close()` functions explicitly via its ID. 34 | 35 | ```xmlui-pg display {3, 7, 19} 36 | <App> 37 | <ModalDialog 38 | id="dialog" 39 | title="Leslie Peters"> 40 | Leslie is always number one to the coffee machine. 41 | He has a competitive personality but gets along with a lot people. 42 | <Button label="Close" onClick="dialog.close()" /> 43 | </ModalDialog> 44 | <NavPanel> 45 | <NavLink label="Users" to="/" icon="user" /> 46 | </NavPanel> 47 | <Pages> 48 | <Page url="/"> 49 | <Card 50 | avatarUrl="https://i.pravatar.cc/100" 51 | title="Leslie Peters" 52 | subtitle="Executive Manager"> 53 | Leslie is pretty smart when it comes to business. 54 | <Button label="Details" onClick="dialog.open()" /> 55 | </Card> 56 | </Page> 57 | </Pages> 58 | </App> 59 | ``` 60 | 61 | When embedding a form in a dialog, the form's cancel and successful submit actions automatically close the dialog hosting the form (unless you change this logic). Note that you can pass data via `dialog.open()`, `ModalDialog` receives it as `$param`. 62 | 63 | 64 | ```xmlui-pg display {3, 23} height="400px" 65 | <App> 66 | <ModalDialog id="dialog"> 67 | <Text> ID: { $param } </Text> 68 | <Form 69 | data="{{ name: 'Leslie', age: 32 }}" 70 | onSubmit="(formData) => console.log(formData)" 71 | > 72 | <FormItem bindTo="name" label="User Name" /> 73 | <FormItem bindTo="age" label="Age" type="integer" zeroOrPositive="true" /> 74 | </Form> 75 | </ModalDialog> 76 | <NavPanel> 77 | <NavLink label="Users" to="/" icon="user" /> 78 | </NavPanel> 79 | <Pages> 80 | <Page url="/"> 81 | <variable name="employeeId" value="{ 123 }" /> 82 | <Card 83 | avatarUrl="https://i.pravatar.cc/100" 84 | title="Leslie Peters" 85 | subtitle="Executive Manager"> 86 | Leslie is pretty smart when it comes to business. 87 | <Button label="Details" onClick="dialog.open(employeeId)" /> 88 | </Card> 89 | </Page> 90 | </Pages> 91 | </App> 92 | ``` 93 | 94 | 95 | `ModalDialog` supports a few kinds of customization. For example, you can hide the close button displayed in the top-right dialog corner and add a restyled title to dialogs. 96 | 97 | ```xmlui-pg display height="220px" 98 | <App> 99 | <Button label="Open Dialog" onClick="dialog.open()" /> 100 | <ModalDialog id="dialog" title="Example Dialog" closeButtonVisible="false"> 101 | <Button label="Close Dialog" onClick="dialog.close()" /> 102 | </ModalDialog> 103 | </App> 104 | ``` 105 | 106 | 107 | See the [ModalDialog](/components/ModalDialog) reference for all properties and events. 108 | ``` -------------------------------------------------------------------------------- /xmlui/src/components/Accordion/AccordionItemNative.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import { 2 | type ForwardedRef, 3 | forwardRef, 4 | type ReactNode, 5 | useEffect, 6 | useId, 7 | useMemo, 8 | useState, 9 | } from "react"; 10 | import * as RAccordion from "@radix-ui/react-accordion"; 11 | import classnames from "classnames"; 12 | 13 | import styles from "../../components/Accordion/Accordion.module.scss"; 14 | 15 | import { useAccordionContext } from "../../components/Accordion/AccordionContext"; 16 | import Icon from "../../components/Icon/IconNative"; 17 | 18 | function defaultRenderer(header: string) { 19 | return <div>{header}</div>; 20 | } 21 | 22 | type Props = { 23 | id: string; 24 | /** 25 | * The header of the accordion. 26 | */ 27 | header: string; 28 | 29 | headerRenderer?: (header: string) => ReactNode; 30 | 31 | /** 32 | * The content of the accordion. 33 | */ 34 | content: ReactNode; 35 | 36 | initiallyExpanded?: boolean; 37 | 38 | style?: React.CSSProperties; 39 | className?: string; 40 | }; 41 | 42 | export const defaultProps: Pick<Props, "initiallyExpanded" | "headerRenderer"> = { 43 | initiallyExpanded: false, 44 | headerRenderer: defaultRenderer, 45 | }; 46 | 47 | export const AccordionItemComponent = forwardRef(function AccordionItemComponent( 48 | { 49 | id, 50 | header, 51 | headerRenderer = defaultProps.headerRenderer, 52 | content, 53 | initiallyExpanded = defaultProps.initiallyExpanded, 54 | style, 55 | className, 56 | ...rest 57 | }: Props, 58 | forwardedRef: ForwardedRef<HTMLDivElement>, 59 | ) { 60 | const generatedId = useId(); 61 | const itemId = useMemo(() => (id ? `${id}` : generatedId), [id, generatedId]); 62 | const triggerId = useMemo(() => `trigger_${itemId}`, [itemId]); 63 | const { 64 | rotateExpanded, 65 | expandedItems, 66 | hideIcon, 67 | expandedIcon, 68 | collapsedIcon, 69 | triggerPosition, 70 | expandItem, 71 | register, 72 | unRegister, 73 | } = useAccordionContext(); 74 | const expanded = useMemo(() => (expandedItems ?? []).includes(itemId), [itemId, expandedItems]); 75 | const [initialised, setInitialised] = useState(false); 76 | 77 | useEffect(() => { 78 | if (!initialised) { 79 | setInitialised(true); 80 | if (initiallyExpanded) { 81 | expandItem(itemId); 82 | } 83 | } 84 | }, [expandItem, itemId, initiallyExpanded, initialised]); 85 | 86 | useEffect(() => { 87 | register(triggerId); 88 | }, [register, triggerId]); 89 | 90 | useEffect(() => { 91 | return () => { 92 | unRegister(triggerId); 93 | }; 94 | }, [triggerId, unRegister]); 95 | 96 | return ( 97 | <RAccordion.Item 98 | id={itemId} 99 | key={itemId} 100 | value={itemId} 101 | className={classnames(styles.item, className)} 102 | ref={forwardedRef} 103 | style={style} 104 | > 105 | <RAccordion.Header className={styles.header}> 106 | <RAccordion.Trigger 107 | {...rest} 108 | id={triggerId} 109 | className={classnames(styles.trigger, { 110 | [styles.triggerStart]: triggerPosition === "start", 111 | })} 112 | > 113 | {headerRenderer(header)} 114 | {!hideIcon && ( 115 | <span 116 | style={{ 117 | transform: expanded && !expandedIcon ? `rotate(${rotateExpanded})` : "rotate(0deg)", 118 | transition: "transform 300ms cubic-bezier(0.87, 0, 0.13, 1)", 119 | }} 120 | > 121 | <Icon 122 | name={!expanded ? collapsedIcon : expandedIcon || collapsedIcon} 123 | className={styles.chevron} 124 | aria-hidden="true" 125 | /> 126 | </span> 127 | )} 128 | </RAccordion.Trigger> 129 | </RAccordion.Header> 130 | <RAccordion.Content className={styles.contentWrapper}> 131 | <div className={styles.content}>{content}</div> 132 | </RAccordion.Content> 133 | </RAccordion.Item> 134 | ); 135 | }); 136 | ``` -------------------------------------------------------------------------------- /xmlui/src/components/CodeBlock/CodeBlock.module.scss: -------------------------------------------------------------------------------- ```scss 1 | @use "../../components-core/theming/themes" as t; 2 | 3 | $themeVars: (); 4 | @function createThemeVar($componentVariable) { 5 | $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global; 6 | @return t.getThemeVar($themeVars, $componentVariable); 7 | } 8 | 9 | $themeVars: t.composePaddingVars($themeVars, "CodeBlock"); 10 | $themeVars: t.composeBorderVars($themeVars, "CodeBlock"); 11 | $backgroundColor-CodeBlock: createThemeVar("backgroundColor-CodeBlock"); 12 | $backgroundColor-CodeBlock-header: createThemeVar("backgroundColor-CodeBlock-header"); 13 | $color-CodeBlock-headerSeparator: createThemeVar("color-CodeBlock-headerSeparator"); 14 | $marginTop-CodeBlock: createThemeVar("marginTop-CodeBlock"); 15 | $marginBottom-CodeBlock: createThemeVar("marginBottom-CodeBlock"); 16 | $backgroundColor-CodeBlock-highlightRow: createThemeVar("backgroundColor-CodeBlock-highlightRow"); 17 | $backgroundColor-CodeBlock-highlightString: createThemeVar("backgroundColor-CodeBlock-highlightString"); 18 | $borderColor-CodeBlock-highlightString-emphasis: createThemeVar("borderColor-CodeBlock-highlightString-emphasis"); 19 | $borderRadius-CodeBlock: createThemeVar("borderRadius-CodeBlock"); 20 | $border-CodeBlock: createThemeVar("border-CodeBlock"); 21 | $boxShadow-CodeBlock: createThemeVar("boxShadow-CodeBlock"); 22 | $height-CodeBlock: createThemeVar("height-CodeBlock"); 23 | $paddingHorizontal-content-CodeBlock: createThemeVar("paddingHorizontal-content-CodeBlock"); 24 | $paddingVertical-content-CodeBlock: createThemeVar("paddingVertical-content-CodeBlock"); 25 | 26 | @layer components { 27 | .codeBlock { 28 | @include t.borderVars($themeVars, "CodeBlock"); 29 | @include t.paddingVars($themeVars, "CodeBlock"); 30 | margin-top: $marginTop-CodeBlock; 31 | margin-bottom: $marginBottom-CodeBlock; 32 | background-color: $backgroundColor-CodeBlock; 33 | height: $height-CodeBlock; 34 | border-radius: $borderRadius-CodeBlock; 35 | //overflow: hidden; 36 | border: $border-CodeBlock; 37 | box-shadow: $boxShadow-CodeBlock; 38 | } 39 | 40 | .copyButton { 41 | opacity: 0.7; 42 | 43 | &:hover { 44 | opacity: 1; 45 | } 46 | } 47 | 48 | .codeBlockHeader { 49 | padding: t.$space-1; 50 | padding-left: t.$space-3; 51 | background-color: $backgroundColor-CodeBlock-header; 52 | border-bottom: $color-CodeBlock-headerSeparator solid 2px; 53 | font-size: t.$fontSize-sm; 54 | border-start-start-radius: t.getThemeVar($themeVars, "borderStartStartRadius-CodeBlock"); 55 | border-start-end-radius: t.getThemeVar($themeVars, "borderStartEndRadius-CodeBlock"); 56 | } 57 | 58 | .codeBlockContent { 59 | padding: $paddingVertical-content-CodeBlock $paddingHorizontal-content-CodeBlock; 60 | position: relative; 61 | display: flex; 62 | //align-items: center; 63 | min-height: 48px; 64 | height: 100%; 65 | overflow: auto; 66 | 67 | .codeBlockCopyButton { 68 | position: absolute; 69 | top: t.$space-1_5; 70 | right: t.$space-1_5; 71 | z-index: 1; 72 | display: none; 73 | background-color: $backgroundColor-CodeBlock; 74 | } 75 | 76 | &:hover { 77 | .codeBlockCopyButton { 78 | display: block; 79 | } 80 | } 81 | 82 | pre { 83 | flex-grow: 1; 84 | } 85 | } 86 | 87 | :global { 88 | .codeBlockHighlightRow { 89 | background-color: $backgroundColor-CodeBlock-highlightRow; 90 | } 91 | 92 | .codeBlockHighlightString { 93 | background-color: $backgroundColor-CodeBlock-highlightString; 94 | padding: 1px; 95 | } 96 | 97 | .codeBlockHighlightStringEmphasis { 98 | border: 2px solid $borderColor-CodeBlock-highlightString-emphasis; 99 | border-radius: 2px; 100 | padding: 1px; 101 | } 102 | } 103 | } 104 | 105 | 106 | :export { 107 | themeVars: t.json-stringify($themeVars); 108 | } 109 | ``` -------------------------------------------------------------------------------- /docs/content/components/Bookmark.md: -------------------------------------------------------------------------------- ```markdown 1 | # Bookmark [#bookmark] 2 | 3 | As its name suggests, this component places a bookmark into its parent component's view. The component has an `id` that you can use in links to navigate (scroll to) the bookmark's location. 4 | 5 | > [!INFO] 6 | > Pop out the examples in this article to view them on full screen. 7 | 8 | ## Using Bookmark [#using-bookmark] 9 | 10 | Use `Bookmark` as a standalone tag or wrap children with it. 11 | 12 | > [!INFO] 13 | > We suggest using a standalone bookmark, which does not increase the nesting depth of the source code, whenever possible. Note that a standalone bookmark will act as an additional child for its parent component, which can affect the layout (a `Stack` puts `gap`s between `Bookmark`s too). 14 | 15 | ### Standalone [#standalone] 16 | 17 | Add an `id` property to `Bookmark` instances and use the same identifiers in links with hash tags, as the following example shows: 18 | 19 | ```xmlui-pg copy display height="320px" name="Example: standalone Bookmark" 20 | ---app display copy 21 | <App layout="vertical-full-header" scrollWholePage="false"> 22 | <NavPanel> 23 | <Link to="/#red">Jump to red</Link> 24 | <Link to="/#green">Jump to green</Link> 25 | <Link to="/#blue">Jump to blue</Link> 26 | </NavPanel> 27 | <Pages> 28 | <Page url="/"> 29 | <Bookmark id="red"> 30 | <VStack height="200px" backgroundColor="red" /> 31 | </Bookmark> 32 | <Bookmark id="green"> 33 | <VStack height="200px" backgroundColor="green" /> 34 | </Bookmark> 35 | <Bookmark id="blue"> 36 | <VStack height="200px" backgroundColor="blue" /> 37 | </Bookmark> 38 | </Page> 39 | </Pages> 40 | </App> 41 | ---desc 42 | Clicking a link scrolls the bookmarked component adjacent to the corresponding `Bookmark` tag into the view: 43 | ``` 44 | 45 | ### With nested children [#with-nested-children] 46 | 47 | Alternatively, you can nest components into `Bookmark`: 48 | 49 | ```xmlui-pg copy display height="320px" name="Example: Bookmark with nested children" 50 | ---app display copy 51 | <App layout="vertical-full-header" scrollWholePage="false"> 52 | <NavPanel> 53 | <Link to="/#red">Jump to red</Link> 54 | <Link to="/#green">Jump to green</Link> 55 | <Link to="/#blue">Jump to blue</Link> 56 | </NavPanel> 57 | <Pages> 58 | <Page url="/"> 59 | <Bookmark id="red"> 60 | <VStack height="200px" backgroundColor="red" /> 61 | </Bookmark> 62 | <Bookmark id="green"> 63 | <VStack height="200px" backgroundColor="green" /> 64 | </Bookmark> 65 | <Bookmark id="blue"> 66 | <VStack height="200px" backgroundColor="blue" /> 67 | </Bookmark> 68 | </Page> 69 | </Pages> 70 | </App> 71 | ---desc 72 | You can try; this example works like the previous one: 73 | ``` 74 | 75 | ## Properties [#properties] 76 | 77 | ### `id` [#id] 78 | 79 | The unique identifier of the bookmark. You can use this identifier in links to navigate to this component's location. If this identifier is not set, you cannot programmatically visit this bookmark. 80 | 81 | ### `level` (default: 1) [#level-default-1] 82 | 83 | The level of the bookmark. The level is used to determine the bookmark's position in the table of contents. 84 | 85 | ### `omitFromToc` (default: false) [#omitfromtoc-default-false] 86 | 87 | If true, this bookmark will be excluded from the table of contents. 88 | 89 | ### `title` [#title] 90 | 91 | Defines the text to display the bookmark in the table of contents. If this property is empty, the text falls back to the value of `id`. 92 | 93 | ## Events [#events] 94 | 95 | This component does not have any events. 96 | 97 | ## Exposed Methods [#exposed-methods] 98 | 99 | ### `scrollIntoView` [#scrollintoview] 100 | 101 | Scrolls the bookmark into view. 102 | 103 | **Signature**: `scrollIntoView()` 104 | 105 | ## Styling [#styling] 106 | 107 | This component does not have any styles. 108 | ``` -------------------------------------------------------------------------------- /xmlui/src/components-core/loader/ExternalDataLoader.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import { useCallback } from "react"; 2 | 3 | import type { 4 | LoaderErrorFn, 5 | LoaderInProgressChangedFn, 6 | LoaderLoadedFn, 7 | } from "../abstractions/LoaderRenderer"; 8 | import type { ComponentDef} from "../../abstractions/ComponentDefs"; 9 | import type { ContainerState } from "../rendering/ContainerWrapper"; 10 | import { removeNullProperties } from "../utils/misc"; 11 | import { extractParam } from "../utils/extractParam"; 12 | import { createLoaderRenderer } from "../renderers"; 13 | import { useAppContext } from "../AppContext"; 14 | import { Loader } from "./Loader"; 15 | import { createMetadata, d } from "../../components/metadata-helpers"; 16 | 17 | /** 18 | * Properties of the Data loader component 19 | */ 20 | type ExternalDataLoaderProps = { 21 | loader: ExternalDataLoaderDef; 22 | state: ContainerState; 23 | doNotRemoveNulls?: boolean; 24 | loaderInProgressChanged: LoaderInProgressChangedFn; 25 | loaderIsRefetchingChanged: LoaderInProgressChangedFn; 26 | loaderLoaded: LoaderLoadedFn; 27 | loaderError: LoaderErrorFn; 28 | structuralSharing?: boolean; 29 | }; 30 | 31 | /** 32 | * Represents a non-displayed React component, which handles the specified API loader 33 | */ 34 | function ExternalDataLoader({ 35 | loader, 36 | loaderInProgressChanged, 37 | loaderIsRefetchingChanged, 38 | loaderError, 39 | loaderLoaded, 40 | state, 41 | doNotRemoveNulls, 42 | structuralSharing = true, 43 | }: ExternalDataLoaderProps) { 44 | const appContext = useAppContext(); 45 | const method = extractParam(state, loader.props.method, appContext); 46 | const headers: Record<string, string> = extractParam(state, loader.props.headers, appContext); 47 | const data = extractParam(state, loader.props.data, appContext); 48 | 49 | const url = extractParam(state, loader.props.url, appContext); 50 | const urlLoadable = !!url; 51 | 52 | const doLoad = useCallback(async () => { 53 | if (!urlLoadable) { 54 | return; 55 | } 56 | const response = await fetch(url, { 57 | method: method || "POST", 58 | headers: { 59 | "Content-Type": "application/json", 60 | ...headers, 61 | }, 62 | body: JSON.stringify(data), 63 | }); 64 | const responseObj = await response.json(); 65 | if (!doNotRemoveNulls) { 66 | removeNullProperties(responseObj); 67 | } 68 | return responseObj; 69 | }, [urlLoadable, headers, data, url, method, doNotRemoveNulls]); 70 | 71 | return ( 72 | <Loader 73 | state={state} 74 | loader={loader} 75 | loaderInProgressChanged={loaderInProgressChanged} 76 | loaderIsRefetchingChanged={loaderIsRefetchingChanged} 77 | loaderLoaded={loaderLoaded} 78 | loaderError={loaderError} 79 | loaderFn={doLoad} 80 | structuralSharing={structuralSharing} 81 | /> 82 | ); 83 | } 84 | 85 | export const ExternalDataLoaderMd = createMetadata({ 86 | status: "stable", 87 | description: `Represents a loader that calls an API through an HTTP/HTTPS GET request`, 88 | props: { 89 | url: d("URL segment to use in the GET request"), 90 | method: d("The HTTP method to use"), 91 | headers: d("Headers to send with the request"), 92 | data: d("The body of the request to be sent as JSON"), 93 | }, 94 | }); 95 | 96 | type ExternalDataLoaderDef = ComponentDef<typeof ExternalDataLoaderMd>; 97 | 98 | export const externalDataLoaderRenderer = createLoaderRenderer( 99 | "ExternalDataLoader", 100 | ({ loader, state, loaderInProgressChanged, loaderIsRefetchingChanged, loaderError, loaderLoaded }) => { 101 | return ( 102 | <ExternalDataLoader 103 | loader={loader} 104 | state={state} 105 | loaderInProgressChanged={loaderInProgressChanged} 106 | loaderIsRefetchingChanged={loaderIsRefetchingChanged} 107 | loaderLoaded={loaderLoaded} 108 | loaderError={loaderError} 109 | /> 110 | ); 111 | }, 112 | ExternalDataLoaderMd, 113 | ); 114 | ``` -------------------------------------------------------------------------------- /xmlui/scripts/generate-docs/logger.mjs: -------------------------------------------------------------------------------- ``` 1 | /** 2 | * Logger class. 3 | * - severity indicates message importance 4 | * - levels control what messages will be logged 5 | */ 6 | class Logger { 7 | // TODO: make class a singleton 8 | 9 | static severity = { 10 | info: "info", 11 | warning: "warning", 12 | error: "error", 13 | }; 14 | 15 | constructor(...levels) { 16 | this.setLevels(...levels); 17 | } 18 | 19 | isValidSeverity(severity) { 20 | return Object.keys(Logger.severity).includes(severity); 21 | } 22 | 23 | isValidLevel(level) { 24 | return Object.keys(LOGGER_LEVELS).includes(level); 25 | } 26 | 27 | defaultSeverity = Logger.severity.error; 28 | defaultLogLevel = LOGGER_LEVELS.all; 29 | 30 | setLevels(...levels) { 31 | levels = Array.from(new Set(levels)); 32 | let validLevels = levels.filter((level) => this.isValidLevel(level)); 33 | if (validLevels.length === 0) { 34 | this._logError(`No valid log levels provided. Using defaults: ${this.defaultLogLevel}.`); 35 | validLevels = [this.defaultLogLevel]; 36 | } 37 | 38 | this.info = this._noop; 39 | this.warning = this._noop; 40 | this.error = this._noop; 41 | 42 | if (validLevels.find((level) => level === LOGGER_LEVELS.none)) { 43 | return; 44 | } 45 | if (validLevels.find((level) => level === LOGGER_LEVELS.all)) { 46 | this.info = this._logInfo; 47 | this.warning = this._logWarning; 48 | this.error = this._logError; 49 | return; 50 | } 51 | for (const level of validLevels) { 52 | this[level] = this[`_log${level.charAt(0).toUpperCase() + level.slice(1)}`]; 53 | } 54 | } 55 | 56 | log(severity = Logger.severity.info, ...args) { 57 | if (!this.isValidSeverity(severity)) { 58 | this.warning( 59 | `Invalid log severity: ${severity}. Defaulting to message severity: ${this.defaultSeverity}.`, 60 | ); 61 | severity = this.defaultSeverity; 62 | } 63 | if (severity === Logger.severity.info) { 64 | this.info(...args); 65 | } else if (severity === Logger.severity.warning) { 66 | this.warning(...args); 67 | } else if (severity === Logger.severity.error) { 68 | this.error(...args); 69 | } 70 | } 71 | 72 | info(...args) {} 73 | warning(...args) {} 74 | warn(...args) { 75 | // Alias for warning() for consistency 76 | this.warning(...args); 77 | } 78 | error(...args) {} 79 | 80 | _logInfo(...args) { 81 | console.log("[INFO]", ...args); 82 | } 83 | 84 | _logWarning(...args) { 85 | console.log("[WARN]", ...args); 86 | } 87 | 88 | _logError(...args) { 89 | if (args[0] instanceof Error) { 90 | console.error("[ERR]", args[0].message + "\n", args[0].stack.split("\n").slice(1).join("\n ")); 91 | } else { 92 | console.error("[ERR]", ...args); 93 | } 94 | } 95 | 96 | _noop(...args) {} 97 | } 98 | 99 | export const LOGGER_LEVELS = { 100 | ...Logger.severity, 101 | all: "all", 102 | none: "none", 103 | }; 104 | 105 | // --- Usable logger instance 106 | export const logger = new Logger(LOGGER_LEVELS.all); 107 | 108 | // --- Error classes 109 | 110 | export class ErrorWithSeverity extends Error { 111 | constructor(message, severity = Logger.severity.error) { 112 | super(message); 113 | this.name = "ErrorWithSeverity"; 114 | this.severity = severity; 115 | } 116 | } 117 | 118 | /** 119 | * Logs error to console depending on the type of the error thrown. 120 | * - ErrorWithSeverity type errors are logged with the severity specified. 121 | * - Other errors are logged with severity ERROR. 122 | * @param {ErrorWithSeverity | Error | string} error 123 | */ 124 | export function processError(error) { 125 | if (error instanceof ErrorWithSeverity) { 126 | // We log the stack trace only for errors with severity ERROR 127 | error.severity === Logger.severity.error 128 | ? logger.log(error.severity, error) 129 | : logger.log(error.severity, error.message); 130 | } else if (error instanceof Error) { 131 | logger.error(error); 132 | } else { 133 | logger.error(error); 134 | } 135 | } 136 | ``` -------------------------------------------------------------------------------- /xmlui/src/components/NavLink/NavLinkNative.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import type { CSSProperties, MouseEventHandler, ReactNode, Ref } from "react"; 2 | import type React from "react"; 3 | import { forwardRef, useContext, useMemo } from "react"; 4 | import { NavLink as RrdNavLink } from "@remix-run/react"; 5 | import type { To } from "react-router-dom"; 6 | import classnames from "classnames"; 7 | 8 | import styles from "./NavLink.module.scss"; 9 | import type { LinkAria, LinkTarget } from "../abstractions"; 10 | import { createUrlWithQueryParams } from "../component-utils"; 11 | import { getAppLayoutOrientation } from "../App/AppNative"; 12 | import { useAppLayoutContext } from "../App/AppLayoutContext"; 13 | import { NavPanelContext } from "../NavPanel/NavPanelNative"; 14 | import { NavGroupContext } from "../NavGroup/NavGroupContext"; 15 | 16 | // Default props for NavLink component 17 | export const defaultProps = { 18 | active: false, 19 | displayActive: true, 20 | }; 21 | 22 | type Props = { 23 | uid?: string; 24 | to?: string; 25 | target?: LinkTarget; 26 | disabled?: boolean; 27 | children?: ReactNode; 28 | displayActive?: boolean; 29 | forceActive?: boolean; 30 | vertical?: boolean; 31 | style?: CSSProperties; 32 | className?: string; 33 | onClick?: MouseEventHandler; 34 | icon?: React.ReactNode; 35 | accessibilityProps?: any; 36 | } & Pick<React.HTMLAttributes<HTMLAnchorElement>, LinkAria>; 37 | 38 | export const NavLink = forwardRef(function NavLink( 39 | { 40 | /* eslint-disable react/prop-types */ 41 | uid, 42 | children, 43 | disabled, 44 | to, 45 | displayActive = defaultProps.displayActive, 46 | vertical, 47 | style, 48 | onClick, 49 | icon, 50 | forceActive, 51 | className, 52 | ...rest 53 | }: Props, 54 | ref: Ref<any>, 55 | ) { 56 | const appLayoutContext = useAppLayoutContext(); 57 | const layoutIsVertical = 58 | !!appLayoutContext && getAppLayoutOrientation(appLayoutContext.layout).includes("vertical"); 59 | const navPanelContext = useContext(NavPanelContext); 60 | const inDrawer = navPanelContext?.inDrawer; 61 | 62 | const { level } = useContext(NavGroupContext); 63 | let safeVertical = vertical; 64 | 65 | if (safeVertical === undefined) { 66 | safeVertical = layoutIsVertical || inDrawer; 67 | } 68 | const smartTo = useMemo(() => { 69 | if (to) { 70 | return createUrlWithQueryParams(to) as To; 71 | } 72 | }, [to]) as To; 73 | 74 | const styleObj = useMemo(() => { 75 | return { 76 | "--nav-link-level": layoutIsVertical ? level + 1 : 0, 77 | ...style, 78 | }; 79 | }, [level, style, layoutIsVertical]); 80 | 81 | const baseClasses = classnames(styles.content, styles.base, className, { 82 | [styles.disabled]: disabled, 83 | [styles.vertical]: safeVertical, 84 | [styles.includeHoverIndicator]: displayActive, 85 | [styles.navItemActive]: displayActive && forceActive, 86 | }); 87 | 88 | let innerContent = ( 89 | <div className={styles.innerContent}> 90 | {icon} 91 | {children} 92 | </div> 93 | ); 94 | let content: React.ReactNode = null; 95 | if (disabled || !smartTo) { 96 | content = ( 97 | <button 98 | {...rest} 99 | ref={ref} 100 | onClick={onClick} 101 | className={baseClasses} 102 | style={styleObj} 103 | disabled={disabled} 104 | > 105 | {innerContent} 106 | </button> 107 | ); 108 | } else { 109 | content = ( 110 | <RrdNavLink 111 | {...rest} 112 | id={uid} 113 | ref={ref} 114 | to={smartTo as To} 115 | style={styleObj} 116 | onClick={onClick} 117 | className={({ isActive }) => 118 | classnames(baseClasses, { 119 | [styles.displayActive]: displayActive, 120 | [styles.navItemActive]: displayActive && (isActive || forceActive), 121 | "xmlui-navlink-active": isActive || forceActive, 122 | }) 123 | } 124 | > 125 | {innerContent} 126 | </RrdNavLink> 127 | ); 128 | } 129 | 130 | return content; 131 | }); 132 | ``` -------------------------------------------------------------------------------- /xmlui/src/parsers/scripting/code-behind-collect.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { 2 | T_ARROW_EXPRESSION, 3 | T_FUNCTION_DECLARATION, 4 | T_VAR_STATEMENT, 5 | type ArrowExpression, 6 | type CodeDeclaration, 7 | type CollectedDeclarations, 8 | type Expression, 9 | type FunctionDeclaration, 10 | type Statement, 11 | } from "../../components-core/script-runner/ScriptingSourceTree"; 12 | import type { VisitorState } from "./tree-visitor"; 13 | import { visitNode } from "./tree-visitor"; 14 | import { isModuleErrors, parseScriptModule } from "./modules"; 15 | 16 | export const PARSED_MARK_PROP = "__PARSED__"; 17 | 18 | // --- Collect module statements from a parsed module 19 | export function collectCodeBehindFromSource( 20 | moduleName: string, 21 | source: string 22 | ): CollectedDeclarations { 23 | const result: CollectedDeclarations = { 24 | vars: {}, 25 | moduleErrors: {}, 26 | functions: {}, 27 | }; 28 | 29 | const collectedFunctions: Record<string, CodeDeclaration> = {}; 30 | 31 | // --- Parse the module (recursively, including imported modules) in restrictive mode 32 | const parsedModule = parseScriptModule(moduleName, source); 33 | if (isModuleErrors(parsedModule)) { 34 | return { ...result, moduleErrors: parsedModule }; 35 | } 36 | 37 | // --- Collect statements from the module 38 | parsedModule.statements.forEach((stmt) => { 39 | switch (stmt.type) { 40 | case T_VAR_STATEMENT: 41 | stmt.decls.forEach((decl) => { 42 | if (decl.id.name in result.vars) { 43 | throw new Error(`Duplicated var declaration: '${decl.id.name}'`); 44 | } 45 | result.vars[decl.id.name] = { 46 | [PARSED_MARK_PROP]: true, 47 | tree: decl.expr, 48 | }; 49 | }); 50 | break; 51 | case T_FUNCTION_DECLARATION: 52 | addFunctionDeclaration(stmt); 53 | break; 54 | default: 55 | throw new Error(`Only reactive variable and function definitions are allowed in a code-behind module.`); 56 | } 57 | }); 58 | return result; 59 | 60 | // --- Collect function declaration data 61 | function addFunctionDeclaration(stmt: FunctionDeclaration): void { 62 | if (collectedFunctions?.[stmt.id.name] !== undefined) { 63 | return; 64 | } 65 | if (stmt.id.name in result.functions) { 66 | throw new Error(`Duplicated function declaration: '${stmt.id.name}'`); 67 | } 68 | const arrow: ArrowExpression = { 69 | type: T_ARROW_EXPRESSION, 70 | args: stmt.args.slice(), 71 | statement: stmt.stmt, 72 | // closureContext: obtainClosures({ 73 | // childThreads: [], 74 | // blocks: [{ vars: {} }], 75 | // loops: [], 76 | // breakLabelValue: -1, 77 | // }), 78 | } as ArrowExpression; 79 | 80 | collectedFunctions[stmt.id.name] = { 81 | [PARSED_MARK_PROP]: true, 82 | tree: arrow, 83 | }; 84 | result.functions[stmt.id.name] = { 85 | [PARSED_MARK_PROP]: true, 86 | tree: arrow, 87 | }; 88 | } 89 | } 90 | 91 | // --- Remove all code-behind tokens from the tree 92 | export function removeCodeBehindTokensFromTree(declarations: CollectedDeclarations): void { 93 | if (!declarations) return; 94 | 95 | const state: VisitorState = { 96 | data: null, 97 | cancel: false, 98 | skipChildren: false, 99 | }; 100 | 101 | Object.keys(declarations.vars).forEach((key) => { 102 | removeTokens(declarations.vars[key]); 103 | }); 104 | Object.keys(declarations.functions).forEach((key) => { 105 | removeTokens(declarations.functions[key]); 106 | }); 107 | 108 | function removeTokens(declaration: CodeDeclaration): void { 109 | const nodeVisitor = (before: boolean, visited: Expression | Statement, state: VisitorState) => { 110 | if (before) { 111 | if (visited) { 112 | delete visited.startToken 113 | delete visited.endToken; 114 | } 115 | } 116 | return state; 117 | }; 118 | 119 | visitNode(declaration.tree, state, nodeVisitor, nodeVisitor); 120 | } 121 | } 122 | ``` -------------------------------------------------------------------------------- /xmlui/scripts/generate-docs/error-handling.mjs: -------------------------------------------------------------------------------- ``` 1 | import { logger, ErrorWithSeverity, LOGGER_LEVELS, processError } from "./logger.mjs"; 2 | import { ERROR_HANDLING, ERROR_MESSAGES } from "./constants.mjs"; 3 | 4 | /** 5 | * Standardized error handling utilities for documentation generation scripts 6 | */ 7 | 8 | /** 9 | * Handles errors and exits gracefully with appropriate exit codes 10 | * @param {Error | ErrorWithSeverity | string} error - The error to handle 11 | * @param {number} exitCode - Optional exit code (defaults to GENERAL_ERROR) 12 | * @param {string} context - Optional context about where the error occurred 13 | */ 14 | export function handleFatalError(error, exitCode = ERROR_HANDLING.EXIT_CODES.GENERAL_ERROR, context = null) { 15 | if (context) { 16 | logger.error(`Error in ${context}:`); 17 | } 18 | 19 | processError(error); 20 | process.exit(exitCode); 21 | } 22 | 23 | /** 24 | * Handles non-fatal errors that should be logged but don't stop execution 25 | * @param {Error | ErrorWithSeverity | string} error - The error to handle 26 | * @param {string} context - Optional context about where the error occurred 27 | */ 28 | export function handleNonFatalError(error, context = null) { 29 | if (context) { 30 | logger.warn(`Warning in ${context}:`); 31 | } 32 | 33 | if (error instanceof ErrorWithSeverity) { 34 | logger.log(error.severity, error.message); 35 | } else if (error instanceof Error) { 36 | logger.warn(error.message); 37 | } else { 38 | logger.warn(error); 39 | } 40 | } 41 | 42 | /** 43 | * Validates required dependencies and throws appropriate errors 44 | * @param {Object} dependencies - Object with dependency checks 45 | * @throws {ErrorWithSeverity} If any required dependency is missing 46 | */ 47 | export function validateDependencies(dependencies) { 48 | for (const [name, value] of Object.entries(dependencies)) { 49 | if (value === undefined || value === null) { 50 | throw new ErrorWithSeverity( 51 | ERROR_MESSAGES[`NO_${name.toUpperCase()}`] || `Missing required dependency: ${name}`, 52 | LOGGER_LEVELS.error 53 | ); 54 | } 55 | } 56 | } 57 | 58 | /** 59 | * Wraps async operations with standardized error handling 60 | * @param {Function} operation - The async operation to execute 61 | * @param {string} operationName - Name of the operation for logging 62 | * @param {number} exitCode - Exit code to use if operation fails 63 | * @returns {Promise<any>} The result of the operation 64 | */ 65 | export async function withErrorHandling(operation, operationName, exitCode = ERROR_HANDLING.EXIT_CODES.GENERAL_ERROR) { 66 | try { 67 | return await operation(); 68 | } catch (error) { 69 | handleFatalError(error, exitCode, operationName); 70 | } 71 | } 72 | 73 | /** 74 | * Wraps file operations with standardized error handling 75 | * @param {Function} fileOperation - The file operation to execute 76 | * @param {string} filePath - Path of the file being operated on 77 | * @param {string} operationType - Type of operation (read, write, delete, etc.) 78 | * @returns {Promise<any>} The result of the operation 79 | */ 80 | export async function withFileErrorHandling(fileOperation, filePath, operationType) { 81 | try { 82 | return await fileOperation(); 83 | } catch (error) { 84 | const errorMessage = `${ERROR_MESSAGES.FILE_WRITE_ERROR}: ${filePath} (${operationType})`; 85 | throw new ErrorWithSeverity(errorMessage, LOGGER_LEVELS.error); 86 | } 87 | } 88 | 89 | /** 90 | * Creates a standardized error for missing metadata 91 | * @param {string} metadataType - Type of metadata that's missing 92 | * @returns {ErrorWithSeverity} 93 | */ 94 | export function createMetadataError(metadataType) { 95 | const message = ERROR_MESSAGES[`NO_${metadataType.toUpperCase()}`] || 96 | `${ERROR_MESSAGES.METADATA_LOAD_ERROR}: ${metadataType}`; 97 | return new ErrorWithSeverity(message, LOGGER_LEVELS.error); 98 | } 99 | ``` -------------------------------------------------------------------------------- /xmlui/tests/components/Tree/Tree-states.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { describe, expect, it } from "vitest"; 2 | import { NodeLoadingState } from '../../../src/components-core/abstractions/treeAbstractions'; 3 | 4 | // Helper functions to test (these would normally be extracted from TreeNative for testing) 5 | type NodeStatesMap = Map<string, NodeLoadingState>; 6 | 7 | function createNodeStateHelpers() { 8 | let nodeStates: NodeStatesMap = new Map(); 9 | 10 | const setNodeLoadingState = (nodeId: string, state: NodeLoadingState) => { 11 | nodeStates.set(nodeId, state); 12 | }; 13 | 14 | const getNodeLoadingState = (nodeId: string): NodeLoadingState => { 15 | return nodeStates.get(nodeId) || 'unloaded'; 16 | }; 17 | 18 | const clearNodeLoadingState = (nodeId: string) => { 19 | nodeStates.delete(nodeId); 20 | }; 21 | 22 | const getAllNodeStates = (): NodeStatesMap => { 23 | return new Map(nodeStates); 24 | }; 25 | 26 | const clearAllNodeStates = () => { 27 | nodeStates.clear(); 28 | }; 29 | 30 | return { 31 | setNodeLoadingState, 32 | getNodeLoadingState, 33 | clearNodeLoadingState, 34 | getAllNodeStates, 35 | clearAllNodeStates 36 | }; 37 | } 38 | 39 | describe('Tree Node States Management - Unit Tests', () => { 40 | it('should set and get node loading state', () => { 41 | const helpers = createNodeStateHelpers(); 42 | 43 | helpers.setNodeLoadingState('node1', 'loading'); 44 | expect(helpers.getNodeLoadingState('node1')).toBe('loading'); 45 | 46 | helpers.setNodeLoadingState('node1', 'loaded'); 47 | expect(helpers.getNodeLoadingState('node1')).toBe('loaded'); 48 | }); 49 | 50 | it('should return unloaded for unknown nodes', () => { 51 | const helpers = createNodeStateHelpers(); 52 | 53 | expect(helpers.getNodeLoadingState('unknown')).toBe('unloaded'); 54 | }); 55 | 56 | it('should clear node loading state', () => { 57 | const helpers = createNodeStateHelpers(); 58 | 59 | helpers.setNodeLoadingState('node1', 'loaded'); 60 | expect(helpers.getNodeLoadingState('node1')).toBe('loaded'); 61 | 62 | helpers.clearNodeLoadingState('node1'); 63 | expect(helpers.getNodeLoadingState('node1')).toBe('unloaded'); 64 | }); 65 | 66 | it('should handle multiple node states', () => { 67 | const helpers = createNodeStateHelpers(); 68 | 69 | helpers.setNodeLoadingState('node1', 'loading'); 70 | helpers.setNodeLoadingState('node2', 'loaded'); 71 | helpers.setNodeLoadingState('node3', 'unloaded'); 72 | 73 | expect(helpers.getNodeLoadingState('node1')).toBe('loading'); 74 | expect(helpers.getNodeLoadingState('node2')).toBe('loaded'); 75 | expect(helpers.getNodeLoadingState('node3')).toBe('unloaded'); 76 | 77 | const allStates = helpers.getAllNodeStates(); 78 | expect(allStates.size).toBe(3); 79 | expect(allStates.get('node1')).toBe('loading'); 80 | expect(allStates.get('node2')).toBe('loaded'); 81 | expect(allStates.get('node3')).toBe('unloaded'); 82 | }); 83 | 84 | it('should clear all node states', () => { 85 | const helpers = createNodeStateHelpers(); 86 | 87 | helpers.setNodeLoadingState('node1', 'loading'); 88 | helpers.setNodeLoadingState('node2', 'loaded'); 89 | 90 | expect(helpers.getAllNodeStates().size).toBe(2); 91 | 92 | helpers.clearAllNodeStates(); 93 | 94 | expect(helpers.getAllNodeStates().size).toBe(0); 95 | expect(helpers.getNodeLoadingState('node1')).toBe('unloaded'); 96 | expect(helpers.getNodeLoadingState('node2')).toBe('unloaded'); 97 | }); 98 | 99 | it('should validate NodeLoadingState enum values', () => { 100 | const helpers = createNodeStateHelpers(); 101 | 102 | // Test all valid enum values 103 | const validStates: NodeLoadingState[] = ['unloaded', 'loading', 'loaded']; 104 | 105 | validStates.forEach(state => { 106 | helpers.setNodeLoadingState('test', state); 107 | expect(helpers.getNodeLoadingState('test')).toBe(state); 108 | }); 109 | }); 110 | }); ``` -------------------------------------------------------------------------------- /docs/public/pages/howto/pass-data-to-a-modal-dialog.md: -------------------------------------------------------------------------------- ```markdown 1 | # Pass data to a Modal Dialog 2 | 3 | ```xmlui-pg name="Click on a team member to edit details" 4 | ---app 5 | <App> 6 | <Test /> 7 | </App> 8 | ---api 9 | { 10 | "apiUrl": "/api", 11 | "initialize": "$state.team_members = [ 12 | { id: 1, name: 'Sarah Chen', role: 'Product Manager', email: '[email protected]', avatar: 'https://i.pravatar.cc/100?u=sarah', department: 'Product', startDate: '2022-03-15' }, 13 | { id: 2, name: 'Marcus Johnson', role: 'Senior Developer', email: '[email protected]', avatar: 'https://i.pravatar.cc/100?u=marcus', department: 'Engineering', startDate: '2021-08-20' }, 14 | { id: 3, name: 'Elena Rodriguez', role: 'UX Designer', email: '[email protected]', avatar: 'https://i.pravatar.cc/100?u=elena', department: 'Design', startDate: '2023-01-10' } 15 | ]", 16 | "operations": { 17 | "get_team_members": { 18 | "url": "/team_members", 19 | "method": "get", 20 | "handler": "return $state.team_members" 21 | } 22 | } 23 | } 24 | ---comp display 25 | <Component name="Test"> 26 | 27 | <DataSource 28 | id="team_members" 29 | url="/api/team_members" 30 | /> 31 | 32 | <ModalDialog id="memberDetailsDialog" title="Team Member Details"> 33 | <Theme backgroundColor-overlay="$color-surface-900"> 34 | <VStack gap="1rem" padding="1rem"> 35 | <!-- Avatar and Basic Info --> 36 | <HStack gap="1rem" alignItems="center"> 37 | <Avatar 38 | url="{$param.avatar}" 39 | size="lg" 40 | name="{$param.name}" 41 | /> 42 | <VStack gap="0.25rem" alignItems="start"> 43 | <Text variant="strong" fontSize="1.2rem">{$param.name}</Text> 44 | <Text variant="caption">{$param.role}</Text> 45 | <Text variant="caption" color="blue">{$param.email}</Text> 46 | </VStack> 47 | </HStack> 48 | 49 | <!-- Details Card --> 50 | <Card padding="1rem"> 51 | <VStack gap="0.5rem"> 52 | <HStack> 53 | <Text variant="strong">Department:</Text> 54 | <Text>{$param.department}</Text> 55 | </HStack> 56 | <HStack> 57 | <Text variant="strong">Start Date:</Text> 58 | <Text>{$param.startDate}</Text> 59 | </HStack> 60 | <HStack> 61 | <Text variant="strong">Employee ID:</Text> 62 | <Text>#{$param.id}</Text> 63 | </HStack> 64 | </VStack> 65 | </Card> 66 | 67 | <!-- Actions --> 68 | <HStack gap="0.5rem"> 69 | <Button 70 | label="Send Email" 71 | size="sm" 72 | onClick="console.log('Email to:', $param.email)" 73 | /> 74 | <Button 75 | label="View Calendar" 76 | size="sm" 77 | variant="secondary" 78 | onClick="console.log('Calendar for:', $param.name)" 79 | /> 80 | </HStack> 81 | </VStack> 82 | </Theme> 83 | </ModalDialog> 84 | 85 | <Text variant="strong" marginBottom="1rem">Team Directory</Text> 86 | 87 | <VStack gap="0.5rem"> 88 | <Items data="{team_members}"> 89 | <Card 90 | padding="1rem" 91 | cursor="pointer" 92 | onClick="{ 93 | memberDetailsDialog.open({ 94 | id: $item.id, 95 | name: $item.name, 96 | role: $item.role, 97 | email: $item.email, 98 | avatar: $item.avatar, 99 | department: $item.department, 100 | startDate: $item.startDate 101 | }) 102 | }" 103 | > 104 | <HStack gap="1rem" alignItems="center"> 105 | <Avatar 106 | url="{$item.avatar}" 107 | size="sm" 108 | name="{$item.name}" 109 | /> 110 | <VStack gap="0.25rem" alignItems="start"> 111 | <Text variant="strong">{$item.name}</Text> 112 | <Text variant="caption">{$item.role} - {$item.department}</Text> 113 | </VStack> 114 | </HStack> 115 | </Card> 116 | </Items> 117 | </VStack> 118 | 119 | </Component> 120 | ``` 121 | ``` -------------------------------------------------------------------------------- /xmlui/src/components/Heading/Heading.md: -------------------------------------------------------------------------------- ```markdown 1 | %-DESC-START 2 | 3 | **Key features:** 4 | - **Semantic levels**: Choose from h1 through h6 for proper document structure and accessibility 5 | - **Text overflow control**: Automatic ellipses and line limiting for long headings 6 | - **Anchor generation**: Optional hover anchors for deep linking to specific sections 7 | 8 | For the shorthand versions see: [H1](./H1), [H2](./H2), [H3](./H3), [H4](./H4), [H5](./H5), [H6](./H6). 9 | 10 | ```xmlui-pg copy display name="Example: Headings with levels" 11 | <App> 12 | <Heading level="h1" value="Heading Level 1" /> 13 | <Text>Text following H1</Text> 14 | <Heading level="h2" value="Heading Level 2" /> 15 | <Text>Text following H2</Text> 16 | <Heading level="h3" value="Heading Level 3" /> 17 | <Text>Text following H3</Text> 18 | <Heading level="h4" value="Heading Level 4" /> 19 | <Text>Text following H4</Text> 20 | <Heading level="h5" value="Heading Level 5" /> 21 | <Text>Text following H5</Text> 22 | <Heading level="h6" value="Heading Level 6" /> 23 | <Text>Text following H6</Text> 24 | </App> 25 | ``` 26 | 27 | %-DESC-END 28 | 29 | %-PROP-START value 30 | 31 | ```xmlui-pg copy display name="Example: value" 32 | <App> 33 | <Heading value="This is level 3 (value)" level="h3" /> 34 | <Heading level="h3">This is level 3 (child)</Heading> 35 | <Heading value="Value" level="h3"><Icon name="trash" /></Heading> 36 | </App> 37 | ``` 38 | 39 | %-PROP-END 40 | 41 | %-PROP-START level 42 | 43 | | Value | Description | 44 | | :---- | :---------------------------------------------------- | 45 | | `h1` | **(default)** Equivalent to the `<h1 />` HTML element | 46 | | `h2` | Equivalent to the `<h2 />` HTML element | 47 | | `h3` | Equivalent to the `<h3 />` HTML element | 48 | | `h4` | Equivalent to the `<h4 />` HTML element | 49 | | `h5` | Equivalent to the `<h5 />` HTML element | 50 | | `h6` | Equivalent to the `<h6 />` HTML element | 51 | 52 | For a visual example, see the component description. 53 | 54 | %-PROP-END 55 | 56 | %-PROP-START maxLines 57 | 58 | ```xmlui-pg copy display name="Example: maxLines" 59 | <App> 60 | <H2 61 | maxWidth="160px" 62 | backgroundColor="cyan" 63 | value="A long heading text that will likely overflow" maxLines="2" /> 64 | </App> 65 | ``` 66 | 67 | %-PROP-END 68 | 69 | %-PROP-START preserveLinebreaks 70 | 71 | ```xmlui-pg copy display name="Example: preserveLinebreaks" 72 | ---app copy display {5} 73 | <App> 74 | <HStack> 75 | <H3 76 | width="200px" 77 | backgroundColor="cyan" 78 | preserveLinebreaks="true" 79 | value="(preserve) This long text 80 | with several line breaks 81 | does not fit into a viewport with a 200-pixel width." /> 82 | <H3 83 | width="200px" 84 | backgroundColor="cyan" 85 | value="(do not preserve) This long text 86 | with several line breaks 87 | does not fit into a viewport with a 200-pixel width." /> 88 | </HStack> 89 | </App> 90 | ---desc 91 | You can observe the effect of using `preserveLinebreaks`: 92 | ``` 93 | 94 | >[!INFO] 95 | > Remember to use the `value` property of `Heading`. 96 | > Linebreaks are converted to spaces when nesting the text in the `Heading` component. 97 | 98 | %-PROP-END 99 | 100 | %-PROP-START ellipses 101 | 102 | ```xmlui-pg copy {4} display name="Example: ellipses" 103 | <App> 104 | <VStack width="200px"> 105 | <H3 106 | backgroundColor="cyan" 107 | maxLines="1" 108 | ellipses="false"> 109 | Though this long text does is about to crop! 110 | </H3> 111 | <H3 112 | backgroundColor="cyan" 113 | maxLines="1"> 114 | Though this long text does is about to crop! 115 | </H3> 116 | </VStack> 117 | </App> 118 | ``` 119 | 120 | %-PROP-END 121 | 122 | %-PROP-START showAnchor 123 | 124 | If this property is not set, the engine checks if `showHeadingAnchors` flag is turned on in the global configuration (in the `appGlobals` configuration object) and displays the heading anchor accordingly. 125 | 126 | %-PROP-END ``` -------------------------------------------------------------------------------- /docs/public/pages/helper-tags.md: -------------------------------------------------------------------------------- ```markdown 1 | # Helper Tags 2 | 3 | Helper tags provide alternative XML markup syntax for declaring variables, properties, and event handlers in XMLUI. 4 | 5 | ## variable 6 | 7 | Use `<variable>` as an alternative to the `var.` attribute prefix syntax. Instead of this: 8 | 9 | ```xmlui 10 | <App var.count="{0}" var.message="Hello, World!"> 11 | <Text>{message}</Text> 12 | <Button onClick="count++" label="Count: {count}" /> 13 | </App> 14 | ``` 15 | 16 | You can do this: 17 | 18 | ```xmlui 19 | <App> 20 | <variable name="count" value="{0}" /> 21 | <variable name="message" value="Hello, World!" /> 22 | <Text>{message}</Text> 23 | <Button onClick="count++" label="Count: {count}" /> 24 | </App> 25 | ``` 26 | 27 | ## property 28 | 29 | Use `<property>` to declare properties with nested markup 30 | 31 | ```xmlui 32 | <Form data='{{ name: "", email: "" }}'> 33 | <FormItem bindTo="name" label="Name" /> 34 | <FormItem bindTo="email" label="Email" /> 35 | 36 | <property name="buttonRowTemplate"> 37 | <HStack gap="1rem"> 38 | <Button type="submit" label="Save" variant="primary" /> 39 | <Button type="reset" label="Cancel" variant="secondary" /> 40 | </HStack> 41 | </property> 42 | </Form> 43 | ``` 44 | 45 | App headers can use logo templates for custom branding: 46 | 47 | ```xmlui 48 | <AppHeader> 49 | <property name="logoTemplate"> 50 | <HStack verticalAlignment="center" gap="0.5rem"> 51 | <Icon name="star" size="lg" color="primary" /> 52 | <H2>My App</H2> 53 | </HStack> 54 | </property> 55 | </AppHeader> 56 | ``` 57 | 58 | Lists and other data-driven components can use item templates: 59 | 60 | ```xmlui 61 | <List data="{users}"> 62 | <property name="itemTemplate"> 63 | <HStack gap="1rem" padding="0.5rem"> 64 | <Avatar url="{$item.avatar}" name="{$item.name}" /> 65 | <VStack> 66 | <Text weight="bold">{$item.name}</Text> 67 | <Text color="muted">{$item.email}</Text> 68 | </VStack> 69 | </HStack> 70 | </property> 71 | </List> 72 | ``` 73 | 74 | Dropdown components can have rich option layouts: 75 | 76 | ```xmlui 77 | <Select data="{countries}" bindTo="selectedCountry"> 78 | <property name="optionTemplate"> 79 | <HStack gap="0.5rem"> 80 | <Image src="{$item.flag}" width="20px" height="15px" /> 81 | <Text>{$item.name}</Text> 82 | <Text color="muted">({$item.code})</Text> 83 | </HStack> 84 | </property> 85 | </Select> 86 | ``` 87 | 88 | ## event 89 | 90 | Use `<event>` to declare event handlers as markup and enable the use of component-based handlers. 91 | 92 | Instead of using the `on` attribute prefix: 93 | 94 | ```xmlui 95 | <Button label="Click me" onClick="count++" /> 96 | ``` 97 | 98 | You can use the `<event>` tag: 99 | 100 | ```xmlui 101 | <Button label="Click me"> 102 | <event name="click"> 103 | count++ 104 | </event> 105 | </Button> 106 | ``` 107 | 108 | `<event>` is necessary when using `<APICall>` as an event handler. 109 | 110 | ```xmlui 111 | <Button label="Save Data"> 112 | <event name="click"> 113 | <APICall 114 | url="/api/save" 115 | method="POST" 116 | body="{formData}" 117 | onSuccess="toast('Data saved successfully!')" 118 | onError="toast('Failed to save data', 'error')" /> 119 | </event> 120 | </Button> 121 | ``` 122 | 123 | ## method 124 | 125 | Use `<method>` to export a method from a component. 126 | 127 | ```xmlui 128 | <App> 129 | <UsingInternalModal id="component"/> 130 | <Button label="Open the internal dialog" onClick="component.openDialog()" /> 131 | </App> 132 | 133 | 134 | Component name="UsingInternalModal"> 135 | <ModalDialog id="dialog" title="Example Dialog"> 136 | <Button label="Close Dialog" onClick="dialog.close()" /> 137 | </ModalDialog> 138 | 139 | <H1>Using an Internal Modal Dialog</H1> 140 | 141 | <method name="openDialog"> 142 | console.log('internal method called') 143 | dialog.open(); 144 | </method> 145 | </Component> 146 | ``` 147 | 148 | ## script 149 | 150 | Use `<script>` to declare inline JavaScript code. 151 | 152 | ```xmlui 153 | <Component name="ImportProducts"> 154 | <script> 155 | var parsedCsv = null; 156 | 157 | function isDuplicate(name) { 158 | return existingProducts.value.some(p => p.name === name); 159 | } 160 | </script> 161 | ... 162 | ``` 163 | 164 | ``` -------------------------------------------------------------------------------- /xmlui/src/parsers/xmlui-parser/utils.ts: -------------------------------------------------------------------------------- ```typescript 1 | import type { Node } from "./syntax-node"; 2 | import type { GetText } from "./parser"; 3 | import { SyntaxKind, getSyntaxKindStrRepr, isInnerNode } from "./syntax-kind"; 4 | 5 | export function toDbgString( 6 | node: Node, 7 | getText: (node: Node) => string, 8 | indentationLvl: number = 0, 9 | ): string { 10 | const prefix = `${" ".repeat(indentationLvl)} ${getSyntaxKindStrRepr(node.kind)} @${node.start}..${node.end}`; 11 | if (!isInnerNode(node.kind)) { 12 | let tokenText = getText(node); 13 | if (node.kind === SyntaxKind.NewLineTrivia) { 14 | tokenText = "*newline*"; 15 | } 16 | return prefix + ` "${tokenText}"`; 17 | } else { 18 | return ( 19 | prefix + 20 | "\n" + 21 | node.children?.map((c) => toDbgString(c, getText, indentationLvl + 1)).join("\n") 22 | ); 23 | } 24 | } 25 | 26 | /** Disregards error nodes amongst the children of the 2 compared name node. (Those reported an error earlyer anyways)*/ 27 | export function tagNameNodesWithoutErrorsMatch( 28 | name1: Node, 29 | name2: Node, 30 | getText: GetText, 31 | ): boolean { 32 | const children1 = name1.children?.filter((c) => c.kind !== SyntaxKind.ErrorNode) ?? []; 33 | const children2 = name2.children?.filter((c) => c.kind !== SyntaxKind.ErrorNode) ?? []; 34 | 35 | if (children1.length !== children2.length) { 36 | return false; 37 | } 38 | for (let i = 0; i < children1.length; ++i) { 39 | if (getText(children1[i]) !== getText(children2[i])) { 40 | return false; 41 | } 42 | } 43 | return true; 44 | } 45 | 46 | /** If the position is in-between two tokens, the chain to the token just before the cursor is provided as well*/ 47 | export type FindTokenSuccess = 48 | | { 49 | chainAtPos: Node[]; 50 | 51 | /** If the position is in-between two tokens, the chain to the token just before the position is provided. */ 52 | chainBeforePos: Node[]; 53 | 54 | /** 55 | * This field specifies the first index where 56 | * `chainBeforePos` differs from chainAtPos 57 | */ 58 | sharedParents: number; 59 | } 60 | | { 61 | chainBeforePos: undefined; 62 | chainAtPos: Node[]; 63 | sharedParents: undefined; 64 | }; 65 | 66 | export function findTokenAtPos(node: Node, position: number): FindTokenSuccess | undefined { 67 | const chain: Node[] = [node]; 68 | let sharedParents: number; 69 | 70 | if (node.start > position || position > node.end) { 71 | return undefined; 72 | } 73 | 74 | const res: FindTokenSuccess = { 75 | chainAtPos: chain, 76 | chainBeforePos: undefined, 77 | sharedParents: undefined, 78 | }; 79 | 80 | while (node.children !== undefined && node.children.length > 0) { 81 | //todo: make it a binary search before finding a fork 82 | const nodeAtPosIdx = node.children.findIndex( 83 | (n) => 84 | n.start <= position && 85 | (position < n.end || (n.kind === SyntaxKind.EndOfFileToken && n.start <= n.end)), 86 | ); 87 | 88 | const nodeAtPos = node.children[nodeAtPosIdx]; 89 | const nodeBeforePos = node.children[nodeAtPosIdx - 1]; 90 | 91 | if (nodeBeforePos !== undefined && position <= nodeAtPos.pos) { 92 | sharedParents = chain.length; 93 | 94 | return { 95 | chainBeforePos: chain.concat(findLastToken(nodeBeforePos)), 96 | sharedParents, 97 | 98 | chainAtPos: chain.concat(findFirstToken(nodeAtPos)), 99 | }; 100 | } 101 | 102 | node = nodeAtPos; 103 | res.chainAtPos!.push(node); 104 | } 105 | return res; 106 | } 107 | 108 | function findFirstToken(node: Node): Node[] { 109 | const chain = [node]; 110 | while (node.children !== undefined && node.children.length > 0) { 111 | node = node.children[0]; 112 | chain.push(node); 113 | } 114 | return chain; 115 | } 116 | 117 | function findLastToken(node: Node): Node[] { 118 | const chain = [node]; 119 | while (node.children !== undefined && node.children.length > 0) { 120 | node = node.children[node.children.length - 1]; 121 | chain.push(node); 122 | } 123 | return chain; 124 | } 125 | ``` -------------------------------------------------------------------------------- /xmlui/src/components-core/script-runner/ParameterParser.ts: -------------------------------------------------------------------------------- ```typescript 1 | import type { Expression } from "./ScriptingSourceTree"; 2 | import { Parser } from "../../parsers/scripting/Parser"; 3 | 4 | /** 5 | * This function parses a parameter string and splits them into string literal and binding expression sections 6 | * @param source String to parse 7 | * @returns Parameter string sections 8 | */ 9 | export function parseParameterString (source: string): (StringLiteralSection | ExpressionSection)[] { 10 | const result: (StringLiteralSection | ExpressionSection)[] = []; 11 | if (source === undefined || source === null) return result; 12 | 13 | let phase = ParsePhase.StringLiteral; 14 | let section = ""; 15 | let escape = ""; 16 | for (let i = 0; i < source.length; i++) { 17 | const ch = source[i]; 18 | switch (phase) { 19 | case ParsePhase.StringLiteral: 20 | if (ch === "\\") { 21 | phase = ParsePhase.Escape; 22 | escape = "\\"; 23 | } else if (ch === "{") { 24 | // --- A new expression starts, close the previous string literal 25 | if (section !== "") { 26 | result.push({ 27 | type: "literal", 28 | value: section 29 | }); 30 | } 31 | // --- Start a new section 32 | section = ""; 33 | phase = ParsePhase.ExprStart; 34 | } else { 35 | section += ch; 36 | } 37 | break; 38 | 39 | case ParsePhase.Escape: 40 | if (ch === "\\") { 41 | // --- Go on with escape 42 | escape += ch; 43 | break; 44 | } 45 | 46 | if (ch === "{") { 47 | // --- End escape as a literal section without the first "\" escape character 48 | section += escape.substring(1) + ch; 49 | } else { 50 | // --- End escape as a literal section with the full sequence 51 | section += escape + ch; 52 | } 53 | phase = ParsePhase.StringLiteral; 54 | break; 55 | 56 | case ParsePhase.ExprStart: 57 | const exprSource = source.substring(i); 58 | const parser = new Parser(source.substring(i)); 59 | let expr: Expression | null = null; 60 | try { 61 | expr = parser.parseExpr(); 62 | } catch (err) { 63 | throw new Error(`Cannot parse expression: '${exprSource}': ${err}`); 64 | } 65 | const tail = parser.getTail(); 66 | if (!tail || tail.trim().length < 1 || tail.trim()[0] !== "}") { 67 | // --- Unclosed expression, back to its beginning 68 | throw new Error(`Unclosed expression: '${source}'\n'${exprSource}'`); 69 | } else { 70 | // --- Successfully parsed expression 71 | result.push({ 72 | type: "expression", 73 | value: expr! 74 | }); 75 | 76 | // --- Skip the parsed part of the expression, and start a new literal section 77 | i = source.length - tail.length; 78 | section = ""; 79 | } 80 | phase = ParsePhase.StringLiteral; 81 | break; 82 | } 83 | } 84 | 85 | // --- Process the last segment 86 | switch (phase) { 87 | case ParsePhase.StringLiteral: 88 | if (section !== "") { 89 | result.push({ 90 | type: "literal", 91 | value: section 92 | }); 93 | } 94 | break; 95 | case ParsePhase.Escape: 96 | result.push({ 97 | type: "literal", 98 | value: section + escape 99 | }); 100 | break; 101 | case ParsePhase.ExprStart: 102 | result.push({ 103 | type: "literal", 104 | value: section + "{" 105 | }); 106 | break; 107 | } 108 | 109 | // --- Done. 110 | return result; 111 | } 112 | 113 | enum ParsePhase { 114 | StringLiteral, 115 | Escape, 116 | ExprStart 117 | } 118 | /** 119 | * Represents a literal segment 120 | */ 121 | type StringLiteralSection = { 122 | type: "literal"; 123 | 124 | // --- The string literal 125 | value: string; 126 | }; 127 | 128 | type ExpressionSection = { 129 | type: "expression"; 130 | 131 | // --- The expression string to parse 132 | value: Expression; 133 | }; 134 | ``` -------------------------------------------------------------------------------- /xmlui/src/components-core/theming/hvar.ts: -------------------------------------------------------------------------------- ```typescript 1 | export type HVar = { 2 | classes: Array<string>; 3 | attribute: string; 4 | component: string; 5 | traits: Array<string>; 6 | states: Array<string>; 7 | }; 8 | 9 | const parsedHVarCache: Record<string, HVar | null> = {}; 10 | 11 | //extremely dummy solution, will come back later 12 | export function parseHVar(input: string): HVar | null { 13 | if (parsedHVarCache[input] !== undefined) { 14 | return parsedHVarCache[input]; 15 | } 16 | // Split the input string into parts using regex 17 | const parts = input.split(/-[A-Z]+/); 18 | if (parts.length !== 2) { 19 | parsedHVarCache[input] = null; 20 | return parsedHVarCache[input]; 21 | } 22 | 23 | const firstPart = parts[0]; 24 | const classessParts = firstPart.split(":"); 25 | const attribute = classessParts[classessParts.length - 1]; 26 | const classes = classessParts.length > 1 ? classessParts.slice(0, classessParts.length - 1) : []; 27 | const secondPart = input.substring(firstPart.length + 1); 28 | const [compName, ...rest] = secondPart.split("-"); 29 | const traitsAndStates = secondPart.substring(compName.length).split("--"); 30 | 31 | const states: Array<string> = []; 32 | const traits: Array<string> = []; 33 | traitsAndStates.forEach((part) => { 34 | if (!part.includes("-") && part) { 35 | states.push(part); 36 | } else { 37 | part.split("-").forEach((trait) => { 38 | if (trait) { 39 | traits.push(trait); 40 | } 41 | }); 42 | } 43 | }); 44 | 45 | parsedHVarCache[input] = { 46 | classes: classes, 47 | attribute: attribute, 48 | component: compName, 49 | traits: traits, 50 | states: states, 51 | }; 52 | 53 | return parsedHVarCache[input]; 54 | } 55 | 56 | function createCombinations(arr: Array<any> = []) { 57 | const stateCombinations = []; 58 | 59 | for (let i = 1; i <= arr.length; i++) { 60 | for (let j = 0; j <= arr.length - i; j++) { 61 | stateCombinations.push(arr.slice(j, j + i)); 62 | } 63 | } 64 | return stateCombinations.sort((a, b) => b.length - a.length); 65 | } 66 | 67 | export type ThemeVarMatchResult = { 68 | forValue: string; 69 | matchedValue: string | undefined; 70 | from: Array<string>; 71 | }; 72 | 73 | export function matchThemeVar( 74 | themeVar: string, 75 | availableThemeVars: Array<Record<string, string>> = [] 76 | ): ThemeVarMatchResult | undefined { 77 | const hvar = parseHVar(themeVar); 78 | if (!hvar) { 79 | return; 80 | } 81 | const stateCombinations = createCombinations(hvar.states); 82 | const traitCombinations = createCombinations(hvar.traits); 83 | 84 | const sortedTraitCombinations: Array<string> = []; 85 | traitCombinations.forEach((traitComb) => { 86 | let result = ""; 87 | traitComb.forEach((t) => { 88 | result = `${result}-${t}`; 89 | }); 90 | sortedTraitCombinations.push(result); 91 | }); 92 | sortedTraitCombinations.push(""); 93 | 94 | const sortedStateCombinations: Array<string> = []; 95 | stateCombinations.forEach((stateComb) => { 96 | let result = ""; 97 | stateComb.forEach((s) => { 98 | result = `${result}--${s}`; 99 | }); 100 | sortedStateCombinations.push(result); 101 | }); 102 | sortedStateCombinations.push(""); 103 | 104 | const componentParts = [hvar.component, ...hvar.classes]; 105 | const from: Array<string> = []; 106 | sortedStateCombinations.forEach((stateComb) => { 107 | sortedTraitCombinations.forEach((traitComb) => { 108 | componentParts.forEach((componentPart) => { 109 | from.push(`${hvar.attribute}-${componentPart}${traitComb}${stateComb}`); 110 | }); 111 | }); 112 | }); 113 | 114 | let matchedValue; 115 | for (let i = availableThemeVars.length - 1; i >= 0; i--) { 116 | const themeVars = availableThemeVars[i]; 117 | let foundValue = from.find((themeVar) => themeVars[themeVar] !== undefined); 118 | if (foundValue) { 119 | matchedValue = foundValue; 120 | break; 121 | } 122 | } 123 | const forValue = from[0]; 124 | return { 125 | forValue: forValue, 126 | matchedValue: matchedValue, 127 | from: from, 128 | }; 129 | } 130 | ``` -------------------------------------------------------------------------------- /xmlui/scripts/generate-docs/build-pages-map.mjs: -------------------------------------------------------------------------------- ``` 1 | import { writeFileSync, statSync, readFileSync } from "fs"; 2 | import { extname } from "path"; 3 | import { 4 | gatherAndRemoveDuplicates, 5 | strBufferToLines, 6 | toHeadingPath, 7 | toNormalizedUpperCase, 8 | traverseDirectory, 9 | } from "./utils.mjs"; 10 | import { createScopedLogger } from "./logging-standards.mjs"; 11 | import { 12 | generateExportStatements, 13 | processDuplicatesWithLogging 14 | } from "./pattern-utilities.mjs"; 15 | import { PAGES_MAP_CONFIG } from "./constants.mjs"; 16 | 17 | const pathCutoff = PAGES_MAP_CONFIG.PATH_CUTOFF; 18 | const includedFileExtensions = PAGES_MAP_CONFIG.INCLUDED_FILE_EXTENSIONS; 19 | 20 | /** 21 | * Creates a file containing link constant variables to components/articles in the pages folder. 22 | * @param {string} pagesFolder The path to the pages folder (use UNIX delimiters) 23 | * @param {string} outFilePathAndName The path and name of the output file (use UNIX delimiters) 24 | */ 25 | export function buildPagesMap(pagesFolder, outFilePathAndName) { 26 | const logger = createScopedLogger("PagesMapBuilder"); 27 | logger.operationStart("building pages map"); 28 | const pages = []; 29 | traverseDirectory({ name: "", path: pagesFolder }, (item, _) => { 30 | /** 31 | * name: the folder's/file's name (eg. "hello-app-engine") 32 | * path: the path to the root of the given folder from the project root (eg. "src/apps/1_basic/samples/hello-app-engine") 33 | * parent: parent node 34 | * children: children file/folder names 35 | */ 36 | if (statSync(item.path).isDirectory()) { 37 | // Node is a folder 38 | } else { 39 | // Node is a file 40 | if (includedFileExtensions.includes(extname(item.name))) { 41 | const articleHeadings = getArticleIds(item); 42 | if (articleHeadings) { 43 | pages.push(...articleHeadings); 44 | } 45 | } 46 | } 47 | }); 48 | 49 | const { filtered: filteredPages, duplicates } = gatherAndRemoveDuplicates(pages); 50 | 51 | // Process duplicates with standardized logging 52 | processDuplicatesWithLogging(duplicates, logger, "article IDs and paths"); 53 | 54 | // Generate export statements using utility 55 | const pagesStr = generateExportStatements(filteredPages); 56 | 57 | writeFileSync(outFilePathAndName, pagesStr); 58 | } 59 | 60 | function getArticleIds(article) { 61 | const content = readFileSync(article.path, { encoding: "utf8" }); 62 | const relativeArticlePath = article.path.split(pathCutoff)[1]?.replace(extname(article.name), ""); 63 | 64 | const lines = strBufferToLines(content); 65 | 66 | const titleId = getTitleId(lines); 67 | if (!titleId) return null; 68 | 69 | const subHeadingIds = getSubHeadingIds(lines); 70 | return [ 71 | { id: toNormalizedUpperCase(titleId), path: relativeArticlePath }, 72 | ...subHeadingIds.map((id) => ({ 73 | id: `${toNormalizedUpperCase(titleId)}_${toNormalizedUpperCase(id)}`, 74 | path: `${relativeArticlePath}#${toHeadingPath(id)}`, 75 | })), 76 | ]; 77 | 78 | // --- 79 | 80 | function getTitleId(lines) { 81 | for (const line of lines) { 82 | const match = line.match(/^#\s+.+?\s*(\[#[\w-]+\])?$/); 83 | if (!match) continue; 84 | if (match[1]) { 85 | // Has ID, extract it and use that 86 | return match[1].replace(/\[#(.*?)\]/, (_, p1) => p1); 87 | } else { 88 | // Generate new ID from the heading title 89 | return match[0].slice(1); 90 | } 91 | } 92 | } 93 | 94 | function getSubHeadingIds(lines) { 95 | const headings = []; 96 | for (const line of lines) { 97 | // We only gather headings which have an explicit ID defined and they are not leveled as h1 98 | const match = line.match(/^##+\s+.+?\s*(\[#[\w-]+\])$/); 99 | if (!match) continue; 100 | if (!match[1]) continue; 101 | // Has ID, extract it and use that 102 | headings.push(match[1].replace(/\[#(.*?)\]/, (_, p1) => p1)); 103 | } 104 | return headings; 105 | } 106 | } 107 | ``` -------------------------------------------------------------------------------- /packages/xmlui-website-blocks/src/HeroSection/HeroSection.module.scss: -------------------------------------------------------------------------------- ```scss 1 | @use "xmlui/themes.scss" as t; 2 | 3 | // --- This code snippet is required to collect the theme variables used in this module 4 | $themeVars: (); 5 | @function createThemeVar($componentVariable) { 6 | $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global; 7 | @return t.getThemeVar($themeVars, $componentVariable); 8 | } 9 | 10 | $component: "HeroSection"; 11 | $preamble: "preamble-#{$component}"; 12 | $headline: "headline-#{$component}"; 13 | $subheadline: "subheadline-#{$component}"; 14 | $mainText: "mainText-#{$component}"; 15 | 16 | $themeVars: t.composePaddingVars($themeVars, $component); 17 | $themeVars: t.composeTextVars($themeVars, $headline); 18 | $themeVars: t.composeTextVars($themeVars, $subheadline); 19 | $themeVars: t.composeTextVars($themeVars, $preamble); 20 | $themeVars: t.composeTextVars($themeVars, $mainText); 21 | 22 | @layer components { 23 | .heroWrapper { 24 | display: flex; 25 | flex-direction: column; 26 | position: relative; 27 | z-index: 0; // Create a stacking context to contain z-index values 28 | overflow: hidden; // Prevent background from extending beyond bounds 29 | 30 | .backgroundTemplate { 31 | position: absolute; 32 | top: 0; 33 | left: 0; 34 | right: 0; 35 | bottom: 0; 36 | z-index: -1; // Place behind content but within the hero wrapper's context 37 | } 38 | 39 | .heroContent { 40 | position: relative; 41 | z-index: 0; // Above background but within normal document flow 42 | display: flex; 43 | flex-direction: column; 44 | @include t.paddingVars($themeVars, $component); 45 | // --- Here, 2 * t.$space-4 accounts for the left and right padding of the page area 46 | width: calc(createThemeVar("maxWidth-content") - 2 * t.$space-4); 47 | margin: 0 auto; // Center horizontally within parent 48 | 49 | &.horizontal { 50 | flex-direction: row; 51 | align-items: flex-start; 52 | } 53 | 54 | &.vertical { 55 | flex-direction: column; 56 | } 57 | } 58 | 59 | .header { 60 | display: flex; 61 | flex-direction: column; 62 | } 63 | 64 | .content { 65 | display: flex; 66 | flex-direction: column; 67 | flex: 1; 68 | 69 | &.contentStart { 70 | align-items: flex-start; 71 | } 72 | 73 | &.contentCenter { 74 | align-items: center; 75 | } 76 | 77 | &.contentEnd { 78 | align-items: flex-end; 79 | } 80 | } 81 | 82 | .headingSection { 83 | display: flex; 84 | flex-direction: column; 85 | 86 | &.start { 87 | text-align: start; 88 | align-items: flex-start; 89 | } 90 | 91 | &.center { 92 | text-align: center; 93 | align-items: center; 94 | } 95 | 96 | &.end { 97 | text-align: end; 98 | align-items: flex-end; 99 | } 100 | } 101 | } 102 | 103 | .preamble { 104 | @include t.textVars($themeVars, $preamble); 105 | white-space: pre-wrap; 106 | display: inline-block; 107 | margin: 0; 108 | padding-bottom: createThemeVar("gap-#{$preamble}"); 109 | } 110 | 111 | .headline { 112 | @include t.textVars($themeVars, $headline); 113 | white-space: pre-wrap; 114 | display: inline-block; 115 | margin: 0; 116 | padding-bottom: createThemeVar("gap-#{$headline}"); 117 | } 118 | 119 | .subheadline { 120 | @include t.textVars($themeVars, $subheadline); 121 | white-space: pre-wrap; 122 | display: inline-block; 123 | margin: 0; 124 | padding-bottom: createThemeVar("gap-#{$subheadline}"); 125 | } 126 | 127 | .textWrapper { 128 | display: flex; 129 | flex-direction: column; 130 | white-space: pre-wrap; 131 | padding-bottom: createThemeVar("gap-#{$mainText}"); 132 | } 133 | 134 | .mainText { 135 | @include t.textVars($themeVars, $mainText); 136 | white-space: pre-wrap; 137 | display: inline-block; 138 | margin: 0; 139 | padding-bottom: createThemeVar("gap-#{$mainText}"); 140 | } 141 | 142 | .ctaButtonWrapper { 143 | margin-top: t.$space-16; 144 | } 145 | 146 | .ctaButton { 147 | font-size: 1.5em; 148 | } 149 | } 150 | 151 | :export { 152 | themeVars: t.json-stringify($themeVars); 153 | } 154 | ```