This is page 121 of 181. Use http://codebase.md/xmlui-org/xmlui/mockApiDef.js?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .changeset │ ├── config.json │ └── cool-queens-look.md ├── .eslintrc.cjs ├── .github │ ├── build-checklist.png │ ├── ISSUE_TEMPLATE │ │ ├── bug_report.md │ │ └── feature_request.md │ └── workflows │ ├── deploy-blog.yml │ ├── deploy-docs-optimized.yml │ ├── deploy-docs.yml │ ├── prepare-versions.yml │ ├── release-packages.yml │ ├── run-all-tests.yml │ └── run-smoke-tests.yml ├── .gitignore ├── .prettierrc.js ├── .vscode │ ├── launch.json │ └── settings.json ├── blog │ ├── .gitignore │ ├── .gitkeep │ ├── CHANGELOG.md │ ├── extensions.ts │ ├── index.html │ ├── index.ts │ ├── package.json │ ├── public │ │ ├── blog │ │ │ ├── images │ │ │ │ ├── blog-page-component.png │ │ │ │ ├── blog-scrabble.png │ │ │ │ ├── integrated-blog-search.png │ │ │ │ └── lorem-ipsum.png │ │ │ ├── lorem-ipsum.md │ │ │ ├── newest-post.md │ │ │ ├── older-post.md │ │ │ └── welcome-to-the-xmlui-blog.md │ │ ├── mockServiceWorker.js │ │ ├── netlify.toml │ │ ├── resources │ │ │ ├── favicon.ico │ │ │ ├── files │ │ │ │ └── for-download │ │ │ │ └── xmlui │ │ │ │ └── xmlui-standalone.umd.js │ │ │ ├── github.svg │ │ │ ├── llms.txt │ │ │ ├── logo-dark.svg │ │ │ ├── logo.svg │ │ │ ├── pg-popout.svg │ │ │ └── xmlui-logo.svg │ │ ├── serve.json │ │ └── web.config │ ├── scripts │ │ ├── download-latest-xmlui.js │ │ ├── generate-rss.js │ │ ├── get-releases.js │ │ └── utils.js │ ├── src │ │ ├── components │ │ │ ├── BlogOverview.xmlui │ │ │ ├── BlogPage.xmlui │ │ │ ├── Debug.xmlui │ │ │ └── PageNotFound.xmlui │ │ ├── config.ts │ │ ├── Main.xmlui │ │ ├── Main.xmlui.xs │ │ └── themes │ │ ├── docs-theme.ts │ │ ├── earthtone.ts │ │ ├── xmlui-gray-on-default.ts │ │ ├── xmlui-green-on-default.ts │ │ └── xmlui-orange-on-default.ts │ └── tsconfig.json ├── CONTRIBUTING.md ├── docs │ ├── .gitignore │ ├── CHANGELOG.md │ ├── ComponentRefLinks.txt │ ├── content │ │ ├── _meta.json │ │ ├── components │ │ │ ├── _meta.json │ │ │ ├── _overview.md │ │ │ ├── APICall.md │ │ │ ├── App.md │ │ │ ├── AppHeader.md │ │ │ ├── AppState.md │ │ │ ├── AutoComplete.md │ │ │ ├── Avatar.md │ │ │ ├── Backdrop.md │ │ │ ├── Badge.md │ │ │ ├── BarChart.md │ │ │ ├── Bookmark.md │ │ │ ├── Breakout.md │ │ │ ├── Button.md │ │ │ ├── Card.md │ │ │ ├── Carousel.md │ │ │ ├── ChangeListener.md │ │ │ ├── Checkbox.md │ │ │ ├── CHStack.md │ │ │ ├── ColorPicker.md │ │ │ ├── Column.md │ │ │ ├── ContentSeparator.md │ │ │ ├── CVStack.md │ │ │ ├── DataSource.md │ │ │ ├── DateInput.md │ │ │ ├── DatePicker.md │ │ │ ├── DonutChart.md │ │ │ ├── DropdownMenu.md │ │ │ ├── EmojiSelector.md │ │ │ ├── ExpandableItem.md │ │ │ ├── FileInput.md │ │ │ ├── FileUploadDropZone.md │ │ │ ├── FlowLayout.md │ │ │ ├── Footer.md │ │ │ ├── Form.md │ │ │ ├── FormItem.md │ │ │ ├── FormSection.md │ │ │ ├── Fragment.md │ │ │ ├── H1.md │ │ │ ├── H2.md │ │ │ ├── H3.md │ │ │ ├── H4.md │ │ │ ├── H5.md │ │ │ ├── H6.md │ │ │ ├── Heading.md │ │ │ ├── HSplitter.md │ │ │ ├── HStack.md │ │ │ ├── Icon.md │ │ │ ├── IFrame.md │ │ │ ├── Image.md │ │ │ ├── Items.md │ │ │ ├── LabelList.md │ │ │ ├── Legend.md │ │ │ ├── LineChart.md │ │ │ ├── Link.md │ │ │ ├── List.md │ │ │ ├── Logo.md │ │ │ ├── Markdown.md │ │ │ ├── MenuItem.md │ │ │ ├── MenuSeparator.md │ │ │ ├── ModalDialog.md │ │ │ ├── NavGroup.md │ │ │ ├── NavLink.md │ │ │ ├── NavPanel.md │ │ │ ├── NoResult.md │ │ │ ├── NumberBox.md │ │ │ ├── Option.md │ │ │ ├── Page.md │ │ │ ├── PageMetaTitle.md │ │ │ ├── Pages.md │ │ │ ├── Pagination.md │ │ │ ├── PasswordInput.md │ │ │ ├── PieChart.md │ │ │ ├── ProgressBar.md │ │ │ ├── Queue.md │ │ │ ├── RadioGroup.md │ │ │ ├── RealTimeAdapter.md │ │ │ ├── Redirect.md │ │ │ ├── Select.md │ │ │ ├── Slider.md │ │ │ ├── Slot.md │ │ │ ├── SpaceFiller.md │ │ │ ├── Spinner.md │ │ │ ├── Splitter.md │ │ │ ├── Stack.md │ │ │ ├── StickyBox.md │ │ │ ├── SubMenuItem.md │ │ │ ├── Switch.md │ │ │ ├── TabItem.md │ │ │ ├── Table.md │ │ │ ├── TableOfContents.md │ │ │ ├── Tabs.md │ │ │ ├── Text.md │ │ │ ├── TextArea.md │ │ │ ├── TextBox.md │ │ │ ├── Theme.md │ │ │ ├── TimeInput.md │ │ │ ├── Timer.md │ │ │ ├── ToneChangerButton.md │ │ │ ├── ToneSwitch.md │ │ │ ├── Tooltip.md │ │ │ ├── Tree.md │ │ │ ├── VSplitter.md │ │ │ ├── VStack.md │ │ │ ├── xmlui-animations │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ ├── Animation.md │ │ │ │ ├── FadeAnimation.md │ │ │ │ ├── FadeInAnimation.md │ │ │ │ ├── FadeOutAnimation.md │ │ │ │ ├── ScaleAnimation.md │ │ │ │ └── SlideInAnimation.md │ │ │ ├── xmlui-pdf │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ └── Pdf.md │ │ │ ├── xmlui-spreadsheet │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ └── Spreadsheet.md │ │ │ └── xmlui-website-blocks │ │ │ ├── _meta.json │ │ │ ├── _overview.md │ │ │ ├── Carousel.md │ │ │ ├── HelloMd.md │ │ │ ├── HeroSection.md │ │ │ └── ScrollToTop.md │ │ └── extensions │ │ ├── _meta.json │ │ ├── xmlui-animations │ │ │ ├── _meta.json │ │ │ ├── _overview.md │ │ │ ├── Animation.md │ │ │ ├── FadeAnimation.md │ │ │ ├── FadeInAnimation.md │ │ │ ├── FadeOutAnimation.md │ │ │ ├── ScaleAnimation.md │ │ │ └── SlideInAnimation.md │ │ └── xmlui-website-blocks │ │ ├── _meta.json │ │ ├── _overview.md │ │ ├── Carousel.md │ │ ├── HelloMd.md │ │ ├── HeroSection.md │ │ └── ScrollToTop.md │ ├── extensions.ts │ ├── index.html │ ├── index.ts │ ├── package.json │ ├── public │ │ ├── feed.rss │ │ ├── mockServiceWorker.js │ │ ├── pages │ │ │ ├── _meta.json │ │ │ ├── app-structure.md │ │ │ ├── build-editor-component.md │ │ │ ├── build-hello-world-component.md │ │ │ ├── components-intro.md │ │ │ ├── context-variables.md │ │ │ ├── forms.md │ │ │ ├── globals.md │ │ │ ├── glossary.md │ │ │ ├── helper-tags.md │ │ │ ├── hosted-deployment.md │ │ │ ├── howto │ │ │ │ ├── assign-a-complex-json-literal-to-a-component-variable.md │ │ │ │ ├── chain-a-refetch.md │ │ │ │ ├── debug-a-component.md │ │ │ │ ├── delay-a-datasource-until-another-datasource-is-ready.md │ │ │ │ ├── delegate-a-method.md │ │ │ │ ├── do-custom-form-validation.md │ │ │ │ ├── expose-a-method-from-a-component.md │ │ │ │ ├── filter-and-transform-data-from-an-api.md │ │ │ │ ├── group-items-in-list-by-a-property.md │ │ │ │ ├── handle-background-operations.md │ │ │ │ ├── hide-an-element-until-its-datasource-is-ready.md │ │ │ │ ├── make-a-set-of-equal-width-cards.md │ │ │ │ ├── make-a-table-responsive.md │ │ │ │ ├── modify-a-value-reported-in-a-column.md │ │ │ │ ├── paginate-a-list.md │ │ │ │ ├── pass-data-to-a-modal-dialog.md │ │ │ │ ├── react-to-button-click-not-keystrokes.md │ │ │ │ ├── set-the-initial-value-of-a-select-from-fetched-data.md │ │ │ │ ├── share-a-modaldialog-across-components.md │ │ │ │ ├── sync-selections-between-table-and-list-views.md │ │ │ │ ├── update-ui-optimistically.md │ │ │ │ ├── use-built-in-form-validation.md │ │ │ │ └── use-the-same-modaldialog-to-add-or-edit.md │ │ │ ├── howto.md │ │ │ ├── intro.md │ │ │ ├── layout.md │ │ │ ├── markup.md │ │ │ ├── mcp.md │ │ │ ├── modal-dialogs.md │ │ │ ├── news-and-reviews.md │ │ │ ├── reactive-intro.md │ │ │ ├── refactoring.md │ │ │ ├── routing-and-links.md │ │ │ ├── samples │ │ │ │ ├── color-palette.xmlui │ │ │ │ ├── color-values.xmlui │ │ │ │ ├── shadow-sizes.xmlui │ │ │ │ ├── spacing-sizes.xmlui │ │ │ │ ├── swatch.xmlui │ │ │ │ ├── theme-gallery-brief.xmlui │ │ │ │ └── theme-gallery.xmlui │ │ │ ├── scoping.md │ │ │ ├── scripting.md │ │ │ ├── styles-and-themes │ │ │ │ ├── common-units.md │ │ │ │ ├── layout-props.md │ │ │ │ ├── theme-variable-defaults.md │ │ │ │ ├── theme-variables.md │ │ │ │ └── themes.md │ │ │ ├── template-properties.md │ │ │ ├── test.md │ │ │ ├── tutorial-01.md │ │ │ ├── tutorial-02.md │ │ │ ├── tutorial-03.md │ │ │ ├── tutorial-04.md │ │ │ ├── tutorial-05.md │ │ │ ├── tutorial-06.md │ │ │ ├── tutorial-07.md │ │ │ ├── tutorial-08.md │ │ │ ├── tutorial-09.md │ │ │ ├── tutorial-10.md │ │ │ ├── tutorial-11.md │ │ │ ├── tutorial-12.md │ │ │ ├── universal-properties.md │ │ │ ├── user-defined-components.md │ │ │ ├── vscode.md │ │ │ ├── working-with-markdown.md │ │ │ ├── working-with-text.md │ │ │ ├── xmlui-animations │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ ├── Animation.md │ │ │ │ ├── FadeAnimation.md │ │ │ │ ├── FadeInAnimation.md │ │ │ │ ├── FadeOutAnimation.md │ │ │ │ ├── ScaleAnimation.md │ │ │ │ └── SlideInAnimation.md │ │ │ ├── xmlui-charts │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ ├── BarChart.md │ │ │ │ ├── DonutChart.md │ │ │ │ ├── LabelList.md │ │ │ │ ├── Legend.md │ │ │ │ ├── LineChart.md │ │ │ │ └── PieChart.md │ │ │ ├── xmlui-pdf │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ └── Pdf.md │ │ │ └── xmlui-spreadsheet │ │ │ ├── _meta.json │ │ │ ├── _overview.md │ │ │ └── Spreadsheet.md │ │ ├── resources │ │ │ ├── devdocs │ │ │ │ ├── debug-proxy-object-2.png │ │ │ │ ├── debug-proxy-object.png │ │ │ │ ├── table_editor_01.png │ │ │ │ ├── table_editor_02.png │ │ │ │ ├── table_editor_03.png │ │ │ │ ├── table_editor_04.png │ │ │ │ ├── table_editor_05.png │ │ │ │ ├── table_editor_06.png │ │ │ │ ├── table_editor_07.png │ │ │ │ ├── table_editor_08.png │ │ │ │ ├── table_editor_09.png │ │ │ │ ├── table_editor_10.png │ │ │ │ ├── table_editor_11.png │ │ │ │ ├── table-editor-01.png │ │ │ │ ├── table-editor-02.png │ │ │ │ ├── table-editor-03.png │ │ │ │ ├── table-editor-04.png │ │ │ │ ├── table-editor-06.png │ │ │ │ ├── table-editor-07.png │ │ │ │ ├── table-editor-08.png │ │ │ │ ├── table-editor-09.png │ │ │ │ └── xmlui-rendering-of-tiptap-markdown.png │ │ │ ├── favicon.ico │ │ │ ├── files │ │ │ │ ├── clients.json │ │ │ │ ├── daily-revenue.json │ │ │ │ ├── dashboard-stats.json │ │ │ │ ├── demo.xmlui │ │ │ │ ├── demo.xmlui.xs │ │ │ │ ├── downloads │ │ │ │ │ └── downloads.json │ │ │ │ ├── for-download │ │ │ │ │ ├── index-with-api.html │ │ │ │ │ ├── index.html │ │ │ │ │ ├── mockApi.js │ │ │ │ │ ├── start-darwin.sh │ │ │ │ │ ├── start-linux.sh │ │ │ │ │ ├── start.bat │ │ │ │ │ └── xmlui │ │ │ │ │ └── xmlui-standalone.umd.js │ │ │ │ ├── getting-started │ │ │ │ │ ├── cl-tutorial-final.zip │ │ │ │ │ ├── cl-tutorial.zip │ │ │ │ │ ├── cl-tutorial2.zip │ │ │ │ │ ├── cl-tutorial3.zip │ │ │ │ │ ├── cl-tutorial4.zip │ │ │ │ │ ├── cl-tutorial5.zip │ │ │ │ │ ├── cl-tutorial6.zip │ │ │ │ │ ├── getting-started.zip │ │ │ │ │ ├── hello-xmlui.zip │ │ │ │ │ ├── xmlui-empty.zip │ │ │ │ │ └── xmlui-starter.zip │ │ │ │ ├── howto │ │ │ │ │ └── component-icons │ │ │ │ │ └── up-arrow.svg │ │ │ │ ├── invoices.json │ │ │ │ ├── monthly-status.json │ │ │ │ ├── news-and-reviews.json │ │ │ │ ├── products.json │ │ │ │ ├── releases.json │ │ │ │ ├── tutorials │ │ │ │ │ ├── datasource │ │ │ │ │ │ └── api.ts │ │ │ │ │ └── p2do │ │ │ │ │ ├── api.ts │ │ │ │ │ └── todo-logo.svg │ │ │ │ └── xmlui.json │ │ │ ├── github.svg │ │ │ ├── images │ │ │ │ ├── apiaction-tutorial │ │ │ │ │ ├── add-success.png │ │ │ │ │ ├── apiaction-param.png │ │ │ │ │ ├── change-completed.png │ │ │ │ │ ├── change-in-progress.png │ │ │ │ │ ├── confirm-delete.png │ │ │ │ │ ├── data-error.png │ │ │ │ │ ├── data-progress.png │ │ │ │ │ ├── data-success.png │ │ │ │ │ ├── display-1.png │ │ │ │ │ ├── item-deleted.png │ │ │ │ │ ├── item-updated.png │ │ │ │ │ ├── missing-api-key.png │ │ │ │ │ ├── new-item-added.png │ │ │ │ │ └── test-message.png │ │ │ │ ├── chat-api │ │ │ │ │ └── domain-model.svg │ │ │ │ ├── components │ │ │ │ │ ├── image │ │ │ │ │ │ └── breakfast.jpg │ │ │ │ │ ├── markdown │ │ │ │ │ │ └── colors.png │ │ │ │ │ └── modal │ │ │ │ │ ├── deep_link_dialog_1.jpg │ │ │ │ │ └── deep_link_dialog_2.jpg │ │ │ │ ├── create-apps │ │ │ │ │ ├── collapsed-vertical.png │ │ │ │ │ ├── using-forms-warning-dialog.png │ │ │ │ │ └── using-forms.png │ │ │ │ ├── datasource-tutorial │ │ │ │ │ ├── data-with-header.png │ │ │ │ │ ├── filtered-data.png │ │ │ │ │ ├── filtered-items.png │ │ │ │ │ ├── initial-page-items.png │ │ │ │ │ ├── list-items.png │ │ │ │ │ ├── next-page-items.png │ │ │ │ │ ├── no-data.png │ │ │ │ │ ├── pagination-1.jpg │ │ │ │ │ ├── pagination-1.png │ │ │ │ │ ├── polling-1.png │ │ │ │ │ ├── refetch-data.png │ │ │ │ │ ├── slow-loading.png │ │ │ │ │ ├── test-message.png │ │ │ │ │ ├── Thumbs.db │ │ │ │ │ ├── unconventional-data.png │ │ │ │ │ └── unfiltered-items.png │ │ │ │ ├── flower.jpg │ │ │ │ ├── get-started │ │ │ │ │ ├── add-new-contact.png │ │ │ │ │ ├── app-modified.png │ │ │ │ │ ├── app-start.png │ │ │ │ │ ├── app-with-boxes.png │ │ │ │ │ ├── app-with-toast.png │ │ │ │ │ ├── boilerplate-structure.png │ │ │ │ │ ├── cl-initial.png │ │ │ │ │ ├── cl-start.png │ │ │ │ │ ├── contact-counts.png │ │ │ │ │ ├── contact-dialog-title.png │ │ │ │ │ ├── contact-dialog.png │ │ │ │ │ ├── contact-menus.png │ │ │ │ │ ├── contact-predicates.png │ │ │ │ │ ├── context-menu.png │ │ │ │ │ ├── dashboard-numbers.png │ │ │ │ │ ├── default-contact-list.png │ │ │ │ │ ├── delete-contact.png │ │ │ │ │ ├── delete-task.png │ │ │ │ │ ├── detailed-template.png │ │ │ │ │ ├── edit-contact-details.png │ │ │ │ │ ├── edited-contact-saved.png │ │ │ │ │ ├── empty-sections.png │ │ │ │ │ ├── filter-completed.png │ │ │ │ │ ├── fullwidth-desktop.png │ │ │ │ │ ├── fullwidth-mobile.png │ │ │ │ │ ├── initial-table.png │ │ │ │ │ ├── items-and-badges.png │ │ │ │ │ ├── loading-message.png │ │ │ │ │ ├── new-contact-button.png │ │ │ │ │ ├── new-contact-saved.png │ │ │ │ │ ├── no-empty-sections.png │ │ │ │ │ ├── personal-todo-initial.png │ │ │ │ │ ├── piechart.png │ │ │ │ │ ├── review-today.png │ │ │ │ │ ├── rudimentary-dashboard.png │ │ │ │ │ ├── section-collapsed.png │ │ │ │ │ ├── sectioned-items.png │ │ │ │ │ ├── sections-ordered.png │ │ │ │ │ ├── spacex-list-with-links.png │ │ │ │ │ ├── spacex-list.png │ │ │ │ │ ├── start-personal-todo-1.png │ │ │ │ │ ├── submit-new-contact.png │ │ │ │ │ ├── submit-new-task.png │ │ │ │ │ ├── syntax-highlighting.png │ │ │ │ │ ├── table-with-badge.png │ │ │ │ │ ├── template-with-card.png │ │ │ │ │ ├── test-emulated-api.png │ │ │ │ │ ├── Thumbs.db │ │ │ │ │ ├── todo-logo.png │ │ │ │ │ └── xmlui-tools.png │ │ │ │ ├── HelloApp.png │ │ │ │ ├── HelloApp2.png │ │ │ │ ├── logos │ │ │ │ │ ├── xmlui1.svg │ │ │ │ │ ├── xmlui2.svg │ │ │ │ │ ├── xmlui3.svg │ │ │ │ │ ├── xmlui4.svg │ │ │ │ │ ├── xmlui5.svg │ │ │ │ │ ├── xmlui6.svg │ │ │ │ │ └── xmlui7.svg │ │ │ │ ├── pdf │ │ │ │ │ └── dummy-pdf.jpg │ │ │ │ ├── rendering-engine │ │ │ │ │ ├── AppEngine-flow.svg │ │ │ │ │ ├── Component.svg │ │ │ │ │ ├── CompoundComponent.svg │ │ │ │ │ ├── RootComponent.svg │ │ │ │ │ └── tree-with-containers.svg │ │ │ │ ├── reviewers-guide │ │ │ │ │ ├── AppEngine-flow.svg │ │ │ │ │ └── incbutton-in-action.png │ │ │ │ ├── tools │ │ │ │ │ └── boilerplate-structure.png │ │ │ │ ├── try.svg │ │ │ │ ├── tutorial │ │ │ │ │ ├── app-chat-history.png │ │ │ │ │ ├── app-content-placeholder.png │ │ │ │ │ ├── app-header-and-content.png │ │ │ │ │ ├── app-links-channel-selected.png │ │ │ │ │ ├── app-links-click.png │ │ │ │ │ ├── app-navigation.png │ │ │ │ │ ├── finished-ex01.png │ │ │ │ │ ├── finished-ex02.png │ │ │ │ │ ├── hello.png │ │ │ │ │ ├── splash-screen-advanced.png │ │ │ │ │ ├── splash-screen-after-click.png │ │ │ │ │ ├── splash-screen-centered.png │ │ │ │ │ ├── splash-screen-events.png │ │ │ │ │ ├── splash-screen-expression.png │ │ │ │ │ ├── splash-screen-reuse-after.png │ │ │ │ │ ├── splash-screen-reuse-before.png │ │ │ │ │ └── splash-screen.png │ │ │ │ └── tutorial-01.png │ │ │ ├── llms.txt │ │ │ ├── logo-dark.svg │ │ │ ├── logo.svg │ │ │ ├── pg-popout.svg │ │ │ └── xmlui-logo.svg │ │ ├── serve.json │ │ └── web.config │ ├── scripts │ │ ├── download-latest-xmlui.js │ │ ├── generate-rss.js │ │ ├── get-releases.js │ │ └── utils.js │ ├── src │ │ ├── components │ │ │ ├── BlogOverview.xmlui │ │ │ ├── BlogPage.xmlui │ │ │ ├── Boxes.xmlui │ │ │ ├── Breadcrumb.xmlui │ │ │ ├── ChangeLog.xmlui │ │ │ ├── ColorPalette.xmlui │ │ │ ├── DocumentLinks.xmlui │ │ │ ├── DocumentPage.xmlui │ │ │ ├── DocumentPageNoTOC.xmlui │ │ │ ├── Icons.xmlui │ │ │ ├── IncButton.xmlui │ │ │ ├── IncButton2.xmlui │ │ │ ├── NameValue.xmlui │ │ │ ├── PageNotFound.xmlui │ │ │ ├── PaletteItem.xmlui │ │ │ ├── Palettes.xmlui │ │ │ ├── SectionHeader.xmlui │ │ │ ├── TBD.xmlui │ │ │ ├── Test.xmlui │ │ │ ├── ThemesIntro.xmlui │ │ │ ├── ThousandThemes.xmlui │ │ │ ├── TubeStops.xmlui │ │ │ ├── TubeStops.xmlui.xs │ │ │ └── TwoColumnCode.xmlui │ │ ├── config.ts │ │ ├── Main.xmlui │ │ └── themes │ │ ├── docs-theme.ts │ │ ├── earthtone.ts │ │ ├── xmlui-gray-on-default.ts │ │ ├── xmlui-green-on-default.ts │ │ └── xmlui-orange-on-default.ts │ └── tsconfig.json ├── LICENSE ├── package-lock.json ├── package.json ├── packages │ ├── xmlui-animations │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── Animation.tsx │ │ │ ├── AnimationNative.tsx │ │ │ ├── FadeAnimation.tsx │ │ │ ├── FadeInAnimation.tsx │ │ │ ├── FadeOutAnimation.tsx │ │ │ ├── index.tsx │ │ │ ├── ScaleAnimation.tsx │ │ │ └── SlideInAnimation.tsx │ │ └── tsconfig.json │ ├── xmlui-devtools │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── devtools │ │ │ │ ├── DevTools.tsx │ │ │ │ ├── DevToolsNative.module.scss │ │ │ │ ├── DevToolsNative.tsx │ │ │ │ ├── ModalDialog.module.scss │ │ │ │ ├── ModalDialog.tsx │ │ │ │ ├── ModalVisibilityContext.tsx │ │ │ │ ├── Tooltip.module.scss │ │ │ │ ├── Tooltip.tsx │ │ │ │ └── utils.ts │ │ │ ├── editor │ │ │ │ └── Editor.tsx │ │ │ └── index.tsx │ │ ├── tsconfig.json │ │ └── vite.config-overrides.ts │ ├── xmlui-hello-world │ │ ├── .gitignore │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── HelloWorld.module.scss │ │ │ ├── HelloWorld.tsx │ │ │ ├── HelloWorldNative.tsx │ │ │ └── index.tsx │ │ └── tsconfig.json │ ├── xmlui-os-frames │ │ ├── .gitignore │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── IPhoneFrame.module.scss │ │ │ ├── IPhoneFrame.tsx │ │ │ ├── MacOSAppFrame.module.scss │ │ │ ├── MacOSAppFrame.tsx │ │ │ ├── WindowsAppFrame.module.scss │ │ │ └── WindowsAppFrame.tsx │ │ └── tsconfig.json │ ├── xmlui-pdf │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ ├── components │ │ │ │ └── Pdf.xmlui │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── LazyPdfNative.tsx │ │ │ ├── Pdf.module.scss │ │ │ └── Pdf.tsx │ │ └── tsconfig.json │ ├── xmlui-playground │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── hooks │ │ │ │ ├── usePlayground.ts │ │ │ │ └── useToast.ts │ │ │ ├── index.tsx │ │ │ ├── playground │ │ │ │ ├── Box.module.scss │ │ │ │ ├── Box.tsx │ │ │ │ ├── CodeSelector.tsx │ │ │ │ ├── ConfirmationDialog.module.scss │ │ │ │ ├── ConfirmationDialog.tsx │ │ │ │ ├── Editor.tsx │ │ │ │ ├── Header.module.scss │ │ │ │ ├── Header.tsx │ │ │ │ ├── Playground.tsx │ │ │ │ ├── PlaygroundContent.module.scss │ │ │ │ ├── PlaygroundContent.tsx │ │ │ │ ├── PlaygroundNative.module.scss │ │ │ │ ├── PlaygroundNative.tsx │ │ │ │ ├── Preview.module.scss │ │ │ │ ├── Preview.tsx │ │ │ │ ├── Select.module.scss │ │ │ │ ├── StandalonePlayground.tsx │ │ │ │ ├── StandalonePlaygroundNative.module.scss │ │ │ │ ├── StandalonePlaygroundNative.tsx │ │ │ │ ├── ThemeSwitcher.module.scss │ │ │ │ ├── ThemeSwitcher.tsx │ │ │ │ ├── ToneSwitcher.tsx │ │ │ │ ├── Tooltip.module.scss │ │ │ │ ├── Tooltip.tsx │ │ │ │ └── utils.ts │ │ │ ├── providers │ │ │ │ ├── Toast.module.scss │ │ │ │ └── ToastProvider.tsx │ │ │ ├── state │ │ │ │ └── store.ts │ │ │ ├── themes │ │ │ │ └── theme.ts │ │ │ └── utils │ │ │ └── helpers.ts │ │ └── tsconfig.json │ ├── xmlui-search │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── Search.module.scss │ │ │ └── Search.tsx │ │ └── tsconfig.json │ ├── xmlui-spreadsheet │ │ ├── .gitignore │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── Spreadsheet.tsx │ │ │ └── SpreadsheetNative.tsx │ │ └── tsconfig.json │ └── xmlui-website-blocks │ ├── .gitignore │ ├── CHANGELOG.md │ ├── demo │ │ ├── components │ │ │ ├── HeroBackgroundBreakoutPage.xmlui │ │ │ ├── HeroBackgroundsPage.xmlui │ │ │ ├── HeroContentsPage.xmlui │ │ │ ├── HeroTextAlignPage.xmlui │ │ │ ├── HeroTextPage.xmlui │ │ │ └── HeroTonesPage.xmlui │ │ ├── Main.xmlui │ │ └── themes │ │ └── default.ts │ ├── index.html │ ├── index.ts │ ├── meta │ │ └── componentsMetadata.ts │ ├── package.json │ ├── public │ │ └── resources │ │ ├── building.jpg │ │ └── xmlui-logo.svg │ ├── src │ │ ├── Carousel │ │ │ ├── Carousel.module.scss │ │ │ ├── Carousel.tsx │ │ │ ├── CarouselContext.tsx │ │ │ └── CarouselNative.tsx │ │ ├── FancyButton │ │ │ ├── FancyButton.module.scss │ │ │ ├── FancyButton.tsx │ │ │ └── FancyButton.xmlui │ │ ├── Hello │ │ │ ├── Hello.tsx │ │ │ ├── Hello.xmlui │ │ │ └── Hello.xmlui.xs │ │ ├── HeroSection │ │ │ ├── HeroSection.module.scss │ │ │ ├── HeroSection.tsx │ │ │ └── HeroSectionNative.tsx │ │ ├── index.tsx │ │ ├── ScrollToTop │ │ │ ├── ScrollToTop.module.scss │ │ │ ├── ScrollToTop.tsx │ │ │ └── ScrollToTopNative.tsx │ │ └── vite-env.d.ts │ └── tsconfig.json ├── README.md ├── tools │ ├── codefence │ │ └── xmlui-code-fence-docs.md │ ├── create-app │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── create-app.ts │ │ ├── helpers │ │ │ ├── copy.ts │ │ │ ├── get-pkg-manager.ts │ │ │ ├── git.ts │ │ │ ├── install.ts │ │ │ ├── is-folder-empty.ts │ │ │ ├── is-writeable.ts │ │ │ ├── make-dir.ts │ │ │ └── validate-pkg.ts │ │ ├── index.ts │ │ ├── package.json │ │ ├── templates │ │ │ ├── default │ │ │ │ └── ts │ │ │ │ ├── gitignore │ │ │ │ ├── index.html │ │ │ │ ├── index.ts │ │ │ │ ├── public │ │ │ │ │ ├── mockServiceWorker.js │ │ │ │ │ ├── resources │ │ │ │ │ │ ├── favicon.ico │ │ │ │ │ │ └── xmlui-logo.svg │ │ │ │ │ └── serve.json │ │ │ │ └── src │ │ │ │ ├── components │ │ │ │ │ ├── ApiAware.xmlui │ │ │ │ │ ├── Home.xmlui │ │ │ │ │ ├── IncButton.xmlui │ │ │ │ │ └── PagePanel.xmlui │ │ │ │ ├── config.ts │ │ │ │ └── Main.xmlui │ │ │ ├── index.ts │ │ │ └── types.ts │ │ └── tsconfig.json │ ├── create-xmlui-hello-world │ │ ├── index.js │ │ └── package.json │ └── vscode │ ├── .gitignore │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ ├── .vscodeignore │ ├── build.sh │ ├── CHANGELOG.md │ ├── esbuild.js │ ├── eslint.config.mjs │ ├── formatter-docs.md │ ├── generate-test-sample.sh │ ├── LICENSE.md │ ├── package-lock.json │ ├── package.json │ ├── README.md │ ├── resources │ │ ├── xmlui-logo.png │ │ └── xmlui-markup-syntax-highlighting.png │ ├── src │ │ ├── extension.ts │ │ └── server.ts │ ├── syntaxes │ │ └── xmlui.tmLanguage.json │ ├── test-samples │ │ └── sample.xmlui │ ├── tsconfig.json │ └── tsconfig.tsbuildinfo ├── turbo.json └── xmlui ├── .gitignore ├── bin │ ├── bootstrap.js │ ├── build-lib.ts │ ├── build.ts │ ├── index.ts │ ├── preview.ts │ ├── start.ts │ ├── vite-xmlui-plugin.ts │ └── viteConfig.ts ├── CHANGELOG.md ├── conventions │ ├── component-qa-checklist.md │ ├── copilot-conventions.md │ ├── create-xmlui-components.md │ ├── mermaid.md │ ├── testing-conventions.md │ └── xmlui-in-a-nutshell.md ├── dev-docs │ ├── accessibility.md │ ├── build-system.md │ ├── build-xmlui.md │ ├── component-behaviors.md │ ├── containers.md │ ├── glossary.md │ ├── index.md │ ├── next │ │ ├── component-dev-guide.md │ │ ├── configuration-management-enhancement-summary.md │ │ ├── documentation-scripts-refactoring-complete-summary.md │ │ ├── documentation-scripts-refactoring-plan.md │ │ ├── duplicate-pattern-extraction-summary.md │ │ ├── error-handling-standardization-summary.md │ │ ├── generating-component-reference.md │ │ ├── index.md │ │ ├── logging-consistency-implementation-summary.md │ │ ├── project-build.md │ │ ├── project-structure.md │ │ ├── theme-context.md │ │ ├── tiptap-design-considerations.md │ │ ├── working-with-code.md │ │ ├── xmlui-runtime-architecture │ │ └── xmlui-wcag-accessibility-report.md │ ├── react-fundamentals.md │ ├── release-method.md │ ├── standalone-app.md │ ├── state-management.md │ └── xmlui-repo.md ├── package.json ├── playwright.config.ts ├── scripts │ ├── coverage-only.js │ ├── e2e-test-summary.js │ ├── generate-docs │ │ ├── build-downloads-map.mjs │ │ ├── build-pages-map.mjs │ │ ├── components-config.json │ │ ├── configuration-management.mjs │ │ ├── constants.mjs │ │ ├── create-theme-files.mjs │ │ ├── DocsGenerator.mjs │ │ ├── error-handling.mjs │ │ ├── extensions-config.json │ │ ├── folders.mjs │ │ ├── generate-summary-files.mjs │ │ ├── get-docs.mjs │ │ ├── input-handler.mjs │ │ ├── logger.mjs │ │ ├── logging-standards.mjs │ │ ├── MetadataProcessor.mjs │ │ ├── pattern-utilities.mjs │ │ └── utils.mjs │ ├── get-langserver-metadata.mjs │ ├── inline-links.mjs │ └── README-e2e-summary.md ├── src │ ├── abstractions │ │ ├── _conventions.md │ │ ├── ActionDefs.ts │ │ ├── AppContextDefs.ts │ │ ├── ComponentDefs.ts │ │ ├── ContainerDefs.ts │ │ ├── ExtensionDefs.ts │ │ ├── FunctionDefs.ts │ │ ├── RendererDefs.ts │ │ ├── scripting │ │ │ ├── BlockScope.ts │ │ │ ├── Compilation.ts │ │ │ ├── LogicalThread.ts │ │ │ ├── LoopScope.ts │ │ │ ├── modules.ts │ │ │ ├── ScriptParserError.ts │ │ │ ├── Token.ts │ │ │ ├── TryScope.ts │ │ │ └── TryScopeExp.ts │ │ └── ThemingDefs.ts │ ├── components │ │ ├── _conventions.md │ │ ├── abstractions.ts │ │ ├── Accordion │ │ │ ├── Accordion.md │ │ │ ├── Accordion.module.scss │ │ │ ├── Accordion.spec.ts │ │ │ ├── Accordion.tsx │ │ │ ├── AccordionContext.tsx │ │ │ ├── AccordionItem.tsx │ │ │ ├── AccordionItemNative.tsx │ │ │ └── AccordionNative.tsx │ │ ├── Animation │ │ │ └── AnimationNative.tsx │ │ ├── APICall │ │ │ ├── APICall.md │ │ │ ├── APICall.spec.ts │ │ │ ├── APICall.tsx │ │ │ └── APICallNative.tsx │ │ ├── App │ │ │ ├── App.md │ │ │ ├── App.module.scss │ │ │ ├── App.spec.ts │ │ │ ├── App.tsx │ │ │ ├── AppLayoutContext.ts │ │ │ ├── AppNative.tsx │ │ │ ├── AppStateContext.ts │ │ │ ├── doc-resources │ │ │ │ ├── condensed-sticky.xmlui │ │ │ │ ├── condensed.xmlui │ │ │ │ ├── horizontal-sticky.xmlui │ │ │ │ ├── horizontal.xmlui │ │ │ │ ├── vertical-full-header.xmlui │ │ │ │ ├── vertical-sticky.xmlui │ │ │ │ └── vertical.xmlui │ │ │ ├── IndexerContext.ts │ │ │ ├── LinkInfoContext.ts │ │ │ ├── SearchContext.tsx │ │ │ ├── Sheet.module.scss │ │ │ └── Sheet.tsx │ │ ├── AppHeader │ │ │ ├── AppHeader.md │ │ │ ├── AppHeader.module.scss │ │ │ ├── AppHeader.spec.ts │ │ │ ├── AppHeader.tsx │ │ │ └── AppHeaderNative.tsx │ │ ├── AppState │ │ │ ├── AppState.md │ │ │ ├── AppState.spec.ts │ │ │ ├── AppState.tsx │ │ │ └── AppStateNative.tsx │ │ ├── AutoComplete │ │ │ ├── AutoComplete.md │ │ │ ├── AutoComplete.module.scss │ │ │ ├── AutoComplete.spec.ts │ │ │ ├── AutoComplete.tsx │ │ │ ├── AutoCompleteContext.tsx │ │ │ └── AutoCompleteNative.tsx │ │ ├── Avatar │ │ │ ├── Avatar.md │ │ │ ├── Avatar.module.scss │ │ │ ├── Avatar.spec.ts │ │ │ ├── Avatar.tsx │ │ │ └── AvatarNative.tsx │ │ ├── Backdrop │ │ │ ├── Backdrop.md │ │ │ ├── Backdrop.module.scss │ │ │ ├── Backdrop.spec.ts │ │ │ ├── Backdrop.tsx │ │ │ └── BackdropNative.tsx │ │ ├── Badge │ │ │ ├── Badge.md │ │ │ ├── Badge.module.scss │ │ │ ├── Badge.spec.ts │ │ │ ├── Badge.tsx │ │ │ └── BadgeNative.tsx │ │ ├── Bookmark │ │ │ ├── Bookmark.md │ │ │ ├── Bookmark.module.scss │ │ │ ├── Bookmark.spec.ts │ │ │ ├── Bookmark.tsx │ │ │ └── BookmarkNative.tsx │ │ ├── Breakout │ │ │ ├── Breakout.module.scss │ │ │ ├── Breakout.spec.ts │ │ │ ├── Breakout.tsx │ │ │ └── BreakoutNative.tsx │ │ ├── Button │ │ │ ├── Button-style.spec.ts │ │ │ ├── Button.md │ │ │ ├── Button.module.scss │ │ │ ├── Button.spec.ts │ │ │ ├── Button.tsx │ │ │ └── ButtonNative.tsx │ │ ├── Card │ │ │ ├── Card.md │ │ │ ├── Card.module.scss │ │ │ ├── Card.spec.ts │ │ │ ├── Card.tsx │ │ │ └── CardNative.tsx │ │ ├── Carousel │ │ │ ├── Carousel.md │ │ │ ├── Carousel.module.scss │ │ │ ├── Carousel.spec.ts │ │ │ ├── Carousel.tsx │ │ │ ├── CarouselContext.tsx │ │ │ ├── CarouselItem.tsx │ │ │ ├── CarouselItemNative.tsx │ │ │ └── CarouselNative.tsx │ │ ├── ChangeListener │ │ │ ├── ChangeListener.md │ │ │ ├── ChangeListener.spec.ts │ │ │ ├── ChangeListener.tsx │ │ │ └── ChangeListenerNative.tsx │ │ ├── chart-color-schemes.ts │ │ ├── Charts │ │ │ ├── AreaChart │ │ │ │ ├── AreaChart.md │ │ │ │ ├── AreaChart.spec.ts │ │ │ │ ├── AreaChart.tsx │ │ │ │ └── AreaChartNative.tsx │ │ │ ├── BarChart │ │ │ │ ├── BarChart.md │ │ │ │ ├── BarChart.module.scss │ │ │ │ ├── BarChart.spec.ts │ │ │ │ ├── BarChart.tsx │ │ │ │ └── BarChartNative.tsx │ │ │ ├── DonutChart │ │ │ │ ├── DonutChart.spec.ts │ │ │ │ └── DonutChart.tsx │ │ │ ├── LabelList │ │ │ │ ├── LabelList.spec.ts │ │ │ │ ├── LabelList.tsx │ │ │ │ ├── LabelListNative.module.scss │ │ │ │ └── LabelListNative.tsx │ │ │ ├── Legend │ │ │ │ ├── Legend.spec.ts │ │ │ │ ├── Legend.tsx │ │ │ │ └── LegendNative.tsx │ │ │ ├── LineChart │ │ │ │ ├── LineChart.md │ │ │ │ ├── LineChart.module.scss │ │ │ │ ├── LineChart.spec.ts │ │ │ │ ├── LineChart.tsx │ │ │ │ └── LineChartNative.tsx │ │ │ ├── PieChart │ │ │ │ ├── PieChart.md │ │ │ │ ├── PieChart.spec.ts │ │ │ │ ├── PieChart.tsx │ │ │ │ ├── PieChartNative.module.scss │ │ │ │ └── PieChartNative.tsx │ │ │ ├── RadarChart │ │ │ │ ├── RadarChart.md │ │ │ │ ├── RadarChart.spec.ts │ │ │ │ ├── RadarChart.tsx │ │ │ │ └── RadarChartNative.tsx │ │ │ ├── Tooltip │ │ │ │ ├── TooltipContent.module.scss │ │ │ │ ├── TooltipContent.spec.ts │ │ │ │ └── TooltipContent.tsx │ │ │ └── utils │ │ │ ├── abstractions.ts │ │ │ └── ChartProvider.tsx │ │ ├── Checkbox │ │ │ ├── Checkbox.md │ │ │ ├── Checkbox.spec.ts │ │ │ └── Checkbox.tsx │ │ ├── CodeBlock │ │ │ ├── CodeBlock.module.scss │ │ │ ├── CodeBlock.spec.ts │ │ │ ├── CodeBlock.tsx │ │ │ ├── CodeBlockNative.tsx │ │ │ └── highlight-code.ts │ │ ├── collectedComponentMetadata.ts │ │ ├── ColorPicker │ │ │ ├── ColorPicker.md │ │ │ ├── ColorPicker.module.scss │ │ │ ├── ColorPicker.spec.ts │ │ │ ├── ColorPicker.tsx │ │ │ └── ColorPickerNative.tsx │ │ ├── Column │ │ │ ├── Column.md │ │ │ ├── Column.tsx │ │ │ ├── ColumnNative.tsx │ │ │ ├── doc-resources │ │ │ │ └── list-component-data.js │ │ │ └── TableContext.tsx │ │ ├── component-utils.ts │ │ ├── ComponentProvider.tsx │ │ ├── ComponentRegistryContext.tsx │ │ ├── container-helpers.tsx │ │ ├── ContentSeparator │ │ │ ├── ContentSeparator.md │ │ │ ├── ContentSeparator.module.scss │ │ │ ├── ContentSeparator.spec.ts │ │ │ ├── ContentSeparator.tsx │ │ │ └── ContentSeparatorNative.tsx │ │ ├── DataSource │ │ │ ├── DataSource.md │ │ │ └── DataSource.tsx │ │ ├── DateInput │ │ │ ├── DateInput.md │ │ │ ├── DateInput.module.scss │ │ │ ├── DateInput.spec.ts │ │ │ ├── DateInput.tsx │ │ │ └── DateInputNative.tsx │ │ ├── DatePicker │ │ │ ├── DatePicker.md │ │ │ ├── DatePicker.module.scss │ │ │ ├── DatePicker.spec.ts │ │ │ ├── DatePicker.tsx │ │ │ └── DatePickerNative.tsx │ │ ├── DropdownMenu │ │ │ ├── DropdownMenu.md │ │ │ ├── DropdownMenu.module.scss │ │ │ ├── DropdownMenu.spec.ts │ │ │ ├── DropdownMenu.tsx │ │ │ ├── DropdownMenuNative.tsx │ │ │ ├── MenuItem.md │ │ │ └── SubMenuItem.md │ │ ├── EmojiSelector │ │ │ ├── EmojiSelector.md │ │ │ ├── EmojiSelector.spec.ts │ │ │ ├── EmojiSelector.tsx │ │ │ └── EmojiSelectorNative.tsx │ │ ├── ExpandableItem │ │ │ ├── ExpandableItem.module.scss │ │ │ ├── ExpandableItem.spec.ts │ │ │ ├── ExpandableItem.tsx │ │ │ └── ExpandableItemNative.tsx │ │ ├── FileInput │ │ │ ├── FileInput.md │ │ │ ├── FileInput.module.scss │ │ │ ├── FileInput.spec.ts │ │ │ ├── FileInput.tsx │ │ │ └── FileInputNative.tsx │ │ ├── FileUploadDropZone │ │ │ ├── FileUploadDropZone.md │ │ │ ├── FileUploadDropZone.module.scss │ │ │ ├── FileUploadDropZone.spec.ts │ │ │ ├── FileUploadDropZone.tsx │ │ │ └── FileUploadDropZoneNative.tsx │ │ ├── FlowLayout │ │ │ ├── FlowLayout.md │ │ │ ├── FlowLayout.module.scss │ │ │ ├── FlowLayout.spec.ts │ │ │ ├── FlowLayout.spec.ts-snapshots │ │ │ │ └── Edge-cases-boxShadow-is-not-clipped-1-non-smoke-darwin.png │ │ │ ├── FlowLayout.tsx │ │ │ └── FlowLayoutNative.tsx │ │ ├── Footer │ │ │ ├── Footer.md │ │ │ ├── Footer.module.scss │ │ │ ├── Footer.spec.ts │ │ │ ├── Footer.tsx │ │ │ └── FooterNative.tsx │ │ ├── Form │ │ │ ├── Form.md │ │ │ ├── Form.module.scss │ │ │ ├── Form.spec.ts │ │ │ ├── Form.tsx │ │ │ ├── formActions.ts │ │ │ ├── FormContext.ts │ │ │ └── FormNative.tsx │ │ ├── FormItem │ │ │ ├── FormItem.md │ │ │ ├── FormItem.module.scss │ │ │ ├── FormItem.spec.ts │ │ │ ├── FormItem.tsx │ │ │ ├── FormItemNative.tsx │ │ │ ├── HelperText.module.scss │ │ │ ├── HelperText.tsx │ │ │ ├── ItemWithLabel.tsx │ │ │ └── Validations.ts │ │ ├── FormSection │ │ │ ├── FormSection.md │ │ │ ├── FormSection.ts │ │ │ └── FormSection.xmlui │ │ ├── Fragment │ │ │ ├── Fragment.spec.ts │ │ │ └── Fragment.tsx │ │ ├── Heading │ │ │ ├── abstractions.ts │ │ │ ├── H1.md │ │ │ ├── H1.spec.ts │ │ │ ├── H2.md │ │ │ ├── H2.spec.ts │ │ │ ├── H3.md │ │ │ ├── H3.spec.ts │ │ │ ├── H4.md │ │ │ ├── H4.spec.ts │ │ │ ├── H5.md │ │ │ ├── H5.spec.ts │ │ │ ├── H6.md │ │ │ ├── H6.spec.ts │ │ │ ├── Heading.md │ │ │ ├── Heading.module.scss │ │ │ ├── Heading.spec.ts │ │ │ ├── Heading.tsx │ │ │ └── HeadingNative.tsx │ │ ├── HoverCard │ │ │ ├── HoverCard.tsx │ │ │ └── HovercardNative.tsx │ │ ├── HtmlTags │ │ │ ├── HtmlTags.module.scss │ │ │ ├── HtmlTags.spec.ts │ │ │ └── HtmlTags.tsx │ │ ├── Icon │ │ │ ├── AdmonitionDanger.tsx │ │ │ ├── AdmonitionInfo.tsx │ │ │ ├── AdmonitionNote.tsx │ │ │ ├── AdmonitionTip.tsx │ │ │ ├── AdmonitionWarning.tsx │ │ │ ├── ApiIcon.tsx │ │ │ ├── ArrowDropDown.module.scss │ │ │ ├── ArrowDropDown.tsx │ │ │ ├── ArrowDropUp.module.scss │ │ │ ├── ArrowDropUp.tsx │ │ │ ├── ArrowLeft.module.scss │ │ │ ├── ArrowLeft.tsx │ │ │ ├── ArrowRight.module.scss │ │ │ ├── ArrowRight.tsx │ │ │ ├── Attach.tsx │ │ │ ├── Binding.module.scss │ │ │ ├── Binding.tsx │ │ │ ├── BoardIcon.tsx │ │ │ ├── BoxIcon.tsx │ │ │ ├── CheckIcon.tsx │ │ │ ├── ChevronDownIcon.tsx │ │ │ ├── ChevronLeft.tsx │ │ │ ├── ChevronRight.tsx │ │ │ ├── ChevronUpIcon.tsx │ │ │ ├── CodeFileIcon.tsx │ │ │ ├── CodeSandbox.tsx │ │ │ ├── CompactListIcon.tsx │ │ │ ├── ContentCopyIcon.tsx │ │ │ ├── DarkToLightIcon.tsx │ │ │ ├── DatabaseIcon.module.scss │ │ │ ├── DatabaseIcon.tsx │ │ │ ├── DocFileIcon.tsx │ │ │ ├── DocIcon.tsx │ │ │ ├── DotMenuHorizontalIcon.tsx │ │ │ ├── DotMenuIcon.tsx │ │ │ ├── EmailIcon.tsx │ │ │ ├── EmptyFolderIcon.tsx │ │ │ ├── ErrorIcon.tsx │ │ │ ├── ExpressionIcon.tsx │ │ │ ├── FillPlusCricleIcon.tsx │ │ │ ├── FilterIcon.tsx │ │ │ ├── FolderIcon.tsx │ │ │ ├── GlobeIcon.tsx │ │ │ ├── HomeIcon.tsx │ │ │ ├── HyperLinkIcon.tsx │ │ │ ├── Icon.md │ │ │ ├── Icon.module.scss │ │ │ ├── Icon.spec.ts │ │ │ ├── Icon.tsx │ │ │ ├── IconNative.tsx │ │ │ ├── ImageFileIcon.tsx │ │ │ ├── Inspect.tsx │ │ │ ├── LightToDark.tsx │ │ │ ├── LinkIcon.tsx │ │ │ ├── ListIcon.tsx │ │ │ ├── LooseListIcon.tsx │ │ │ ├── MoonIcon.tsx │ │ │ ├── MoreOptionsIcon.tsx │ │ │ ├── NoSortIcon.tsx │ │ │ ├── PDFIcon.tsx │ │ │ ├── PenIcon.tsx │ │ │ ├── PhoneIcon.tsx │ │ │ ├── PhotoIcon.tsx │ │ │ ├── PlusIcon.tsx │ │ │ ├── SearchIcon.tsx │ │ │ ├── ShareIcon.tsx │ │ │ ├── SortAscendingIcon.tsx │ │ │ ├── SortDescendingIcon.tsx │ │ │ ├── StarsIcon.tsx │ │ │ ├── SunIcon.tsx │ │ │ ├── svg │ │ │ │ ├── admonition_danger.svg │ │ │ │ ├── admonition_info.svg │ │ │ │ ├── admonition_note.svg │ │ │ │ ├── admonition_tip.svg │ │ │ │ ├── admonition_warning.svg │ │ │ │ ├── api.svg │ │ │ │ ├── arrow-dropdown.svg │ │ │ │ ├── arrow-left.svg │ │ │ │ ├── arrow-right.svg │ │ │ │ ├── arrow-up.svg │ │ │ │ ├── attach.svg │ │ │ │ ├── binding.svg │ │ │ │ ├── box.svg │ │ │ │ ├── bulb.svg │ │ │ │ ├── code-file.svg │ │ │ │ ├── code-sandbox.svg │ │ │ │ ├── dark_to_light.svg │ │ │ │ ├── database.svg │ │ │ │ ├── doc.svg │ │ │ │ ├── empty-folder.svg │ │ │ │ ├── expression.svg │ │ │ │ ├── eye-closed.svg │ │ │ │ ├── eye-dark.svg │ │ │ │ ├── eye.svg │ │ │ │ ├── file-text.svg │ │ │ │ ├── filter.svg │ │ │ │ ├── folder.svg │ │ │ │ ├── img.svg │ │ │ │ ├── inspect.svg │ │ │ │ ├── light_to_dark.svg │ │ │ │ ├── moon.svg │ │ │ │ ├── pdf.svg │ │ │ │ ├── photo.svg │ │ │ │ ├── share.svg │ │ │ │ ├── stars.svg │ │ │ │ ├── sun.svg │ │ │ │ ├── trending-down.svg │ │ │ │ ├── trending-level.svg │ │ │ │ ├── trending-up.svg │ │ │ │ ├── txt.svg │ │ │ │ ├── unknown-file.svg │ │ │ │ ├── unlink.svg │ │ │ │ └── xls.svg │ │ │ ├── TableDeleteColumnIcon.tsx │ │ │ ├── TableDeleteRowIcon.tsx │ │ │ ├── TableInsertColumnIcon.tsx │ │ │ ├── TableInsertRowIcon.tsx │ │ │ ├── TrashIcon.tsx │ │ │ ├── TrendingDownIcon.tsx │ │ │ ├── TrendingLevelIcon.tsx │ │ │ ├── TrendingUpIcon.tsx │ │ │ ├── TxtIcon.tsx │ │ │ ├── UnknownFileIcon.tsx │ │ │ ├── UnlinkIcon.tsx │ │ │ ├── UserIcon.tsx │ │ │ ├── WarningIcon.tsx │ │ │ └── XlsIcon.tsx │ │ ├── IconProvider.tsx │ │ ├── IconRegistryContext.tsx │ │ ├── IFrame │ │ │ ├── IFrame.md │ │ │ ├── IFrame.module.scss │ │ │ ├── IFrame.spec.ts │ │ │ ├── IFrame.tsx │ │ │ └── IFrameNative.tsx │ │ ├── Image │ │ │ ├── Image.md │ │ │ ├── Image.module.scss │ │ │ ├── Image.spec.ts │ │ │ ├── Image.tsx │ │ │ └── ImageNative.tsx │ │ ├── Input │ │ │ ├── index.ts │ │ │ ├── InputAdornment.module.scss │ │ │ ├── InputAdornment.tsx │ │ │ ├── InputDivider.module.scss │ │ │ ├── InputDivider.tsx │ │ │ ├── InputLabel.module.scss │ │ │ ├── InputLabel.tsx │ │ │ ├── PartialInput.module.scss │ │ │ └── PartialInput.tsx │ │ ├── InspectButton │ │ │ ├── InspectButton.module.scss │ │ │ └── InspectButton.tsx │ │ ├── Items │ │ │ ├── Items.md │ │ │ ├── Items.spec.ts │ │ │ ├── Items.tsx │ │ │ └── ItemsNative.tsx │ │ ├── Link │ │ │ ├── Link.md │ │ │ ├── Link.module.scss │ │ │ ├── Link.spec.ts │ │ │ ├── Link.tsx │ │ │ └── LinkNative.tsx │ │ ├── List │ │ │ ├── doc-resources │ │ │ │ └── list-component-data.js │ │ │ ├── List.md │ │ │ ├── List.module.scss │ │ │ ├── List.spec.ts │ │ │ ├── List.tsx │ │ │ └── ListNative.tsx │ │ ├── Logo │ │ │ ├── doc-resources │ │ │ │ └── xmlui-logo.svg │ │ │ ├── Logo.md │ │ │ ├── Logo.tsx │ │ │ └── LogoNative.tsx │ │ ├── Markdown │ │ │ ├── CodeText.module.scss │ │ │ ├── CodeText.tsx │ │ │ ├── Markdown.md │ │ │ ├── Markdown.module.scss │ │ │ ├── Markdown.spec.ts │ │ │ ├── Markdown.tsx │ │ │ ├── MarkdownNative.tsx │ │ │ ├── parse-binding-expr.ts │ │ │ └── utils.ts │ │ ├── metadata-helpers.ts │ │ ├── ModalDialog │ │ │ ├── ConfirmationModalContextProvider.tsx │ │ │ ├── Dialog.module.scss │ │ │ ├── Dialog.tsx │ │ │ ├── ModalDialog.md │ │ │ ├── ModalDialog.module.scss │ │ │ ├── ModalDialog.spec.ts │ │ │ ├── ModalDialog.tsx │ │ │ ├── ModalDialogNative.tsx │ │ │ └── ModalVisibilityContext.tsx │ │ ├── NavGroup │ │ │ ├── NavGroup.md │ │ │ ├── NavGroup.module.scss │ │ │ ├── NavGroup.spec.ts │ │ │ ├── NavGroup.tsx │ │ │ ├── NavGroupContext.ts │ │ │ └── NavGroupNative.tsx │ │ ├── NavLink │ │ │ ├── NavLink.md │ │ │ ├── NavLink.module.scss │ │ │ ├── NavLink.spec.ts │ │ │ ├── NavLink.tsx │ │ │ └── NavLinkNative.tsx │ │ ├── NavPanel │ │ │ ├── NavPanel.md │ │ │ ├── NavPanel.module.scss │ │ │ ├── NavPanel.spec.ts │ │ │ ├── NavPanel.tsx │ │ │ └── NavPanelNative.tsx │ │ ├── NestedApp │ │ │ ├── AppWithCodeView.module.scss │ │ │ ├── AppWithCodeView.tsx │ │ │ ├── AppWithCodeViewNative.tsx │ │ │ ├── defaultProps.tsx │ │ │ ├── logo.svg │ │ │ ├── NestedApp.module.scss │ │ │ ├── NestedApp.tsx │ │ │ ├── NestedAppNative.tsx │ │ │ ├── Tooltip.module.scss │ │ │ ├── Tooltip.tsx │ │ │ └── utils.ts │ │ ├── NoResult │ │ │ ├── NoResult.md │ │ │ ├── NoResult.module.scss │ │ │ ├── NoResult.spec.ts │ │ │ ├── NoResult.tsx │ │ │ └── NoResultNative.tsx │ │ ├── NumberBox │ │ │ ├── numberbox-abstractions.ts │ │ │ ├── NumberBox.md │ │ │ ├── NumberBox.module.scss │ │ │ ├── NumberBox.spec.ts │ │ │ ├── NumberBox.tsx │ │ │ └── NumberBoxNative.tsx │ │ ├── Option │ │ │ ├── Option.md │ │ │ ├── Option.spec.ts │ │ │ ├── Option.tsx │ │ │ ├── OptionNative.tsx │ │ │ └── OptionTypeProvider.tsx │ │ ├── PageMetaTitle │ │ │ ├── PageMetaTilteNative.tsx │ │ │ ├── PageMetaTitle.md │ │ │ ├── PageMetaTitle.spec.ts │ │ │ └── PageMetaTitle.tsx │ │ ├── Pages │ │ │ ├── Page.md │ │ │ ├── Pages.md │ │ │ ├── Pages.module.scss │ │ │ ├── Pages.tsx │ │ │ └── PagesNative.tsx │ │ ├── Pagination │ │ │ ├── Pagination.md │ │ │ ├── Pagination.module.scss │ │ │ ├── Pagination.spec.ts │ │ │ ├── Pagination.tsx │ │ │ └── PaginationNative.tsx │ │ ├── PositionedContainer │ │ │ ├── PositionedContainer.module.scss │ │ │ ├── PositionedContainer.tsx │ │ │ └── PositionedContainerNative.tsx │ │ ├── ProfileMenu │ │ │ ├── ProfileMenu.module.scss │ │ │ └── ProfileMenu.tsx │ │ ├── ProgressBar │ │ │ ├── ProgressBar.md │ │ │ ├── ProgressBar.module.scss │ │ │ ├── ProgressBar.spec.ts │ │ │ ├── ProgressBar.tsx │ │ │ └── ProgressBarNative.tsx │ │ ├── Queue │ │ │ ├── Queue.md │ │ │ ├── Queue.spec.ts │ │ │ ├── Queue.tsx │ │ │ ├── queueActions.ts │ │ │ └── QueueNative.tsx │ │ ├── RadioGroup │ │ │ ├── RadioGroup.md │ │ │ ├── RadioGroup.module.scss │ │ │ ├── RadioGroup.spec.ts │ │ │ ├── RadioGroup.tsx │ │ │ ├── RadioGroupNative.tsx │ │ │ ├── RadioItem.tsx │ │ │ └── RadioItemNative.tsx │ │ ├── RealTimeAdapter │ │ │ ├── RealTimeAdapter.tsx │ │ │ └── RealTimeAdapterNative.tsx │ │ ├── Redirect │ │ │ ├── Redirect.md │ │ │ ├── Redirect.spec.ts │ │ │ └── Redirect.tsx │ │ ├── ResponsiveBar │ │ │ ├── README.md │ │ │ ├── ResponsiveBar.md │ │ │ ├── ResponsiveBar.module.scss │ │ │ ├── ResponsiveBar.spec.ts │ │ │ ├── ResponsiveBar.tsx │ │ │ └── ResponsiveBarNative.tsx │ │ ├── Select │ │ │ ├── HiddenOption.tsx │ │ │ ├── MultiSelectOption.tsx │ │ │ ├── OptionContext.ts │ │ │ ├── Select.md │ │ │ ├── Select.module.scss │ │ │ ├── Select.spec.ts │ │ │ ├── Select.tsx │ │ │ ├── SelectContext.tsx │ │ │ ├── SelectNative.tsx │ │ │ ├── SelectOption.tsx │ │ │ └── SimpleSelect.tsx │ │ ├── SelectionStore │ │ │ ├── SelectionStore.md │ │ │ ├── SelectionStore.tsx │ │ │ └── SelectionStoreNative.tsx │ │ ├── Slider │ │ │ ├── Slider.md │ │ │ ├── Slider.module.scss │ │ │ ├── Slider.spec.ts │ │ │ ├── Slider.tsx │ │ │ └── SliderNative.tsx │ │ ├── Slot │ │ │ ├── Slot.md │ │ │ ├── Slot.spec.ts │ │ │ └── Slot.ts │ │ ├── SlotItem.tsx │ │ ├── SpaceFiller │ │ │ ├── SpaceFiller.md │ │ │ ├── SpaceFiller.module.scss │ │ │ ├── SpaceFiller.spec.ts │ │ │ ├── SpaceFiller.tsx │ │ │ └── SpaceFillerNative.tsx │ │ ├── Spinner │ │ │ ├── Spinner.md │ │ │ ├── Spinner.module.scss │ │ │ ├── Spinner.spec.ts │ │ │ ├── Spinner.tsx │ │ │ └── SpinnerNative.tsx │ │ ├── Splitter │ │ │ ├── HSplitter.md │ │ │ ├── HSplitter.spec.ts │ │ │ ├── Splitter.md │ │ │ ├── Splitter.module.scss │ │ │ ├── Splitter.spec.ts │ │ │ ├── Splitter.tsx │ │ │ ├── SplitterNative.tsx │ │ │ ├── utils.ts │ │ │ ├── VSplitter.md │ │ │ └── VSplitter.spec.ts │ │ ├── Stack │ │ │ ├── CHStack.md │ │ │ ├── CHStack.spec.ts │ │ │ ├── CVStack.md │ │ │ ├── CVStack.spec.ts │ │ │ ├── HStack.md │ │ │ ├── HStack.spec.ts │ │ │ ├── Stack.md │ │ │ ├── Stack.module.scss │ │ │ ├── Stack.spec.ts │ │ │ ├── Stack.tsx │ │ │ ├── StackNative.tsx │ │ │ ├── VStack.md │ │ │ └── VStack.spec.ts │ │ ├── StickyBox │ │ │ ├── StickyBox.md │ │ │ ├── StickyBox.module.scss │ │ │ ├── StickyBox.tsx │ │ │ └── StickyBoxNative.tsx │ │ ├── Switch │ │ │ ├── Switch.md │ │ │ ├── Switch.spec.ts │ │ │ └── Switch.tsx │ │ ├── Table │ │ │ ├── doc-resources │ │ │ │ └── list-component-data.js │ │ │ ├── react-table-config.d.ts │ │ │ ├── Table.md │ │ │ ├── Table.module.scss │ │ │ ├── Table.spec.ts │ │ │ ├── Table.tsx │ │ │ ├── TableNative.tsx │ │ │ └── useRowSelection.tsx │ │ ├── TableOfContents │ │ │ ├── TableOfContents.module.scss │ │ │ ├── TableOfContents.spec.ts │ │ │ ├── TableOfContents.tsx │ │ │ └── TableOfContentsNative.tsx │ │ ├── Tabs │ │ │ ├── TabContext.tsx │ │ │ ├── TabItem.md │ │ │ ├── TabItem.tsx │ │ │ ├── TabItemNative.tsx │ │ │ ├── Tabs.md │ │ │ ├── Tabs.module.scss │ │ │ ├── Tabs.spec.ts │ │ │ ├── Tabs.tsx │ │ │ └── TabsNative.tsx │ │ ├── Text │ │ │ ├── Text.md │ │ │ ├── Text.module.scss │ │ │ ├── Text.spec.ts │ │ │ ├── Text.tsx │ │ │ └── TextNative.tsx │ │ ├── TextArea │ │ │ ├── TextArea.md │ │ │ ├── TextArea.module.scss │ │ │ ├── TextArea.spec.ts │ │ │ ├── TextArea.tsx │ │ │ ├── TextAreaNative.tsx │ │ │ ├── TextAreaResizable.tsx │ │ │ └── useComposedRef.ts │ │ ├── TextBox │ │ │ ├── TextBox.md │ │ │ ├── TextBox.module.scss │ │ │ ├── TextBox.spec.ts │ │ │ ├── TextBox.tsx │ │ │ └── TextBoxNative.tsx │ │ ├── Theme │ │ │ ├── NotificationToast.tsx │ │ │ ├── Theme.md │ │ │ ├── Theme.module.scss │ │ │ ├── Theme.spec.ts │ │ │ ├── Theme.tsx │ │ │ └── ThemeNative.tsx │ │ ├── TimeInput │ │ │ ├── TimeInput.md │ │ │ ├── TimeInput.module.scss │ │ │ ├── TimeInput.spec.ts │ │ │ ├── TimeInput.tsx │ │ │ ├── TimeInputNative.tsx │ │ │ └── utils.ts │ │ ├── Timer │ │ │ ├── Timer.md │ │ │ ├── Timer.spec.ts │ │ │ ├── Timer.tsx │ │ │ └── TimerNative.tsx │ │ ├── Toggle │ │ │ ├── Toggle.module.scss │ │ │ └── Toggle.tsx │ │ ├── ToneChangerButton │ │ │ ├── ToneChangerButton.md │ │ │ ├── ToneChangerButton.spec.ts │ │ │ └── ToneChangerButton.tsx │ │ ├── ToneSwitch │ │ │ ├── ToneSwitch.md │ │ │ ├── ToneSwitch.module.scss │ │ │ ├── ToneSwitch.spec.ts │ │ │ ├── ToneSwitch.tsx │ │ │ └── ToneSwitchNative.tsx │ │ ├── Tooltip │ │ │ ├── Tooltip.md │ │ │ ├── Tooltip.module.scss │ │ │ ├── Tooltip.spec.ts │ │ │ ├── Tooltip.tsx │ │ │ └── TooltipNative.tsx │ │ ├── Tree │ │ │ ├── testData.ts │ │ │ ├── Tree-dynamic.spec.ts │ │ │ ├── Tree-icons.spec.ts │ │ │ ├── Tree.md │ │ │ ├── Tree.spec.ts │ │ │ ├── TreeComponent.module.scss │ │ │ ├── TreeComponent.tsx │ │ │ └── TreeNative.tsx │ │ ├── TreeDisplay │ │ │ ├── TreeDisplay.md │ │ │ ├── TreeDisplay.module.scss │ │ │ ├── TreeDisplay.tsx │ │ │ └── TreeDisplayNative.tsx │ │ ├── ValidationSummary │ │ │ ├── ValidationSummary.module.scss │ │ │ └── ValidationSummary.tsx │ │ └── VisuallyHidden.tsx │ ├── components-core │ │ ├── abstractions │ │ │ ├── ComponentRenderer.ts │ │ │ ├── LoaderRenderer.ts │ │ │ ├── standalone.ts │ │ │ └── treeAbstractions.ts │ │ ├── action │ │ │ ├── actions.ts │ │ │ ├── APICall.tsx │ │ │ ├── FileDownloadAction.tsx │ │ │ ├── FileUploadAction.tsx │ │ │ ├── NavigateAction.tsx │ │ │ └── TimedAction.tsx │ │ ├── ApiBoundComponent.tsx │ │ ├── appContext │ │ │ ├── date-functions.ts │ │ │ ├── math-function.ts │ │ │ └── misc-utils.ts │ │ ├── AppContext.tsx │ │ ├── behaviors │ │ │ ├── Behavior.tsx │ │ │ └── CoreBehaviors.tsx │ │ ├── component-hooks.ts │ │ ├── ComponentDecorator.tsx │ │ ├── ComponentViewer.tsx │ │ ├── CompoundComponent.tsx │ │ ├── constants.ts │ │ ├── DebugViewProvider.tsx │ │ ├── descriptorHelper.ts │ │ ├── devtools │ │ │ ├── InspectorDialog.module.scss │ │ │ ├── InspectorDialog.tsx │ │ │ └── InspectorDialogVisibilityContext.tsx │ │ ├── EngineError.ts │ │ ├── event-handlers.ts │ │ ├── InspectorButton.module.scss │ │ ├── InspectorContext.tsx │ │ ├── interception │ │ │ ├── abstractions.ts │ │ │ ├── ApiInterceptor.ts │ │ │ ├── ApiInterceptorProvider.tsx │ │ │ ├── apiInterceptorWorker.ts │ │ │ ├── Backend.ts │ │ │ ├── Errors.ts │ │ │ ├── IndexedDb.ts │ │ │ ├── initMock.ts │ │ │ ├── InMemoryDb.ts │ │ │ ├── ReadonlyCollection.ts │ │ │ └── useApiInterceptorContext.tsx │ │ ├── loader │ │ │ ├── ApiLoader.tsx │ │ │ ├── DataLoader.tsx │ │ │ ├── ExternalDataLoader.tsx │ │ │ ├── Loader.tsx │ │ │ ├── MockLoaderRenderer.tsx │ │ │ └── PageableLoader.tsx │ │ ├── LoaderComponent.tsx │ │ ├── markup-check.ts │ │ ├── parts.ts │ │ ├── renderers.ts │ │ ├── rendering │ │ │ ├── AppContent.tsx │ │ │ ├── AppRoot.tsx │ │ │ ├── AppWrapper.tsx │ │ │ ├── buildProxy.ts │ │ │ ├── collectFnVarDeps.ts │ │ │ ├── ComponentAdapter.tsx │ │ │ ├── ComponentWrapper.tsx │ │ │ ├── Container.tsx │ │ │ ├── containers.ts │ │ │ ├── ContainerWrapper.tsx │ │ │ ├── ErrorBoundary.module.scss │ │ │ ├── ErrorBoundary.tsx │ │ │ ├── InvalidComponent.module.scss │ │ │ ├── InvalidComponent.tsx │ │ │ ├── nodeUtils.ts │ │ │ ├── reducer.ts │ │ │ ├── renderChild.tsx │ │ │ ├── StandaloneComponent.tsx │ │ │ ├── StateContainer.tsx │ │ │ ├── UnknownComponent.module.scss │ │ │ ├── UnknownComponent.tsx │ │ │ └── valueExtractor.ts │ │ ├── reportEngineError.ts │ │ ├── RestApiProxy.ts │ │ ├── script-runner │ │ │ ├── asyncProxy.ts │ │ │ ├── AttributeValueParser.ts │ │ │ ├── bannedFunctions.ts │ │ │ ├── BindingTreeEvaluationContext.ts │ │ │ ├── eval-tree-async.ts │ │ │ ├── eval-tree-common.ts │ │ │ ├── eval-tree-sync.ts │ │ │ ├── ParameterParser.ts │ │ │ ├── process-statement-async.ts │ │ │ ├── process-statement-common.ts │ │ │ ├── process-statement-sync.ts │ │ │ ├── ScriptingSourceTree.ts │ │ │ ├── simplify-expression.ts │ │ │ ├── statement-queue.ts │ │ │ └── visitors.ts │ │ ├── StandaloneApp.tsx │ │ ├── StandaloneExtensionManager.ts │ │ ├── TableOfContentsContext.tsx │ │ ├── theming │ │ │ ├── _themes.scss │ │ │ ├── component-layout-resolver.ts │ │ │ ├── extendThemeUtils.ts │ │ │ ├── hvar.ts │ │ │ ├── layout-resolver.ts │ │ │ ├── parse-layout-props.ts │ │ │ ├── StyleContext.tsx │ │ │ ├── StyleRegistry.ts │ │ │ ├── ThemeContext.tsx │ │ │ ├── ThemeProvider.tsx │ │ │ ├── themes │ │ │ │ ├── base-utils.ts │ │ │ │ ├── palette.ts │ │ │ │ ├── root.ts │ │ │ │ ├── solid.ts │ │ │ │ ├── theme-colors.ts │ │ │ │ └── xmlui.ts │ │ │ ├── themeVars.module.scss │ │ │ ├── themeVars.ts │ │ │ ├── transformThemeVars.ts │ │ │ └── utils.ts │ │ ├── utils │ │ │ ├── actionUtils.ts │ │ │ ├── audio-utils.ts │ │ │ ├── compound-utils.ts │ │ │ ├── css-utils.ts │ │ │ ├── DataLoaderQueryKeyGenerator.ts │ │ │ ├── date-utils.ts │ │ │ ├── extractParam.ts │ │ │ ├── hooks.tsx │ │ │ ├── LruCache.ts │ │ │ ├── mergeProps.ts │ │ │ ├── misc.ts │ │ │ ├── request-params.ts │ │ │ ├── statementUtils.ts │ │ │ └── treeUtils.ts │ │ └── xmlui-parser.ts │ ├── index-standalone.ts │ ├── index.scss │ ├── index.ts │ ├── language-server │ │ ├── server-common.ts │ │ ├── server-web-worker.ts │ │ ├── server.ts │ │ ├── services │ │ │ ├── common │ │ │ │ ├── docs-generation.ts │ │ │ │ ├── lsp-utils.ts │ │ │ │ ├── metadata-utils.ts │ │ │ │ └── syntax-node-utilities.ts │ │ │ ├── completion.ts │ │ │ ├── diagnostic.ts │ │ │ ├── format.ts │ │ │ └── hover.ts │ │ └── xmlui-metadata-generated.mjs │ ├── logging │ │ ├── LoggerContext.tsx │ │ ├── LoggerInitializer.tsx │ │ ├── LoggerService.ts │ │ └── xmlui.ts │ ├── logo.svg │ ├── parsers │ │ ├── common │ │ │ ├── GenericToken.ts │ │ │ ├── InputStream.ts │ │ │ └── utils.ts │ │ ├── scripting │ │ │ ├── code-behind-collect.ts │ │ │ ├── Lexer.ts │ │ │ ├── modules.ts │ │ │ ├── Parser.ts │ │ │ ├── ParserError.ts │ │ │ ├── ScriptingNodeTypes.ts │ │ │ ├── TokenTrait.ts │ │ │ ├── TokenType.ts │ │ │ └── tree-visitor.ts │ │ ├── style-parser │ │ │ ├── errors.ts │ │ │ ├── source-tree.ts │ │ │ ├── StyleInputStream.ts │ │ │ ├── StyleLexer.ts │ │ │ ├── StyleParser.ts │ │ │ └── tokens.ts │ │ └── xmlui-parser │ │ ├── CharacterCodes.ts │ │ ├── diagnostics.ts │ │ ├── fileExtensions.ts │ │ ├── index.ts │ │ ├── lint.ts │ │ ├── parser.ts │ │ ├── ParserError.ts │ │ ├── scanner.ts │ │ ├── syntax-kind.ts │ │ ├── syntax-node.ts │ │ ├── transform.ts │ │ ├── utils.ts │ │ ├── xmlui-serializer.ts │ │ └── xmlui-tree.ts │ ├── react-app-env.d.ts │ ├── syntax │ │ ├── monaco │ │ │ ├── grammar.monacoLanguage.ts │ │ │ ├── index.ts │ │ │ ├── xmlui-dark.ts │ │ │ ├── xmlui-light.ts │ │ │ └── xmluiscript.monacoLanguage.ts │ │ └── textMate │ │ ├── index.ts │ │ ├── xmlui-dark.json │ │ ├── xmlui-light.json │ │ ├── xmlui.json │ │ └── xmlui.tmLanguage.json │ ├── testing │ │ ├── assertions.ts │ │ ├── component-test-helpers.ts │ │ ├── ComponentDrivers.ts │ │ ├── drivers │ │ │ ├── DateInputDriver.ts │ │ │ ├── ModalDialogDriver.ts │ │ │ ├── NumberBoxDriver.ts │ │ │ ├── TextBoxDriver.ts │ │ │ ├── TimeInputDriver.ts │ │ │ ├── TimerDriver.ts │ │ │ └── TreeDriver.ts │ │ ├── fixtures.ts │ │ ├── infrastructure │ │ │ ├── index.html │ │ │ ├── main.tsx │ │ │ ├── public │ │ │ │ ├── mockServiceWorker.js │ │ │ │ ├── resources │ │ │ │ │ ├── bell.svg │ │ │ │ │ ├── box.svg │ │ │ │ │ ├── doc.svg │ │ │ │ │ ├── eye.svg │ │ │ │ │ ├── flower-640x480.jpg │ │ │ │ │ ├── sun.svg │ │ │ │ │ ├── test-image-100x100.jpg │ │ │ │ │ └── txt.svg │ │ │ │ └── serve.json │ │ │ └── TestBed.tsx │ │ └── themed-app-test-helpers.ts │ └── vite-env.d.ts ├── tests │ ├── components │ │ ├── CodeBlock │ │ │ └── hightlight-code.test.ts │ │ ├── playground-pattern.test.ts │ │ └── Tree │ │ └── Tree-states.test.ts │ ├── components-core │ │ ├── abstractions │ │ │ └── treeAbstractions.test.ts │ │ ├── container │ │ │ └── buildProxy.test.ts │ │ ├── interception │ │ │ ├── orderBy.test.ts │ │ │ ├── ReadOnlyCollection.test.ts │ │ │ └── request-param-converter.test.ts │ │ ├── scripts-runner │ │ │ ├── AttributeValueParser.test.ts │ │ │ ├── eval-tree-arrow-async.test.ts │ │ │ ├── eval-tree-arrow.test.ts │ │ │ ├── eval-tree-func-decl-async.test.ts │ │ │ ├── eval-tree-func-decl.test.ts │ │ │ ├── eval-tree-pre-post.test.ts │ │ │ ├── eval-tree-regression.test.ts │ │ │ ├── eval-tree.test.ts │ │ │ ├── function-proxy.test.ts │ │ │ ├── parser-regression.test.ts │ │ │ ├── process-event.test.ts │ │ │ ├── process-function.test.ts │ │ │ ├── process-implicit-context.test.ts │ │ │ ├── process-statement-asgn.test.ts │ │ │ ├── process-statement-destruct.test.ts │ │ │ ├── process-statement-regs.test.ts │ │ │ ├── process-statement-sync.test.ts │ │ │ ├── process-statement.test.ts │ │ │ ├── process-switch-sync.test.ts │ │ │ ├── process-switch.test.ts │ │ │ ├── process-try-sync.test.ts │ │ │ ├── process-try.test.ts │ │ │ └── test-helpers.ts │ │ ├── test-metadata-handler.ts │ │ ├── theming │ │ │ ├── border-segments.test.ts │ │ │ ├── component-layout.resolver.test.ts │ │ │ ├── layout-property-parser.test.ts │ │ │ ├── layout-resolver.test.ts │ │ │ ├── layout-resolver2.test.ts │ │ │ ├── layout-vp-override.test.ts │ │ │ └── padding-segments.test.ts │ │ └── utils │ │ ├── date-utils.test.ts │ │ ├── format-human-elapsed-time.test.ts │ │ └── LruCache.test.ts │ ├── language-server │ │ ├── completion.test.ts │ │ ├── format.test.ts │ │ ├── hover.test.ts │ │ └── mockData.ts │ └── parsers │ ├── common │ │ └── input-stream.test.ts │ ├── markdown │ │ └── parse-binding-expression.test.ts │ ├── parameter-parser.test.ts │ ├── paremeter-parser.test.ts │ ├── scripting │ │ ├── eval-tree-arrow.test.ts │ │ ├── eval-tree-pre-post.test.ts │ │ ├── eval-tree.test.ts │ │ ├── function-proxy.test.ts │ │ ├── lexer-literals.test.ts │ │ ├── lexer-misc.test.ts │ │ ├── module-parse.test.ts │ │ ├── parser-arrow.test.ts │ │ ├── parser-assignments.test.ts │ │ ├── parser-binary.test.ts │ │ ├── parser-destructuring.test.ts │ │ ├── parser-errors.test.ts │ │ ├── parser-expressions.test.ts │ │ ├── parser-function.test.ts │ │ ├── parser-literals.test.ts │ │ ├── parser-primary.test.ts │ │ ├── parser-regex.test.ts │ │ ├── parser-statements.test.ts │ │ ├── parser-unary.test.ts │ │ ├── process-event.test.ts │ │ ├── process-implicit-context.test.ts │ │ ├── process-statement-asgn.test.ts │ │ ├── process-statement-destruct.test.ts │ │ ├── process-statement-regs.test.ts │ │ ├── process-statement-sync.test.ts │ │ ├── process-statement.test.ts │ │ ├── process-switch-sync.test.ts │ │ ├── process-switch.test.ts │ │ ├── process-try-sync.test.ts │ │ ├── process-try.test.ts │ │ ├── simplify-expression.test.ts │ │ ├── statement-hooks.test.ts │ │ └── test-helpers.ts │ ├── style-parser │ │ ├── generateHvarChain.test.ts │ │ ├── parseHVar.test.ts │ │ ├── parser.test.ts │ │ └── tokens.test.ts │ └── xmlui │ ├── lint.test.ts │ ├── parser.test.ts │ ├── scanner.test.ts │ ├── transform.attr.test.ts │ ├── transform.circular.test.ts │ ├── transform.element.test.ts │ ├── transform.errors.test.ts │ ├── transform.escape.test.ts │ ├── transform.regression.test.ts │ ├── transform.script.test.ts │ ├── transform.test.ts │ └── xmlui.ts ├── tests-e2e │ ├── api-bound-component-regression.spec.ts │ ├── api-call-as-extracted-component.spec.ts │ ├── assign-to-object-or-array-regression.spec.ts │ ├── binding-regression.spec.ts │ ├── children-as-template-context-vars.spec.ts │ ├── compound-component.spec.ts │ ├── context-vars-regression.spec.ts │ ├── data-bindings.spec.ts │ ├── datasource-and-api-usage-in-var.spec.ts │ ├── datasource-direct-binding.spec.ts │ ├── datasource-onLoaded-regression.spec.ts │ ├── modify-array-item-regression.spec.ts │ ├── namespaces.spec.ts │ ├── push-to-array-regression.spec.ts │ ├── screen-breakpoints.spec.ts │ ├── scripting.spec.ts │ ├── state-scope-in-pages.spec.ts │ └── state-var-scopes.spec.ts ├── tsconfig.bin.json ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── vitest.config.ts ``` # Files -------------------------------------------------------------------------------- /xmlui/dev-docs/containers.md: -------------------------------------------------------------------------------- ```markdown 1 | # XMLUI Container-Based State Management 2 | 3 | A deep dive into XMLUI's hierarchical container system for managing component state, variables, and reactive data binding. 4 | 5 | ## Fundamentals of XMLUI Reactivity 6 | 7 | XMLUI implements **automatic reactivity** - a programming model where UI updates happen automatically when underlying data changes, without requiring manual DOM manipulation or explicit update calls. 8 | 9 | ### The Reactive Loop 10 | 11 | XMLUI follows a simple reactive cycle: 12 | 1. **Initial Render**: Framework renders the complete UI from declarative markup 13 | 2. **Event Occurrence**: User interactions or system events trigger changes 14 | 3. **Automatic State Detection**: Framework detects state mutations transparently 15 | 4. **Selective UI Refresh**: Only components affected by the state change re-render 16 | 5. **Loop Continuation**: Process repeats for subsequent events 17 | 18 | From a developer perspective, reactivity means: 19 | - **Write declarative expressions**: Use `{count}` to display values, `count++` to modify them 20 | - **No manual updates**: Framework automatically handles UI synchronization 21 | - **Transparent mutations**: Change variables directly without special APIs 22 | - **Predictable behavior**: UI always reflects current state consistently 23 | 24 | ### The Role of Containers in Reactivity Implementation 25 | 26 | Containers are the **core mechanism** that enables XMLUI's automatic reactivity. They provide the infrastructure necessary to detect state changes, coordinate updates, and maintain UI consistency. 27 | 28 | Reactivity requires several technical capabilities: 29 | - **State Storage**: A place to hold component variables and data 30 | - **Change Detection**: Mechanism to detect when state mutations occur 31 | - **Update Coordination**: System to propagate changes to affected UI components 32 | - **Scope Management**: Control over which components can access which state 33 | - **Performance Optimization**: Efficient updates that don't block the UI 34 | 35 | Containers provide all these capabilities through a hierarchical architecture that mirrors the component structure. 36 | 37 | ## Automatic Container Wrapping 38 | 39 | XMLUI **automatically wraps components with containers** when they need reactive capabilities. You declare variables or data sources, and the framework creates the container infrastructure. 40 | 41 | The framework creates containers for these cases: 42 | - **App Root Container** - Wraps the entire application entry point 43 | - **User-Defined Component Containers** - Each component instance gets isolated state 44 | - **Variables** - Reactive state holders that trigger UI updates 45 | - **Loaders** - Handle asynchronous data operations 46 | - **Uses Declarations** - Control state inheritance from parents 47 | - **Context Variables** - Framework-injected variables (routing, forms) 48 | - **Scripts** - JavaScript blocks with variables and functions 49 | - **Code-Behind Files** - External script files for application root or component roots 50 | 51 | Every component receives an identifier (user-defined or framework-assigned). **User-defined IDs enable programmatic component interaction** - accessing methods, properties, and APIs. Framework IDs handle internal state. Component IDs scope to their declaring file (.xmlui). These IDs are stored in the App Root and User-Defined Component Containers. 52 | 53 | ### App Root Container 54 | For every XMLUI application, a root container is automatically created to wrap the entire application entry point (typically Main.xmlui). This ensures there's always a top-level container to manage application-wide state. 55 | 56 | **Example - Main.xmlui:** 57 | ```xml 58 | <!-- File: Main.xmlui --> 59 | <App> 60 | <VStack> 61 | <Text id="title">Welcome to My App</Text> 62 | <Button id="actionBtn" label="Click Me" /> 63 | <Button label="Cancel" /> 64 | </VStack> 65 | </App> 66 | ``` 67 | 68 | ```mermaid 69 | %%{init: {"block": {"padding": 8}}}%% 70 | block-beta 71 | columns 1 72 | 73 | AR["<div style='text-align: left; width: 100%; padding: 0 15px;'>🏠 App Root Container<br/>💡 Container State:<br/> ├─ title → Text reference<br/> ├─ actionBtn → Button reference<br/> └─ [[cancel]] → Button reference<br/>🌳 Component Tree:<br/> VStack:<br/> ├─ Text (id: title)<br/> ├─ Button (id: actionBtn)<br/> └─ Button (id: [[cancel]])</div>"] 74 | 75 | classDef rootContainer fill:#f0f0f0,stroke:#888,stroke-width:2px,color:#333,padding-left:15px,padding-right:15px 76 | class AR rootContainer 77 | ``` 78 | 79 | > **Note**: `[[cancel]]` is an framework-assigned ID for the Cancel button since it doesn't have an explicit `id` attribute. 80 | 81 | In this example, XMLUI automatically creates a root container that stores the IDs for `title`, `actionBtn`, and `[[cancel]]`, making them accessible throughout the application. 82 | 83 | ### User-Defined Component Instance Containers 84 | Each time you use a user-defined component (created with `<Component name="...">`) in your markup, XMLUI automatically creates a container for that specific instance. This ensures **Instance Isolation** - each component usage gets its own state container with isolated internal state. 85 | 86 | **Example Files:** 87 | 88 | **File: Main.xmlui** 89 | ```xml 90 | <!-- File: Main.xmlui --> 91 | <App> 92 | <VStack> 93 | <!-- Each MyButton usage creates its own container --> 94 | <MyButton label="Save" /> <!-- Creates Container A --> 95 | <MyButton label="Cancel" /> <!-- Creates Container B --> 96 | <MyButton label="Delete" /> <!-- Creates Container C --> 97 | </VStack> 98 | </App> 99 | ``` 100 | 101 | **File: components/MyButton.xmlui** 102 | ```xml 103 | <!-- File: components/MyButton.xmlui --> 104 | <Component name="MyButton" var.count="{0}"> 105 | <Button 106 | label="{$props.label} ({count})" 107 | onClick="count++" 108 | /> 109 | </Component> 110 | ``` 111 | 112 | ```mermaid 113 | %%{init: {"block": {"padding": 8}}}%% 114 | block-beta 115 | columns 3 116 | 117 | block:AppRoot:3 118 | AR["<div style='text-align: left; width: 100%'>🏠 App Root Container<br/>💡 Container State: MyButton instances<br/>🌳 Component Tree:<br/> VStack:<br/> ├─ MyButton (Container A)<br/> ├─ MyButton (Container B)<br/> └─ MyButton (Container C)</div>"] 119 | end 120 | 121 | CA["<div style='text-align: left; width: 100%'>🏠 Container A<br/>💡 State:<br/> ├─ count: 0<br/> └─ $props.label: 'Save'<br/>🌳 Component: Button (Save 0)</div>"] 122 | 123 | CB["<div style='text-align: left; width: 100%'>🏠 Container B<br/>💡 State:<br/> ├─ count: 0<br/> └─ $props.label: 'Cancel'<br/>🌳 Component: Button (Cancel 0)</div>"] 124 | 125 | CC["<div style='text-align: left; width: 100%'>🏠 Container C<br/>💡 State:<br/> ├─ count: 0<br/> └─ $props.label: 'Delete'<br/>🌳 Component: Button (Delete 0)</div>"] 126 | 127 | classDef rootContainer fill:#f0f0f0,stroke:#888,stroke-width:2px,color:#333 128 | classDef innerContainer fill:#f8f8f8,stroke:#aaa,stroke-width:1px,color:#333 129 | 130 | class AR,CC,CB,CA rootContainer 131 | class ARS,ART,CHC,CHB,CHA,CSC,CSB,CSA innerContainer 132 | ``` 133 | 134 | Each `MyButton` instance gets its own container with isolated `count` variable - clicking one button doesn't affect the others. 135 | 136 | ### Variables 137 | Variables in XMLUI are reactive state holders that automatically trigger UI updates when changed. They can contain any JavaScript value, including functions. Variables can declare functions, and those functions can be invoked. 138 | 139 | **Simple Variable Example:** 140 | ```xml 141 | <Stack var.count="{0}"> 142 | <Button onClick="count++" label="Count: {count}" /> 143 | </Stack> 144 | ``` 145 | 146 | **Function Variable Example:** 147 | ```xml 148 | <Stack var.count="{0}" var.increment="{() => count++}"> 149 | <Button onClick="increment()" label="Count: {count}" /> 150 | </Stack> 151 | ``` 152 | 153 | Variables and functions can also be defined in `<script>` tags: 154 | 155 | ```xml 156 | <Stack> 157 | <script> 158 | let count = 0; 159 | const increment = () => count++; 160 | function reset() { count = 0; } 161 | </script> 162 | <Button onClick="increment()" label="Count: {count}" /> 163 | <Button onClick="reset()" label="Reset" /> 164 | </Stack> 165 | ``` 166 | 167 | **Role in Reactivity**: Variables provide the state storage layer for reactive data binding. When a variable changes, the container detects this through proxy-based change detection and triggers selective UI updates. Functions participate in dependency tracking - when they reference other variables, the system tracks these dependencies and re-evaluates when dependencies change. 168 | 169 | ### Loaders (`loaders`) 170 | Loaders are XMLUI's mechanism for handling asynchronous data operations. The framework automatically creates loaders when it recognizes that components contain data-handling requirements. This happens through the **ApiBoundComponent** system. 171 | 172 | **Automatic Loader Creation Cases:** 173 | 174 | 1. **Properties with DataSource/DataSourceRef types**: When a component property contains a `DataSource` or `DataSourceRef` object 175 | 2. **Events with API action types**: When a component event contains `APICall`, `FileDownload`, or `FileUpload` objects 176 | 3. **Components with data properties**: Any component with URL-based `data` properties 177 | 178 | ```xml 179 | <!-- Property-based loader creation --> 180 | <Table data="{users}" /> 181 | <!-- Framework creates internal DataLoader when 'users' references a DataSource --> 182 | 183 | <!-- Direct URL pattern (frequently used) --> 184 | <Table data="/api/users" /> 185 | <!-- Framework automatically creates DataLoader for URL-based data properties --> 186 | 187 | <!-- Event-based loader creation --> 188 | <Button> 189 | Save 190 | <event name="click"> 191 | <APICall url="/api/save" method="POST" /> 192 | </event> 193 | </Button> 194 | <!-- Framework creates APICall handler --> 195 | 196 | <!-- Explicit DataSource (also creates loaders) --> 197 | <DataSource id="users" url="/api/users" /> 198 | <Table data="{users}" /> 199 | <!-- Creates DataLoader managing: loading states, error states, caching, polling --> 200 | ``` 201 | 202 | **Framework Detection Process**: The `ComponentAdapter` scans component properties and events for specific object types that require API operations. It looks for: 203 | - **Properties**: Objects with `type: "DataSource"` or `type: "DataSourceRef"` 204 | - **Events**: Objects with `type: "APICall"`, `type: "FileDownload"`, or `type: "FileUpload"` 205 | 206 | When any of these object types are found, the framework wraps the component in an `ApiBoundComponent`, which automatically generates the necessary loaders and wires them into the container system. 207 | 208 | **Role in Reactivity**: Loaders manage asynchronous state transitions (loading → loaded/error) and integrate with the container's reducer system via actions like `LOADER_LOADED`, `LOADER_IN_PROGRESS_CHANGED`, etc. They provide reactive properties like `users.value`, `users.inProgress`, `users.loaded`. 209 | 210 | ### Uses Declarations (`uses`) 211 | The `uses` property provides explicit control over state inheritance from parent containers: 212 | 213 | ```xml 214 | <!-- Inherit all parent state (default) --> 215 | <Stack> 216 | <!-- children --> 217 | </Stack> 218 | 219 | <!-- Inherit no parent state --> 220 | <Stack uses="[]"> 221 | <!-- children --> 222 | </Stack> 223 | 224 | <!-- Inherit only specific parent state --> 225 | <Stack uses="['userInfo', 'theme']"> 226 | <!-- children --> 227 | </Stack> 228 | ``` 229 | 230 | **Note**: XMLUI is moving toward automating the use of `uses` declarations, with plans to eliminate this property in favor of automatic dependency detection. 231 | 232 | **Role in Reactivity**: Controls the scope of reactive data flow between parent and child containers. This prevents unnecessary re-renders when unrelated parent state changes and provides explicit data dependencies. 233 | 234 | ### Context Variables 235 | Context variables are automatically injected by the framework and component implementations to provide contextual information to child components. These cannot be declared through attributes but are provided by the React implementation of XMLUI components. 236 | 237 | **Naming Convention**: Context variables start with `$` by convention to distinguish them from user-defined variables. 238 | 239 | **Examples of Context Variables** (not exhaustive): 240 | - **Routing**: `$pathname`, `$routeParams`, `$queryParams`, `$linkInfo` 241 | - **Iterators**: `$item`, `$itemIndex` (in Lists, Tables, etc.) 242 | - **Forms**: `$data`, `$validationResult`, `$value`, `$setValue` (in FormItems) 243 | - **Events**: `$param` (in event handlers) 244 | 245 | Different components may inject additional context variables specific to their functionality. 246 | 247 | ```xml 248 | <!-- Context variables are automatically available --> 249 | <Table data="/api/users"> 250 | <Column bindTo="name"> 251 | <Text>{$item.name}</Text> <!-- $item provided by Table --> 252 | </Column> 253 | </Table> 254 | 255 | <Page url="/users/:id"> 256 | <Text>User ID: {$routeParams.id}</Text> <!-- $routeParams provided by Page --> 257 | </Page> 258 | ``` 259 | 260 | **Role in Reactivity**: Context variables are automatically injected into the container state, making them available for reactive data binding. When these variables change (e.g., route changes, current item changes), all dependent UI elements automatically update. 261 | 262 | ### Scripts 263 | JavaScript code blocks that declare variables and functions: 264 | 265 | ```xml 266 | <Stack> 267 | <script> 268 | var counter = 0; 269 | function increment() { counter++; } 270 | var multiplier = (x) => x * 2; 271 | </script> 272 | <Button onClick="increment()" label="Count: {counter}" /> 273 | </Stack> 274 | ``` 275 | 276 | **Role in Reactivity**: Scripts are parsed and their declarations (variables, functions) become part of the container's reactive state. The scripting system integrates with the dependency tracking and memoization systems to ensure efficient updates. 277 | 278 | ### Code-Behind Files 279 | Code-behind files provide a way to define scripts in separate files rather than inline within components. The framework automatically treats these external script files as if they were `<script>` declarations within the application root or user-defined component root. 280 | 281 | **File Naming Convention:** 282 | - **Application Root**: `Main.xmlui.xs` (for `Main.xmlui`) 283 | - **User-Defined Component**: `ComponentName.xmlui.xs` (for `ComponentName.xmlui`) 284 | 285 | **Role in Reactivity**: Code-behind files are processed identically to inline `<script>` blocks. Their variable and function declarations become part of the container's reactive state, with the same dependency tracking and memoization behavior. The framework automatically imports and executes code-behind files during component initialization, making their exports available in the component's reactive scope. 286 | 287 | ### Examples 288 | 289 | **Important**: Simply having an `id` attribute does NOT automatically create a container. Component IDs are handled differently and are stored in the nearest parent container. 290 | 291 | Components are wrapped in containers when they have any of these characteristics (determined by the `isContainerLike` function): 292 | 293 | ```tsx 294 | // From ContainerWrapper.tsx - Container creation logic 295 | export function isContainerLike(node: ComponentDef) { 296 | if (node.type === "Container") { 297 | return true; 298 | } 299 | 300 | // If any of the following properties have a value, we need a container 301 | return !!( 302 | node.loaders || // Data loading operations 303 | node.vars || // Variable declarations 304 | node.uses || // Parent state scoping 305 | node.contextVars || // Context variable provision 306 | node.functions || // Function declarations 307 | node.scriptCollected // Script blocks 308 | ); 309 | } 310 | ``` 311 | 312 | **Container Creation Examples:** 313 | ```xml 314 | <!-- Creates container - has variable --> 315 | <Stack var.count="{0}" /> 316 | 317 | <!-- Creates container - has script --> 318 | <Stack> 319 | <script>let x = 1;</script> 320 | </Stack> 321 | 322 | <!-- Creates container - has data loading --> 323 | <Table data="/api/users" /> 324 | 325 | <!-- Does NOT create container - only has ID --> 326 | <Stack id="myStack" /> 327 | 328 | <!-- Does NOT create container - only layout properties --> 329 | <Stack direction="horizontal" /> 330 | ``` 331 | 332 | ## Container Structure, Hierarchy, and Features 333 | 334 | This section explores the architectural design of containers, their hierarchical organization, and how they support XMLUI's special concepts like loaders, user-defined components, and reactive variables. 335 | 336 | ### Container Hierarchy 337 | 338 | The container system consists of three main React components working together: 339 | 340 | ``` 341 | ContainerWrapper 342 | └── ErrorBoundary 343 | └── StateContainer 344 | └── ErrorBoundary 345 | └── Container 346 | ``` 347 | 348 | ### Core Container Components 349 | 350 | #### ContainerWrapper 351 | - **Purpose**: Outer wrapper that converts components into containerized form 352 | - **Location**: `/xmlui/src/components-core/rendering/ContainerWrapper.tsx` 353 | - **Key Responsibility**: Determines if a component needs containerization and wraps it accordingly 354 | 355 | Components are wrapped in containers if they have any of these characteristics: 356 | - Loaders (data management operations) 357 | - Variables declared (`vars`) 358 | - Context variables (`contextVars`) 359 | - Scripts (`script` or `scriptCollected`) 360 | - Explicit state usage declarations (`uses` property) 361 | 362 | #### StateContainer 363 | - **Purpose**: Manages the actual state storage and state composition 364 | - **Location**: `/xmlui/src/components-core/rendering/StateContainer.tsx` 365 | - **Key Responsibility**: Assembles state from multiple sources and manages state lifecycle 366 | 367 | #### Container 368 | - **Purpose**: Executes event handlers and manages state changes while maintaining UI responsiveness 369 | - **Location**: `/xmlui/src/components-core/rendering/Container.tsx` 370 | - **Key Responsibility**: Asynchronous event handler execution and state synchronization 371 | 372 | ## State Composition and Management 373 | 374 | ### ContainerState Type 375 | 376 | ```typescript 377 | // From /xmlui/src/abstractions/ContainerDefs.ts 378 | export type ContainerState = Record<string | symbol, any>; 379 | ``` 380 | 381 | ### State Assembly Process 382 | 383 | The `StateContainer` assembles the complete state from multiple layers: 384 | 385 | 1. **State from Parent**: Inherited state from parent containers (with optional scoping via `uses`) 386 | 2. **Component APIs**: Exposed component methods and properties 387 | 3. **Local Variables**: Variables declared in the current component 388 | 4. **Context Variables**: Variables provided to child components 389 | 5. **Routing Parameters**: URL parameters, query strings, and navigation context 390 | 391 | ### State Flow Implementation 392 | 393 | ```typescript 394 | // Simplified flow from StateContainer.tsx 395 | const stateFromOutside = extractScopedState(parentState, node.uses); 396 | const componentStateWithApis = mergeComponentApis(componentState, componentApis); 397 | const localVarsStateContext = useCombinedState(stateFromOutside, componentStateWithApis, node.contextVars); 398 | const resolvedLocalVars = useVars(varDefinitions, functionDeps, localVarsStateContext, memoedVars); 399 | const combinedState = useCombinedState(stateFromOutside, node.contextVars, mergedWithVars, routingParams); 400 | ``` 401 | 402 | ### Variable Resolution and Memoization 403 | 404 | Variables are resolved through a sophisticated memoization system: 405 | 406 | ```typescript 407 | // From StateContainer.tsx - useVars function 408 | type MemoedVars = Map<any, { 409 | getDependencies: (value: string | CodeDeclaration, referenceTrackedApi: Record<string, ComponentApi>) => Array<string>; 410 | obtainValue: (expression: any, state: ContainerState, appContext: AppContextObject | undefined, strict: boolean | undefined, stateDeps: Record<string, any>, appContextDeps: Record<string, any>) => any; 411 | }>; 412 | ``` 413 | 414 | #### Variable Dependency Tracking 415 | 416 | 1. **Function Dependencies**: Arrow expressions and their variable dependencies are collected first 417 | 2. **Variable Dependencies**: Dependencies between variables are resolved in multiple passes 418 | 3. **Memoization**: Results are memoized based on dependency changes to avoid unnecessary recalculation 419 | 420 | ## Reducer-Based State Updates 421 | 422 | ### Container Actions 423 | 424 | State changes are managed through Redux-style actions via the `ContainerActionKind` enum: 425 | 426 | ```typescript 427 | // From /xmlui/src/components-core/rendering/containers.ts 428 | export const enum ContainerActionKind { 429 | LOADER_LOADED = "ContainerActionKind:LOADER_LOADED", 430 | LOADER_IN_PROGRESS_CHANGED = "ContainerActionKind:LOADER_IN_PROGRESS_CHANGED", 431 | LOADER_IS_REFETCHING_CHANGED = "ContainerActionKind:LOADER_IS_REFETCHING_CHANGED", 432 | LOADER_ERROR = "ContainerActionKind:LOADER_ERROR", 433 | EVENT_HANDLER_STARTED = "ContainerActionKind:EVENT_HANDLER_STARTED", 434 | EVENT_HANDLER_COMPLETED = "ContainerActionKind:EVENT_HANDLER_COMPLETED", 435 | EVENT_HANDLER_ERROR = "ContainerActionKind:EVENT_HANDLER_ERROR", 436 | COMPONENT_STATE_CHANGED = "ContainerActionKind:COMPONENT_STATE_CHANGED", 437 | STATE_PART_CHANGED = "ContainerActionKind:STATE_PART_CHANGED", 438 | } 439 | ``` 440 | 441 | ### Partial State Changes 442 | 443 | The most sophisticated action is `STATE_PART_CHANGED`, which handles deep object/array mutations: 444 | 445 | ```typescript 446 | // Example: Updating nested property some.count++ 447 | { 448 | "type": "ContainerActionKind:STATE_PART_CHANGED", 449 | "payload": { 450 | "actionType": "set", 451 | "path": ["some", "count"], 452 | "target": { "count": 0 }, 453 | "value": 1, 454 | "localVars": resolvedLocalVars 455 | } 456 | } 457 | ``` 458 | 459 | The reducer uses Lodash's `setWith` to update nested paths while preserving object vs array types based on target structure. 460 | 461 | ## Proxy-Based Change Detection 462 | 463 | ### BuildProxy Function 464 | 465 | State changes are detected using JavaScript Proxies that intercept get/set operations: 466 | 467 | ```typescript 468 | // From /xmlui/src/components-core/rendering/buildProxy.ts 469 | export function buildProxy( 470 | proxyTarget: any, 471 | callback: (changeInfo: ProxyCallbackArgs) => void, 472 | tree: Array<string | symbol> = [], 473 | ): any { 474 | return new Proxy(proxyTarget, { 475 | get: function (target, prop, receiver) { 476 | const value = Reflect.get(target, prop, receiver); 477 | // Create nested proxies for objects and arrays 478 | if (value && typeof value === "object" && ["Array", "Object"].includes(value.constructor.name)) { 479 | if (!proxiedValues.has(value)) { 480 | proxiedValues.set(value, buildProxy(value, callback, tree.concat(prop))); 481 | } 482 | return proxiedValues.get(value); 483 | } 484 | return value; 485 | }, 486 | set: function (target, prop, value, receiver) { 487 | // Notify of state changes 488 | callback({ 489 | action: "set", 490 | path: getPath(prop), 491 | pathArray: tree.concat(prop), 492 | target, 493 | newValue: value, 494 | previousValue: Reflect.get(target, prop, receiver), 495 | }); 496 | return Reflect.set(target, prop, value, receiver); 497 | } 498 | }); 499 | } 500 | ``` 501 | 502 | ### Change Propagation 503 | 504 | When a proxied state change occurs: 505 | 506 | 1. **Detection**: Proxy intercepts the change and calls the callback 507 | 2. **Path Analysis**: Determines if the change belongs to current container or parent 508 | 3. **Dispatch**: Sends `STATE_PART_CHANGED` action to appropriate container 509 | 4. **Reduction**: Immer-based reducer applies the change immutably 510 | 5. **Re-render**: React re-renders affected components 511 | 512 | ## Component Identification and APIs 513 | 514 | ### Symbol-Based Component IDs 515 | 516 | Components within containers are identified using JavaScript Symbols: 517 | 518 | ```typescript 519 | // Component ID resolution 520 | const componentUid = componentId ?? Symbol(id); 521 | if (id) { 522 | componentUid.description = id; // For debugging and API access 523 | } 524 | ``` 525 | 526 | ### Component ID Storage vs Accessibility 527 | 528 | A key insight about XMLUI's component management is understanding where component IDs are stored, how they flow through the container hierarchy, and what happens when a component has an ID but doesn't create its own container. 529 | 530 | **Component ID Creation and Storage Flow** 531 | 532 | 1. **Universal ID Assignment**: ALL components receive a unique Symbol-based ID, regardless of whether they create containers: 533 | 534 | ```tsx 535 | // From ComponentAdapter.tsx - every component gets a UID 536 | const uid = useMemo(() => Symbol(safeNode.uid), [safeNode.uid]); 537 | ``` 538 | 539 | 2. **Container vs Non-Container Components**: 540 | - **Components that create containers**: Store their child component IDs in their own `componentApis` state 541 | - **Components that DON'T create containers**: Their IDs are stored in the nearest parent container 542 | 543 | 3. **Registration Mechanism**: Components register their APIs (including their ID) with the nearest container: 544 | 545 | ```tsx 546 | // From ComponentAdapter.tsx - all components register with nearest container 547 | const memoedRegisterComponentApi: RegisterComponentApiFn = useCallback( 548 | (api) => { 549 | registerComponentApi(uid, api); // Registers with nearest parent container 550 | }, 551 | [registerComponentApi, uid], 552 | ); 553 | ``` 554 | 555 | 4. **Storage in Parent Container**: The `StateContainer` maintains a `componentApis` map: 556 | 557 | ```tsx 558 | // From StateContainer.tsx - parent container stores all child component IDs 559 | const registerComponentApi: RegisterComponentApiFnInner = useCallback((uid, api) => { 560 | setComponentApis(produce((draft) => { 561 | if (!draft[uid]) { 562 | draft[uid] = {}; 563 | } 564 | Object.entries(api).forEach(([key, value]) => { 565 | draft[uid][key] = value; 566 | }); 567 | })); 568 | }, []); 569 | ``` 570 | 571 | **Global Access Through State Composition** 572 | Even though component IDs are stored hierarchically, they become accessible file-wide through state inheritance: 573 | 574 | ```tsx 575 | // From StateContainer.tsx - state flows down through hierarchy 576 | const combinedState = useCombinedState( 577 | stateFromOutside, // Parent state (contains parent component IDs) 578 | node.contextVars, // Framework context variables 579 | mergedWithVars, // Local variables and component state 580 | routingParams, // Routing parameters 581 | ); 582 | ``` 583 | 584 | **Example Scenarios**: 585 | 586 | 1. **Component with ID but no container**: 587 | ```xml 588 | <Stack var.count="{0}"> <!-- Creates container A --> 589 | <Button id="myBtn" /> <!-- ID stored in container A --> 590 | <Text id="myText" /> <!-- ID also stored in container A --> 591 | </Stack> 592 | ``` 593 | 594 | 2. **Nested containers with inheritance**: 595 | ```xml 596 | <Stack var.count="{0}"> <!-- Creates container A --> 597 | <Button id="myBtn" /> <!-- ID stored in container A --> 598 | <Stack var.data="{null}"> <!-- Creates container B --> 599 | <Text>{myBtn.visible}</Text> <!-- Can access myBtn via state inheritance --> 600 | <Button id="innerBtn" /> <!-- ID stored in container B --> 601 | </Stack> 602 | </Stack> 603 | ``` 604 | 605 | **Storage and Access Pattern**: 606 | - `myBtn` and `myText` IDs are stored in container A's `componentApis` (they don't create their own containers) 607 | - `innerBtn` ID is stored in container B's `componentApis` 608 | - Container B inherits parent state through `stateFromOutside`, making `myBtn` accessible 609 | - All components can reference each other within the same .xmlui file through the state inheritance chain 610 | 611 | **Root Component Storage**: 612 | If `<Stack id="myStack" />` is a root component that doesn't create its own container, its ID is stored in the automatically-created root container. XMLUI ensures there's always at least one container in the hierarchy to store component IDs. 613 | 614 | This design ensures that: 615 | - **Performance**: Each container only manages its direct responsibility 616 | - **Usability**: All components within a .xmlui file can reference each other 617 | - **Consistency**: Component IDs flow predictably through the container hierarchy 618 | - **Reliability**: There's always a root container to store component IDs, even for simple root components 619 | 620 | ### Component API Registration 621 | 622 | Components can expose APIs that other components can invoke: 623 | 624 | ```typescript 625 | // From StateContainer.tsx 626 | const registerComponentApi: RegisterComponentApiFnInner = useCallback((uid, api) => { 627 | setComponentApis(produce((draft) => { 628 | if (!draft[uid]) { 629 | draft[uid] = {}; 630 | } 631 | Object.entries(api).forEach(([key, value]) => { 632 | if (draft[uid][key] !== value) { 633 | draft[uid][key] = value; 634 | } 635 | }); 636 | })); 637 | }, []); 638 | ``` 639 | 640 | APIs are merged into the component state so they're accessible via the component's ID: 641 | 642 | ```typescript 643 | // API access pattern 644 | componentStateWithApis[componentId] = { ...componentState, ...exposedApi }; 645 | ``` 646 | 647 | ## Asynchronous Event Handler Execution 648 | 649 | ### UI Responsiveness Strategy 650 | 651 | The Container component maintains UI responsiveness during long-running operations through: 652 | 653 | 1. **Async Instruction Execution**: Each instruction in an event handler runs asynchronously 654 | 2. **State Synchronization Points**: Execution pauses after each instruction for state updates 655 | 3. **Promise-Based Coordination**: Uses promises to coordinate when state changes are committed 656 | 4. **Transition-Based Updates**: Uses React's `startTransition` for non-urgent updates 657 | 658 | ### Execution Flow 659 | 660 | ```typescript 661 | // Simplified execution pattern from Container.tsx 662 | const executeHandler = async (statements: Statement[], options: ExecutionOptions) => { 663 | for (const statement of statements) { 664 | // Execute statement 665 | await processStatement(statement, evalContext); 666 | 667 | // Collect state changes 668 | if (changes.length > 0) { 669 | // Dispatch state changes 670 | changes.forEach(change => { 671 | statePartChanged(change.pathArray, change.newValue, change.target, change.action); 672 | }); 673 | 674 | // Create promise for this statement 675 | const statementPromise = new Promise(resolve => { 676 | statementPromiseResolvers.current.set(statementId, resolve); 677 | }); 678 | 679 | // Increment version to trigger re-render 680 | startTransition(() => { 681 | setVersion(prev => prev + 1); 682 | }); 683 | 684 | // Wait for state to be committed 685 | await statementPromise; 686 | } 687 | } 688 | }; 689 | ``` 690 | 691 | ### Version-Based Synchronization 692 | 693 | The Container uses a version counter to coordinate state updates: 694 | 695 | ```typescript 696 | // When version changes, resolve pending promises 697 | useEffect(() => { 698 | // Resolve all pending statement promises 699 | statementPromiseResolvers.current.forEach(resolve => resolve()); 700 | statementPromiseResolvers.current.clear(); 701 | }, [version]); 702 | ``` 703 | 704 | ## State Scoping and Inheritance 705 | 706 | ### Parent State Extraction 707 | 708 | Child containers can limit which parent state they receive: 709 | 710 | ```typescript 711 | // From StateContainer.tsx 712 | function extractScopedState(parentState: ContainerState, uses?: string[]): ContainerState | undefined { 713 | if (!uses) { 714 | return parentState; // Inherit all parent state 715 | } 716 | if (uses.length === 0) { 717 | return EMPTY_OBJECT; // Inherit no parent state 718 | } 719 | return pick(parentState, uses); // Inherit only specified keys 720 | } 721 | ``` 722 | 723 | ### Context Variables 724 | 725 | Containers can provide context variables to their children: 726 | 727 | ```typescript 728 | // contextVars flow down to child containers automatically 729 | const localVarsStateContext = useCombinedState( 730 | stateFromOutside, 731 | componentStateWithApis, 732 | node.contextVars // These become available to children 733 | ); 734 | ``` 735 | 736 | ### State Merging Strategies 737 | 738 | Different merging strategies are used for different state layers: 739 | 740 | 1. **Override**: Higher-priority state overrides lower-priority (`useCombinedState`) 741 | 2. **Merge**: Object and array values are deep-merged (`useMergedState`) 742 | 3. **Replace**: Simple values replace entirely 743 | 744 | ## Routing Integration 745 | 746 | ### Routing Parameters as State 747 | 748 | Routing information is automatically injected into container state: 749 | 750 | ```typescript 751 | // From StateContainer.tsx - useRoutingParams 752 | const routingParams = useMemo(() => { 753 | return { 754 | $pathname: location.pathname, 755 | $routeParams: routeParams, // URL parameters like :id 756 | $queryParams: queryParamsMap, // Query string parameters 757 | $linkInfo: linkInfo, // Navigation context 758 | }; 759 | }, [linkInfo, location.pathname, queryParamsMap, routeParams]); 760 | ``` 761 | 762 | These are automatically available in all containers as context variables. 763 | 764 | ## Error Boundaries and Resilience 765 | 766 | ### Layered Error Protection 767 | 768 | The container system has multiple error boundaries: 769 | 770 | 1. **Container-Level**: Protects StateContainer from Container errors 771 | 2. **StateContainer-Level**: Protects parent containers from child container errors 772 | 3. **Component-Level**: Individual component errors don't crash containers 773 | 774 | ### State Preservation 775 | 776 | If a Container crashes during event handler execution, the StateContainer preserves its state, allowing recovery or graceful degradation. 777 | 778 | ## Performance Optimizations 779 | 780 | ### Memoization Strategies 781 | 782 | 1. **Component Memoization**: Container components are memoized to prevent unnecessary re-renders 783 | 2. **Variable Memoization**: Variable resolution results are cached based on dependencies 784 | 3. **Shallow Comparison**: State objects use shallow comparison for change detection 785 | 4. **Reference Tracking**: Proxy objects maintain stable references for nested access 786 | 787 | ### Change Detection Optimizations 788 | 789 | 1. **Path-Based Updates**: Only affected parts of state tree are updated 790 | 2. **Dependency Tracking**: Variables are only re-evaluated when their dependencies change 791 | 3. **Batch Updates**: Multiple changes in single instruction are batched together 792 | 4. **Transition Updates**: Non-urgent updates use React transitions for better performance 793 | 794 | ## Debugging Support 795 | 796 | ### State Transition Logging 797 | 798 | The container reducer can log state transitions for debugging: 799 | 800 | ```typescript 801 | // From reducer.ts 802 | export function createContainerReducer(debugView: IDebugViewContext) { 803 | const allowLogging = debugView.collectStateTransitions; 804 | // ... logging implementation 805 | } 806 | ``` 807 | 808 | ### Component Keys and Resolution 809 | 810 | Components receive resolved keys for debugging and tracking: 811 | 812 | ```typescript 813 | // From renderChild.tsx 814 | const key = extractParam(state, node.uid, appContext, true); 815 | return ( 816 | <ComponentWrapper 817 | key={key} 818 | resolvedKey={key} // For debugging container parent chains 819 | // ... 820 | /> 821 | ); 822 | ``` 823 | 824 | ## Integration Points 825 | 826 | ### DataSource Integration 827 | 828 | DataSource components integrate with the container system through loader actions: 829 | 830 | ```typescript 831 | // Loader state management 832 | dispatch({ 833 | type: ContainerActionKind.LOADER_LOADED, 834 | payload: { 835 | uid: dataSourceUid, 836 | data: fetchedData, 837 | pageInfo: paginationInfo 838 | } 839 | }); 840 | ``` 841 | 842 | ### Form Integration 843 | 844 | Form components manage their state through the container system while maintaining their own internal state for performance. 845 | 846 | ### API Integration 847 | 848 | Component APIs are registered with containers and become part of the state, making them accessible to other components via component IDs. 849 | 850 | ## Key Design Principles 851 | 852 | 1. **Hierarchical Encapsulation**: State is scoped to container boundaries while allowing controlled inheritance 853 | 2. **Reactive Consistency**: All state changes automatically propagate to dependent UI elements 854 | 3. **Async Coordination**: Long-running operations don't block the UI thread 855 | 4. **Immutable Updates**: All state changes produce new immutable state objects 856 | 5. **Proxy Transparency**: State mutations are detected transparently without special syntax 857 | 6. **Error Isolation**: Component errors are contained and don't propagate to affect other parts of the application 858 | 859 | This container-based architecture provides XMLUI with a robust, scalable state management solution that maintains the declarative programming model while handling complex state interactions and ensuring optimal performance characteristics. ``` -------------------------------------------------------------------------------- /xmlui/src/parsers/scripting/Lexer.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { parseRegExpLiteral } from "@eslint-community/regexpp"; 2 | 3 | import type { GenericToken } from "../common/GenericToken"; 4 | import type { InputStream } from "../common/InputStream"; 5 | import { TokenType } from "./TokenType"; 6 | 7 | type Token = GenericToken<TokenType>; 8 | 9 | /** 10 | * This enum indicates the current lexer phase 11 | */ 12 | enum LexerPhase { 13 | // Start getting a token 14 | Start = 0, 15 | 16 | // Collecting whitespace 17 | InWhiteSpace, 18 | 19 | // Comment phases 20 | InlineCommentTrail, 21 | BlockCommentTrail1, 22 | BlockCommentTrail2, 23 | 24 | // Multi-char tokens 25 | Slash, 26 | Dollar, 27 | Or, 28 | Asterisk, 29 | Ampersand, 30 | Equal, 31 | DoubleEqual, 32 | NotEqual, 33 | Exclamation, 34 | AngleLeft, 35 | AngleRight, 36 | SignedShiftRight, 37 | IdTail, 38 | Dot, 39 | DotDot, 40 | Colon, 41 | Zero, 42 | QuestionMark, 43 | HexaFirst, 44 | HexaTail, 45 | BinaryFirst, 46 | BinaryTail, 47 | DecimalOrReal, 48 | RealFractionalFirst, 49 | RealFractionalTail, 50 | RealExponent, 51 | RealExponentSign, 52 | RealExponentTail, 53 | StringTemplateLiteralBackSlash, 54 | StringTemplateLiteral, 55 | String, 56 | StringBackSlash, 57 | StringHexa1, 58 | StringHexa2, 59 | StringUHexa1, 60 | StringUHexa2, 61 | StringUHexa3, 62 | StringUHexa4, 63 | StringUcp1, 64 | StringUcp2, 65 | StringUcp3, 66 | StringUcp4, 67 | StringUcp5, 68 | StringUcp6, 69 | StringUcpTail, 70 | 71 | // --- Assignments 72 | Exponent, 73 | Plus, 74 | Minus, 75 | Divide, 76 | Remainder, 77 | ShiftLeft, 78 | ShiftRight, 79 | LogicalAnd, 80 | BitwiseXor, 81 | LogicalOr, 82 | NullCoalesce, 83 | 84 | // --- Other 85 | RegEx, 86 | } 87 | 88 | /** 89 | * This class implements the lexer of binding expressions 90 | */ 91 | export class Lexer { 92 | // --- Already fetched tokens 93 | private _ahead: Token[] = []; 94 | 95 | // --- Prefetched character (from the next token) 96 | private _prefetched: string | null = null; 97 | 98 | // --- Prefetched character position (from the next token) 99 | private _prefetchedPos: number | null = null; 100 | 101 | // --- Prefetched character column (from the next token) 102 | private _prefetchedColumn: number | null = null; 103 | 104 | // --- input position at the beginning of last fetch 105 | private _lastFetchPosition = 0; 106 | 107 | private _phaseExternallySet: null | LexerPhase = null; 108 | /** 109 | * Initializes the tokenizer with the input stream 110 | * @param input Input source code stream 111 | */ 112 | constructor(public readonly input: InputStream) {} 113 | 114 | /** 115 | * Fetches the next token without advancing to its position 116 | * @param ws If true, retrieve whitespaces too 117 | */ 118 | peek(ws = false): Token { 119 | return this.ahead(0, ws); 120 | } 121 | 122 | /** 123 | * Reads tokens ahead 124 | * @param n Number of token positions to read ahead 125 | * @param ws If true, retrieve whitespaces too 126 | */ 127 | ahead(n = 1, ws = false): Token { 128 | if (n > 16) { 129 | throw new Error("Cannot look ahead more than 16 tokens"); 130 | } 131 | 132 | // --- Prefetch missing tokens 133 | while (this._ahead.length <= n) { 134 | const token = this.fetch(); 135 | if (isEof(token)) { 136 | return token; 137 | } 138 | if (ws || (!ws && !isWs(token))) { 139 | this._ahead.push(token); 140 | } 141 | } 142 | return this._ahead[n]; 143 | } 144 | 145 | /** 146 | * Fetches the next token and advances the stream position 147 | * @param ws If true, retrieve whitespaces too 148 | */ 149 | get(ws = false): Token { 150 | if (this._ahead.length > 0) { 151 | const token = this._ahead.shift(); 152 | if (!token) { 153 | throw new Error("Token expected"); 154 | } 155 | return token; 156 | } 157 | while (true) { 158 | const token = this.fetch(); 159 | if (isEof(token) || ws || (!ws && !isWs(token))) { 160 | return token; 161 | } 162 | } 163 | } 164 | 165 | /** 166 | * Gets a RegEx from the current position 167 | */ 168 | getRegEx(): RegExpLexerResult { 169 | return this.fetchRegEx(); 170 | } 171 | 172 | /** 173 | * Gets the remaining characters after the parsing phase 174 | */ 175 | getTail(): string { 176 | return this._ahead.length > 0 177 | ? this.input.getTail(this._ahead[0].startPosition) 178 | : this.input.getTail(this._lastFetchPosition); 179 | } 180 | 181 | /** Parsing template literals requires a context sensitive lexer. 182 | * This method has to be called by the parser when the lexer needs to scan a string inside a template literal. 183 | * Call this after the first opening backing and after the parser is done with parsing a placeholder, after the right brace. 184 | */ 185 | setStartingPhaseToTemplateLiteral() { 186 | this._phaseExternallySet = LexerPhase.StringTemplateLiteral; 187 | } 188 | 189 | /** 190 | * Fetches the next character from the input stream 191 | */ 192 | private fetchNextChar(): string | null { 193 | if (!this._prefetched) { 194 | this._prefetchedPos = this.input.position; 195 | this._prefetchedColumn = this.input.column; 196 | this._prefetched = this.input.get(); 197 | } 198 | return this._prefetched; 199 | } 200 | 201 | /** 202 | * Fetches the next token from the input stream 203 | */ 204 | private fetch(): Token { 205 | // --- Captured constants used in nested functions 206 | const input = this.input; 207 | const startPos = this._prefetchedPos || input.position; 208 | const line = input.line; 209 | const startColumn = this._prefetchedColumn || input.column; 210 | this._lastFetchPosition = this.input.position; 211 | 212 | // --- State variables 213 | let stringState: string | null = null; 214 | let text = ""; 215 | let tokenType = TokenType.Eof; 216 | let lastEndPos = input.position; 217 | let lastEndColumn = input.column; 218 | let ch: string | null = null; 219 | let useResolver = false; 220 | 221 | /** 222 | * Appends the last character to the token, and manages positions 223 | */ 224 | const appendTokenChar = (): void => { 225 | text += ch; 226 | this._prefetched = null; 227 | this._prefetchedPos = null; 228 | this._prefetchedColumn = null; 229 | lastEndPos = input.position; 230 | lastEndColumn = input.position; 231 | }; 232 | 233 | // --- Start from the beginning 234 | let phase: LexerPhase = this.getStartingPhaseThenReset(); 235 | 236 | // --- Process all token characters 237 | while (true) { 238 | // --- Get the next character 239 | ch = this.fetchNextChar(); 240 | 241 | // --- In case of EOF, return the current token data 242 | if (ch === null) { 243 | return makeToken(); 244 | } 245 | 246 | // --- Set the initial token type to unknown for the other characters 247 | if (tokenType === TokenType.Eof) { 248 | tokenType = TokenType.Unknown; 249 | } 250 | 251 | // --- Follow the lexer state machine 252 | phaseSwitch: switch (phase) { 253 | // ==================================================================== 254 | // Process the first character 255 | case LexerPhase.Start: 256 | switch (ch) { 257 | // --- Go on with whitespaces 258 | case " ": 259 | case "\t": 260 | case "\n": 261 | case "\r": 262 | phase = LexerPhase.InWhiteSpace; 263 | tokenType = TokenType.Ws; 264 | break; 265 | 266 | // --- Divide, BlockComment, or EolComment 267 | case "/": 268 | phase = LexerPhase.Slash; 269 | tokenType = TokenType.Divide; 270 | break; 271 | 272 | case "$": 273 | phase = LexerPhase.Dollar; 274 | tokenType = TokenType.Identifier; 275 | break; 276 | 277 | case "*": 278 | phase = LexerPhase.Asterisk; 279 | tokenType = TokenType.Multiply; 280 | break; 281 | 282 | case "%": 283 | phase = LexerPhase.Remainder; 284 | tokenType = TokenType.Remainder; 285 | break; 286 | 287 | case "+": 288 | phase = LexerPhase.Plus; 289 | tokenType = TokenType.Plus; 290 | break; 291 | 292 | case "-": 293 | phase = LexerPhase.Minus; 294 | tokenType = TokenType.Minus; 295 | break; 296 | 297 | case "^": 298 | phase = LexerPhase.BitwiseXor; 299 | tokenType = TokenType.BitwiseXor; 300 | break; 301 | 302 | // --- BitwiseOr, LogicalOr 303 | case "|": 304 | phase = LexerPhase.Or; 305 | tokenType = TokenType.BitwiseOr; 306 | break; 307 | 308 | // --- BitwiseAnd, LogicalAnd 309 | case "&": 310 | phase = LexerPhase.Ampersand; 311 | tokenType = TokenType.BitwiseAnd; 312 | break; 313 | 314 | // --- "?", "?", or "?." 315 | case "?": 316 | phase = LexerPhase.QuestionMark; 317 | tokenType = TokenType.QuestionMark; 318 | break; 319 | 320 | case ";": 321 | return completeToken(TokenType.Semicolon); 322 | 323 | case ",": 324 | return completeToken(TokenType.Comma); 325 | 326 | case "(": 327 | return completeToken(TokenType.LParent); 328 | 329 | case ")": 330 | return completeToken(TokenType.RParent); 331 | 332 | // --- ":" or "::" 333 | case ":": 334 | phase = LexerPhase.Colon; 335 | tokenType = TokenType.Colon; 336 | break; 337 | 338 | case "`": 339 | return completeToken(TokenType.Backtick); 340 | 341 | case "[": 342 | return completeToken(TokenType.LSquare); 343 | 344 | case "]": 345 | return completeToken(TokenType.RSquare); 346 | 347 | case "~": 348 | return completeToken(TokenType.BinaryNot); 349 | 350 | case "{": 351 | return completeToken(TokenType.LBrace); 352 | 353 | case "}": 354 | return completeToken(TokenType.RBrace); 355 | 356 | // --- "=","==", "===", or "=>" 357 | case "=": 358 | phase = LexerPhase.Equal; 359 | tokenType = TokenType.Assignment; 360 | break; 361 | 362 | // --- "!", "!=", or "!==" 363 | case "!": 364 | phase = LexerPhase.Exclamation; 365 | tokenType = TokenType.LogicalNot; 366 | break; 367 | 368 | // --- "<", "<=", or "<<" 369 | case "<": 370 | phase = LexerPhase.AngleLeft; 371 | tokenType = TokenType.LessThan; 372 | break; 373 | 374 | // --- ">", ">=", ">>", or ">>>" 375 | case ">": 376 | phase = LexerPhase.AngleRight; 377 | tokenType = TokenType.GreaterThan; 378 | break; 379 | 380 | // --- Decimal or Real literal 381 | case "0": 382 | phase = LexerPhase.Zero; 383 | tokenType = TokenType.DecimalLiteral; 384 | break; 385 | 386 | case ".": 387 | phase = LexerPhase.Dot; 388 | tokenType = TokenType.Dot; 389 | break; 390 | 391 | // --- String (both " and ' are accepted as string wrappers 392 | case '"': 393 | case "'": 394 | stringState = ch; 395 | phase = LexerPhase.String; 396 | break; 397 | 398 | default: 399 | if (isIdStart(ch)) { 400 | useResolver = true; 401 | phase = LexerPhase.IdTail; 402 | tokenType = TokenType.Identifier; 403 | } else if (isDecimalDigit(ch)) { 404 | phase = LexerPhase.DecimalOrReal; 405 | tokenType = TokenType.DecimalLiteral; 406 | } else { 407 | completeToken(TokenType.Unknown); 408 | } 409 | break; 410 | } 411 | break; 412 | 413 | // ==================================================================== 414 | // Process comments 415 | 416 | // --- Looking for the end of whitespace 417 | case LexerPhase.InWhiteSpace: 418 | if (ch !== " " && ch !== "\t" && ch !== "\r" && ch !== "\n") { 419 | return makeToken(); 420 | } 421 | break; 422 | 423 | // --- Wait for an "*" that may complete a block comment 424 | case LexerPhase.BlockCommentTrail1: 425 | if (ch === "*") { 426 | phase = LexerPhase.BlockCommentTrail2; 427 | } 428 | break; 429 | 430 | // --- Wait for a "/" that may complete a block comment 431 | case LexerPhase.BlockCommentTrail2: 432 | if (ch === "/") { 433 | return completeToken(TokenType.BlockComment); 434 | } 435 | break; 436 | 437 | case LexerPhase.InlineCommentTrail: 438 | if (ch === "\n") { 439 | return completeToken(); 440 | } 441 | break; 442 | 443 | // ==================================================================== 444 | // Process identifiers 445 | 446 | case LexerPhase.IdTail: 447 | if (!isIdContinuation(ch)) { 448 | return makeToken(); 449 | } 450 | break; 451 | 452 | // ==================================================================== 453 | // Process miscellaneous tokens 454 | 455 | case LexerPhase.Colon: 456 | return ch === ":" ? completeToken(TokenType.Global) : makeToken(); 457 | 458 | case LexerPhase.Slash: 459 | if (ch === "*") { 460 | phase = LexerPhase.BlockCommentTrail1; 461 | } else if (ch === "/") { 462 | phase = LexerPhase.InlineCommentTrail; 463 | tokenType = TokenType.EolComment; 464 | } else if (ch === "=") { 465 | return completeToken(TokenType.DivideAssignment); 466 | } else { 467 | return makeToken(); 468 | } 469 | break; 470 | 471 | case LexerPhase.Plus: 472 | if (ch === "+") { 473 | return completeToken(TokenType.IncOp); 474 | } 475 | return ch === "=" ? completeToken(TokenType.AddAssignment) : makeToken(); 476 | 477 | case LexerPhase.Minus: 478 | if (ch === "-") { 479 | return completeToken(TokenType.DecOp); 480 | } 481 | return ch === "=" ? completeToken(TokenType.SubtractAssignment) : makeToken(); 482 | 483 | case LexerPhase.Remainder: 484 | return ch === "=" ? completeToken(TokenType.RemainderAssignment) : makeToken(); 485 | 486 | case LexerPhase.BitwiseXor: 487 | return ch === "=" ? completeToken(TokenType.BitwiseXorAssignment) : makeToken(); 488 | 489 | case LexerPhase.Or: 490 | if (ch === "=") { 491 | return completeToken(TokenType.BitwiseOrAssignment); 492 | } 493 | if (ch === "|") { 494 | phase = LexerPhase.LogicalOr; 495 | tokenType = TokenType.LogicalOr; 496 | break; 497 | } 498 | return makeToken(); 499 | 500 | case LexerPhase.LogicalOr: 501 | return ch === "=" ? completeToken(TokenType.LogicalOrAssignment) : makeToken(); 502 | 503 | case LexerPhase.Ampersand: 504 | if (ch === "=") { 505 | return completeToken(TokenType.BitwiseAndAssignment); 506 | } 507 | if (ch === "&") { 508 | phase = LexerPhase.LogicalAnd; 509 | tokenType = TokenType.LogicalAnd; 510 | break; 511 | } 512 | return makeToken(); 513 | 514 | case LexerPhase.LogicalAnd: 515 | return ch === "=" ? completeToken(TokenType.LogicalAndAssignment) : makeToken(); 516 | 517 | case LexerPhase.Asterisk: 518 | if (ch === "*") { 519 | phase = LexerPhase.Exponent; 520 | tokenType = TokenType.Exponent; 521 | break; 522 | } else if (ch === "=") { 523 | return completeToken(TokenType.MultiplyAssignment); 524 | } 525 | return makeToken(); 526 | 527 | case LexerPhase.Exponent: 528 | return ch === "=" ? completeToken(TokenType.ExponentAssignment) : makeToken(); 529 | 530 | case LexerPhase.QuestionMark: 531 | if (ch === "?") { 532 | phase = LexerPhase.NullCoalesce; 533 | tokenType = TokenType.NullCoalesce; 534 | break; 535 | } 536 | if (ch === ".") { 537 | return completeToken(TokenType.OptionalChaining); 538 | } 539 | return makeToken(); 540 | 541 | case LexerPhase.NullCoalesce: 542 | return ch === "=" ? completeToken(TokenType.NullCoalesceAssignment) : makeToken(); 543 | 544 | case LexerPhase.Equal: 545 | if (ch === ">") { 546 | return completeToken(TokenType.Arrow); 547 | } 548 | if (ch === "=") { 549 | phase = LexerPhase.DoubleEqual; 550 | tokenType = TokenType.Equal; 551 | break; 552 | } 553 | return makeToken(); 554 | 555 | case LexerPhase.DoubleEqual: 556 | return ch === "=" ? completeToken(TokenType.StrictEqual) : makeToken(); 557 | 558 | case LexerPhase.Exclamation: 559 | if (ch === "=") { 560 | phase = LexerPhase.NotEqual; 561 | tokenType = TokenType.NotEqual; 562 | break; 563 | } 564 | return makeToken(); 565 | 566 | case LexerPhase.NotEqual: 567 | return ch === "=" ? completeToken(TokenType.StrictNotEqual) : makeToken(); 568 | 569 | case LexerPhase.AngleLeft: 570 | if (ch === "=") { 571 | return completeToken(TokenType.LessThanOrEqual); 572 | } 573 | if (ch === "<") { 574 | phase = LexerPhase.ShiftLeft; 575 | tokenType = TokenType.ShiftLeft; 576 | break; 577 | } 578 | return makeToken(); 579 | 580 | case LexerPhase.ShiftLeft: 581 | return ch === "=" ? completeToken(TokenType.ShiftLeftAssignment) : makeToken(); 582 | 583 | case LexerPhase.AngleRight: 584 | if (ch === "=") { 585 | return completeToken(TokenType.GreaterThanOrEqual); 586 | } 587 | if (ch === ">") { 588 | phase = LexerPhase.SignedShiftRight; 589 | tokenType = TokenType.SignedShiftRight; 590 | break; 591 | } 592 | return makeToken(); 593 | 594 | case LexerPhase.SignedShiftRight: 595 | if (ch === ">") { 596 | phase = LexerPhase.ShiftRight; 597 | tokenType = TokenType.ShiftRight; 598 | break; 599 | } 600 | if (ch === "=") { 601 | return completeToken(TokenType.SignedShiftRightAssignment); 602 | } 603 | return makeToken(); 604 | 605 | case LexerPhase.ShiftRight: 606 | return ch === "=" ? completeToken(TokenType.ShiftRightAssignment) : makeToken(); 607 | 608 | // ==================================================================== 609 | // --- Literals 610 | 611 | case LexerPhase.Zero: 612 | if (ch === "x") { 613 | phase = LexerPhase.HexaFirst; 614 | tokenType = TokenType.Unknown; 615 | } else if (ch === "b") { 616 | phase = LexerPhase.BinaryFirst; 617 | tokenType = TokenType.Unknown; 618 | } else if (isDecimalDigit(ch) || ch === "_") { 619 | phase = LexerPhase.DecimalOrReal; 620 | } else if (ch === ".") { 621 | phase = LexerPhase.RealFractionalFirst; 622 | tokenType = TokenType.Unknown; 623 | } else if (ch === "e" || ch === "E") { 624 | phase = LexerPhase.RealExponent; 625 | tokenType = TokenType.Unknown; 626 | } else { 627 | return makeToken(); 628 | } 629 | break; 630 | 631 | case LexerPhase.Dot: 632 | if (ch === ".") { 633 | phase = LexerPhase.DotDot; 634 | tokenType = TokenType.Unknown; 635 | break; 636 | } 637 | if (!isDecimalDigit(ch)) { 638 | return makeToken(); 639 | } 640 | phase = LexerPhase.RealFractionalTail; 641 | tokenType = TokenType.RealLiteral; 642 | break; 643 | 644 | case LexerPhase.DotDot: 645 | return ch === "." ? completeToken(TokenType.Spread) : makeToken(); 646 | 647 | case LexerPhase.HexaFirst: 648 | if (ch === "_") { 649 | break; 650 | } 651 | if (!isHexadecimalDigit(ch)) { 652 | return makeToken(); 653 | } 654 | phase = LexerPhase.HexaTail; 655 | tokenType = TokenType.HexadecimalLiteral; 656 | break; 657 | 658 | case LexerPhase.HexaTail: 659 | if (!isHexadecimalDigit(ch) && ch !== "_") { 660 | return makeToken(); 661 | } 662 | break; 663 | 664 | case LexerPhase.BinaryFirst: 665 | if (ch === "_") { 666 | break; 667 | } 668 | if (!isBinaryDigit(ch)) { 669 | return makeToken(); 670 | } 671 | phase = LexerPhase.BinaryTail; 672 | tokenType = TokenType.BinaryLiteral; 673 | break; 674 | 675 | case LexerPhase.BinaryTail: 676 | if (!isBinaryDigit(ch) && ch !== "_") { 677 | return makeToken(); 678 | } 679 | tokenType = TokenType.BinaryLiteral; 680 | break; 681 | 682 | case LexerPhase.DecimalOrReal: 683 | if (isDecimalDigit(ch) || ch === "_") { 684 | break; 685 | } else if ( 686 | ch === "." && 687 | (this.input.peek() === null || isDecimalDigit(this.input.peek()!)) 688 | ) { 689 | phase = LexerPhase.RealFractionalFirst; 690 | tokenType = TokenType.Unknown; 691 | } else if (ch === "e" || ch === "E") { 692 | phase = LexerPhase.RealExponent; 693 | tokenType = TokenType.Unknown; 694 | } else { 695 | return makeToken(); 696 | } 697 | break; 698 | 699 | case LexerPhase.RealFractionalFirst: 700 | if (isDecimalDigit(ch)) { 701 | phase = LexerPhase.RealFractionalTail; 702 | tokenType = TokenType.RealLiteral; 703 | } else if (ch === "e" || ch === "E") { 704 | phase = LexerPhase.RealExponent; 705 | } else { 706 | return makeToken(); 707 | } 708 | break; 709 | 710 | case LexerPhase.RealFractionalTail: 711 | if (ch === "e" || ch === "E") { 712 | phase = LexerPhase.RealExponent; 713 | tokenType = TokenType.Unknown; 714 | } else if (!isDecimalDigit(ch) && ch !== "_") { 715 | return makeToken(); 716 | } 717 | break; 718 | 719 | case LexerPhase.RealExponent: 720 | if (ch === "+" || ch === "-") { 721 | phase = LexerPhase.RealExponentSign; 722 | } else if (isDecimalDigit(ch)) { 723 | phase = LexerPhase.RealExponentTail; 724 | tokenType = TokenType.RealLiteral; 725 | } else { 726 | return makeToken(); 727 | } 728 | break; 729 | 730 | case LexerPhase.RealExponentSign: 731 | if (isDecimalDigit(ch)) { 732 | phase = LexerPhase.RealExponentTail; 733 | tokenType = TokenType.RealLiteral; 734 | } else { 735 | return makeToken(); 736 | } 737 | break; 738 | 739 | case LexerPhase.RealExponentTail: 740 | if (!isDecimalDigit(ch)) { 741 | return makeToken(); 742 | } 743 | break; 744 | 745 | // A dollar sign is also a valid variable name. When it isn't a dollar left brace, we continue as if it was an identifier 746 | case LexerPhase.Dollar: 747 | if (ch === "{") { 748 | return completeToken(TokenType.DollarLBrace); 749 | } 750 | phase = LexerPhase.IdTail; 751 | useResolver = true; 752 | tokenType = TokenType.Identifier; 753 | if (!isIdContinuation(ch)) { 754 | makeToken(); 755 | } 756 | break; 757 | 758 | case LexerPhase.StringTemplateLiteralBackSlash: { 759 | phase = LexerPhase.StringTemplateLiteral; 760 | const charAhead1 = this.input.ahead(0); 761 | const charAhead2 = this.input.ahead(1); 762 | 763 | if (charAhead1 === "`" || (charAhead1 === "$" && charAhead2 === "{")) { 764 | return completeToken(TokenType.StringLiteral); 765 | } 766 | break; 767 | } 768 | 769 | case LexerPhase.StringTemplateLiteral: 770 | switch (ch) { 771 | case "\\": 772 | phase = LexerPhase.StringTemplateLiteralBackSlash; 773 | tokenType = TokenType.Unknown; 774 | break phaseSwitch; 775 | case "`": 776 | return completeToken(TokenType.Backtick); 777 | case "$": 778 | const charAhead = this.input.ahead(0); 779 | if (charAhead === "{") { 780 | appendTokenChar(); 781 | this.fetchNextChar(); 782 | return completeToken(TokenType.DollarLBrace); 783 | } 784 | } 785 | const charAhead1 = this.input.ahead(0); 786 | const charAhead2 = this.input.ahead(1); 787 | 788 | if (charAhead1 === "`" || (charAhead1 === "$" && charAhead2 === "{")) { 789 | return completeToken(TokenType.StringLiteral); 790 | } 791 | break; 792 | 793 | case LexerPhase.String: 794 | if (ch === stringState) { 795 | return completeToken(TokenType.StringLiteral); 796 | } else if (isRestrictedInString(ch)) { 797 | return completeToken(TokenType.Unknown); 798 | } else if (ch === "\\") { 799 | phase = LexerPhase.StringBackSlash; 800 | tokenType = TokenType.Unknown; 801 | } 802 | break; 803 | 804 | // Start of string character escape 805 | case LexerPhase.StringBackSlash: 806 | switch (ch) { 807 | case "b": 808 | case "f": 809 | case "n": 810 | case "r": 811 | case "t": 812 | case "v": 813 | case "S": 814 | case "0": 815 | case "'": 816 | case '"': 817 | case "`": 818 | case "\\": 819 | phase = LexerPhase.String; 820 | break; 821 | case "x": 822 | phase = LexerPhase.StringHexa1; 823 | break; 824 | case "u": 825 | phase = LexerPhase.StringUHexa1; 826 | break; 827 | default: 828 | phase = LexerPhase.String; 829 | break; 830 | } 831 | break; 832 | 833 | // --- First hexadecimal digit of string character escape 834 | case LexerPhase.StringHexa1: 835 | if (isHexadecimalDigit(ch)) { 836 | phase = LexerPhase.StringHexa2; 837 | } else { 838 | return completeToken(TokenType.Unknown); 839 | } 840 | break; 841 | 842 | // --- Second hexadecimal digit of character escape 843 | case LexerPhase.StringHexa2: 844 | if (isHexadecimalDigit(ch)) { 845 | phase = LexerPhase.String; 846 | } else { 847 | return completeToken(TokenType.Unknown); 848 | } 849 | break; 850 | 851 | // --- First hexadecimal digit of Unicode string character escape 852 | case LexerPhase.StringUHexa1: 853 | if (ch === "{") { 854 | phase = LexerPhase.StringUcp1; 855 | break; 856 | } 857 | if (isHexadecimalDigit(ch)) { 858 | phase = LexerPhase.StringUHexa2; 859 | } else { 860 | return completeToken(TokenType.Unknown); 861 | } 862 | break; 863 | 864 | // --- Second hexadecimal digit of Unicode string character escape 865 | case LexerPhase.StringUHexa2: 866 | if (isHexadecimalDigit(ch)) { 867 | phase = LexerPhase.StringUHexa3; 868 | } else { 869 | return completeToken(TokenType.Unknown); 870 | } 871 | break; 872 | 873 | // --- Third hexadecimal digit of Unicode string character escape 874 | case LexerPhase.StringUHexa3: 875 | if (isHexadecimalDigit(ch)) { 876 | phase = LexerPhase.StringUHexa4; 877 | } else { 878 | return completeToken(TokenType.Unknown); 879 | } 880 | break; 881 | 882 | // --- Fourth hexadecimal digit of Unicode string character escape 883 | case LexerPhase.StringUHexa4: 884 | if (isHexadecimalDigit(ch)) { 885 | phase = LexerPhase.String; 886 | } else { 887 | return completeToken(TokenType.Unknown); 888 | } 889 | break; 890 | 891 | // --- First hexadecimal digit of Unicode codepoint string character escape 892 | case LexerPhase.StringUcp1: 893 | if (isHexadecimalDigit(ch)) { 894 | phase = LexerPhase.StringUcp2; 895 | } else { 896 | return completeToken(TokenType.Unknown); 897 | } 898 | break; 899 | 900 | // --- Second hexadecimal digit of Unicode codepoint string character escape 901 | case LexerPhase.StringUcp2: 902 | if (ch === "}") { 903 | phase = LexerPhase.String; 904 | } else if (isHexadecimalDigit(ch)) { 905 | phase = LexerPhase.StringUcp3; 906 | } else { 907 | return completeToken(TokenType.Unknown); 908 | } 909 | break; 910 | 911 | // --- Third hexadecimal digit of Unicode codepoint string character escape 912 | case LexerPhase.StringUcp3: 913 | if (ch === "}") { 914 | phase = LexerPhase.String; 915 | } else if (isHexadecimalDigit(ch)) { 916 | phase = LexerPhase.StringUcp4; 917 | } else { 918 | return completeToken(TokenType.Unknown); 919 | } 920 | break; 921 | 922 | // --- Fourth hexadecimal digit of Unicode codepoint string character escape 923 | case LexerPhase.StringUcp4: 924 | if (ch === "}") { 925 | phase = LexerPhase.String; 926 | } else if (isHexadecimalDigit(ch)) { 927 | phase = LexerPhase.StringUcp5; 928 | } else { 929 | return completeToken(TokenType.Unknown); 930 | } 931 | break; 932 | 933 | // --- Fifth hexadecimal digit of Unicode codepoint string character escape 934 | case LexerPhase.StringUcp5: 935 | if (ch === "}") { 936 | phase = LexerPhase.String; 937 | } else if (isHexadecimalDigit(ch)) { 938 | phase = LexerPhase.StringUcp6; 939 | } else { 940 | return completeToken(TokenType.Unknown); 941 | } 942 | break; 943 | 944 | // --- Sixth hexadecimal digit of Unicode codepoint string character escape 945 | case LexerPhase.StringUcp6: 946 | if (ch === "}") { 947 | phase = LexerPhase.String; 948 | } else if (isHexadecimalDigit(ch)) { 949 | phase = LexerPhase.StringUcpTail; 950 | } else { 951 | return completeToken(TokenType.Unknown); 952 | } 953 | break; 954 | 955 | // --- Closing bracket of Unicode codepoint string character escape 956 | case LexerPhase.StringUcpTail: 957 | if (ch === "}") { 958 | phase = LexerPhase.String; 959 | } else { 960 | return completeToken(TokenType.Unknown); 961 | } 962 | break; 963 | 964 | // ==================================================================== 965 | // --- We cannot continue 966 | default: 967 | return makeToken(); 968 | } 969 | 970 | // --- Append the char to the current text 971 | appendTokenChar(); 972 | 973 | // --- Go on with parsing the next character 974 | } 975 | 976 | /** 977 | * Packs the specified type of token to send back 978 | */ 979 | function makeToken(): Token { 980 | if (useResolver) { 981 | tokenType = 982 | resolverHash.get(text) ?? 983 | (isIdStart(text[0]) && text[text.length - 1] !== "'" 984 | ? TokenType.Identifier 985 | : TokenType.Unknown); 986 | } 987 | return { 988 | text, 989 | type: tokenType, 990 | startPosition: startPos, 991 | endPosition: lastEndPos, 992 | startLine: line, 993 | endLine: line, 994 | startColumn, 995 | endColumn: lastEndColumn, 996 | }; 997 | } 998 | 999 | /** 1000 | * Add the last character to the token and return it 1001 | */ 1002 | function completeToken(suggestedType?: TokenType): Token { 1003 | appendTokenChar(); 1004 | 1005 | // --- Send back the token 1006 | if (suggestedType !== undefined) { 1007 | tokenType = suggestedType; 1008 | } 1009 | return makeToken(); 1010 | } 1011 | } 1012 | 1013 | getStartingPhaseThenReset(): LexerPhase { 1014 | if (this._phaseExternallySet !== null) { 1015 | const phase = this._phaseExternallySet; 1016 | this._phaseExternallySet = null; 1017 | return phase; 1018 | } 1019 | return LexerPhase.Start; 1020 | } 1021 | 1022 | /** 1023 | * Fetches the next RegEx token from the input stream 1024 | */ 1025 | private fetchRegEx(): RegExpLexerResult { 1026 | // --- Get the tail 1027 | const tailPosition = 1028 | this._ahead.length > 0 ? this._ahead[0].startPosition : this._lastFetchPosition; 1029 | const tail = this.input.getTail(tailPosition); 1030 | 1031 | // --- Parse the tail. If no error, the entire tail is the RegExp 1032 | try { 1033 | const regexpResult = parseRegExpLiteral(tail); 1034 | const text = regexpResult.raw; 1035 | 1036 | // --- Consume the characters parsed successfully 1037 | for (let i = 1; i < text.length; i++) { 1038 | this.fetchNextChar(); 1039 | this._prefetched = null; 1040 | this._prefetchedPos = null; 1041 | this._prefetchedColumn = null; 1042 | } 1043 | 1044 | this._ahead.length = 0; 1045 | 1046 | // --- Return the token 1047 | return { 1048 | success: true, 1049 | pattern: regexpResult.pattern.raw, 1050 | flags: regexpResult.flags.raw, 1051 | length: text.length, 1052 | }; 1053 | } catch (parseErr: any) { 1054 | let errorIndex = parseErr.index; 1055 | if (parseErr.toString().includes("Invalid flag")) { 1056 | while (errorIndex < tail.length && "dgimsuy".includes(tail[errorIndex])) { 1057 | errorIndex++; 1058 | } 1059 | } 1060 | 1061 | // --- If there is no error, something is wrong 1062 | if (errorIndex === undefined) { 1063 | return { 1064 | success: false, 1065 | pattern: tail[0], 1066 | }; 1067 | } 1068 | 1069 | // --- Try to parse the tail before the error position 1070 | const tailBeforeError = tail.substring(0, errorIndex); 1071 | try { 1072 | const regexpResult = parseRegExpLiteral(tailBeforeError); 1073 | const text = regexpResult.raw; 1074 | 1075 | // --- Consume the characters parsed successfully 1076 | for (let i = 1; i < text.length; i++) { 1077 | this.fetchNextChar(); 1078 | this._prefetched = null; 1079 | this._prefetchedPos = null; 1080 | this._prefetchedColumn = null; 1081 | } 1082 | 1083 | this._ahead.length = 0; 1084 | 1085 | // --- Return the token 1086 | return { 1087 | success: true, 1088 | pattern: regexpResult.pattern.raw, 1089 | flags: regexpResult.flags.raw, 1090 | length: text.length, 1091 | }; 1092 | } catch (parseErr2) { 1093 | // --- This is really not a regexp 1094 | return { 1095 | success: false, 1096 | pattern: tailBeforeError, 1097 | }; 1098 | } 1099 | } 1100 | } 1101 | } 1102 | 1103 | /** 1104 | * Reserved ID-like tokens 1105 | */ 1106 | const resolverHash = new Map<string, TokenType>(); 1107 | resolverHash.set("typeof", TokenType.Typeof); 1108 | resolverHash.set("Infinity", TokenType.Infinity); 1109 | resolverHash.set("NaN", TokenType.NaN); 1110 | resolverHash.set("true", TokenType.True); 1111 | resolverHash.set("false", TokenType.False); 1112 | resolverHash.set("undefined", TokenType.Undefined); 1113 | resolverHash.set("null", TokenType.Null); 1114 | resolverHash.set("in", TokenType.In); 1115 | resolverHash.set("let", TokenType.Let); 1116 | resolverHash.set("const", TokenType.Const); 1117 | resolverHash.set("var", TokenType.Var); 1118 | resolverHash.set("if", TokenType.If); 1119 | resolverHash.set("else", TokenType.Else); 1120 | resolverHash.set("return", TokenType.Return); 1121 | resolverHash.set("break", TokenType.Break); 1122 | resolverHash.set("continue", TokenType.Continue); 1123 | resolverHash.set("do", TokenType.Do); 1124 | resolverHash.set("while", TokenType.While); 1125 | resolverHash.set("for", TokenType.For); 1126 | resolverHash.set("of", TokenType.Of); 1127 | resolverHash.set("try", TokenType.Try); 1128 | resolverHash.set("catch", TokenType.Catch); 1129 | resolverHash.set("finally", TokenType.Finally); 1130 | resolverHash.set("throw", TokenType.Throw); 1131 | resolverHash.set("switch", TokenType.Switch); 1132 | resolverHash.set("case", TokenType.Case); 1133 | resolverHash.set("default", TokenType.Default); 1134 | resolverHash.set("delete", TokenType.Delete); 1135 | resolverHash.set("function", TokenType.Function); 1136 | resolverHash.set("as", TokenType.As); 1137 | 1138 | /** 1139 | * Tests if a token id EOF 1140 | * @param t Token instance 1141 | */ 1142 | function isEof(t: Token): boolean { 1143 | return t.type === TokenType.Eof; 1144 | } 1145 | 1146 | /** 1147 | * Tests if a token is whitespace 1148 | * @param t Token instance 1149 | */ 1150 | function isWs(t: Token): boolean { 1151 | return t.type <= TokenType.Ws; 1152 | } 1153 | 1154 | /** 1155 | * Tests if a character is an identifier start character 1156 | * @param ch Character to test 1157 | */ 1158 | function isIdStart(ch: string): boolean { 1159 | return (ch >= "a" && ch <= "z") || (ch >= "A" && ch <= "Z") || ch === "_" || ch === "$"; 1160 | } 1161 | 1162 | /** 1163 | * Tests if a character is an identifier continuation character 1164 | * @param ch Character to test 1165 | */ 1166 | function isIdContinuation(ch: string): boolean { 1167 | return ( 1168 | (ch >= "a" && ch <= "z") || 1169 | (ch >= "A" && ch <= "Z") || 1170 | (ch >= "0" && ch <= "9") || 1171 | ch === "_" || 1172 | ch === "$" 1173 | ); 1174 | } 1175 | 1176 | /** 1177 | * Tests if a character is a binary digit 1178 | * @param ch Character to test 1179 | */ 1180 | function isBinaryDigit(ch: string): boolean { 1181 | return ch === "0" || ch === "1"; 1182 | } 1183 | 1184 | /** 1185 | * Tests if a character is a decimal digit 1186 | * @param ch Character to test 1187 | */ 1188 | function isDecimalDigit(ch: string): boolean { 1189 | return ch >= "0" && ch <= "9"; 1190 | } 1191 | 1192 | /** 1193 | * Tests if a character is a hexadecimal digit 1194 | * @param ch Character to test 1195 | */ 1196 | function isHexadecimalDigit(ch: string): boolean { 1197 | return (ch >= "0" && ch <= "9") || (ch >= "A" && ch <= "F") || (ch >= "a" && ch <= "f"); 1198 | } 1199 | 1200 | /** 1201 | * Tests if a character is restricted in a string 1202 | * @param ch Character to test 1203 | */ 1204 | function isRestrictedInString(ch: string): boolean { 1205 | return ch === "\r" || ch === "\n" || ch === "\u0085" || ch === "\u2028" || ch === "\u2029"; 1206 | } 1207 | 1208 | // --- Result from RegExp lexing 1209 | export type RegExpLexerResult = { 1210 | success: boolean; 1211 | pattern?: string; 1212 | flags?: string; 1213 | length?: number; 1214 | }; 1215 | ```