themelia-react-ui 2.0.3 → 2.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +185 -0
- package/README.md +168 -36
- package/dist/{accordion-Cjb6sx0G.js → accordion-B9MHG0cT.js} +11 -11
- package/dist/accordion-B9MHG0cT.js.map +1 -0
- package/dist/action-menu-Dr8K-ghE.js +284 -0
- package/dist/action-menu-Dr8K-ghE.js.map +1 -0
- package/dist/{affordance-geometry.generated-tF36bREk.js → affordance-geometry.generated-P-aVJisu.js} +42 -36
- package/dist/affordance-geometry.generated-P-aVJisu.js.map +1 -0
- package/dist/{ai-artifact-DvTwDVyL.js → ai-artifact-C58tQcBy.js} +387 -350
- package/dist/ai-artifact-C58tQcBy.js.map +1 -0
- package/dist/app-shell-B_IuNWOo.js +321 -0
- package/dist/app-shell-B_IuNWOo.js.map +1 -0
- package/dist/{attachment-BxDfL2mw.js → attachment-Dg_69lPS.js} +9 -9
- package/dist/{attachment-BxDfL2mw.js.map → attachment-Dg_69lPS.js.map} +1 -1
- package/dist/{auth-BGua8ouh.js → auth-DTkkshS8.js} +44 -47
- package/dist/auth-DTkkshS8.js.map +1 -0
- package/dist/{avatar-DAPlDvhJ.js → avatar-CeZaFmNH.js} +8 -8
- package/dist/avatar-CeZaFmNH.js.map +1 -0
- package/dist/{badge-HpFqoqSY.js → badge-xIiV_E7f.js} +5 -5
- package/dist/{badge-HpFqoqSY.js.map → badge-xIiV_E7f.js.map} +1 -1
- package/dist/base/accordion.js +1 -1
- package/dist/base/action-menu.js +1 -1
- package/dist/base/attachment.js +1 -1
- package/dist/base/avatar.js +1 -1
- package/dist/base/badge.js +1 -1
- package/dist/base/batch-action-bar.js +1 -1
- package/dist/base/buttons.js +1 -1
- package/dist/base/cards.js +2 -2
- package/dist/base/carousel.js +3 -3
- package/dist/base/cascader.js +2 -372
- package/dist/base/chart.js +1 -1
- package/dist/base/choice-inputs.js +2 -2
- package/dist/base/code-block.js +1 -1
- package/dist/base/combobox.js +2 -2
- package/dist/base/command.js +1 -1
- package/dist/base/copyable.js +1 -1
- package/dist/base/date-pickers.js +2 -2
- package/dist/base/display.js +6 -6
- package/dist/base/feedback.js +3 -3
- package/dist/base/forms-numeric.js +1 -1
- package/dist/base/forms.js +3 -3
- package/dist/base/inline-edit.js +10 -10
- package/dist/base/item.js +1 -1
- package/dist/base/media-gallery.js +1 -1
- package/dist/base/metadata-list.js +1 -1
- package/dist/base/navigation.js +7 -7
- package/dist/base/popover-menu.js +1 -1
- package/dist/base/popover.js +1 -1
- package/dist/base/qr-code.js +1 -1
- package/dist/base/repeaters.js +12 -12
- package/dist/base/repeaters.js.map +1 -1
- package/dist/base/sheet.js +1 -1
- package/dist/base/sidebar.js +2 -2
- package/dist/base/signature-pad.js +7 -7
- package/dist/base/signature-pad.js.map +1 -1
- package/dist/base/skeleton.js +1 -1
- package/dist/base/spinner.js +4 -4
- package/dist/base/structure.js +1 -1
- package/dist/base/table.js +1 -1
- package/dist/base/text-inputs.js +2 -2
- package/dist/base/toaster.js +36 -36
- package/dist/base/toaster.js.map +1 -1
- package/dist/base/tree.js +1 -1
- package/dist/base/upload-queue.js +1 -1
- package/dist/base/upload.js +3 -3
- package/dist/base/value-inputs.js +1 -1
- package/dist/{batch-action-bar-DnUJ21NQ.js → batch-action-bar-Cb8cuvyy.js} +6 -6
- package/dist/batch-action-bar-Cb8cuvyy.js.map +1 -0
- package/dist/blocks/admin.js +23 -23
- package/dist/blocks/admin.js.map +1 -1
- package/dist/blocks/ai.js +230 -216
- package/dist/blocks/ai.js.map +1 -1
- package/dist/blocks/analytics/usage-card.js +1 -1
- package/dist/blocks/analytics.js +326 -307
- package/dist/blocks/analytics.js.map +1 -1
- package/dist/blocks/collaboration.js +1 -1
- package/dist/blocks/commerce/receipt.js +1 -1
- package/dist/blocks/commerce.js +1247 -1169
- package/dist/blocks/commerce.js.map +1 -1
- package/dist/blocks/data-display.js +2 -2
- package/dist/blocks/onboarding.js +6 -6
- package/dist/blocks/onboarding.js.map +1 -1
- package/dist/blocks/planning.js +11 -11
- package/dist/blocks/timelines.js +1 -1
- package/dist/{breadcrumbs-C-oqs1sQ.js → breadcrumbs-J-K2kl4c.js} +6 -6
- package/dist/{breadcrumbs-C-oqs1sQ.js.map → breadcrumbs-J-K2kl4c.js.map} +1 -1
- package/dist/{button-DVzspB_G.js → button-S2Cubiiz.js} +3 -3
- package/dist/button-S2Cubiiz.js.map +1 -0
- package/dist/{buttons-DPZekJFu.js → buttons-BedGfygC.js} +16 -16
- package/dist/buttons-BedGfygC.js.map +1 -0
- package/dist/cards-rNX1M4Xn.js +69 -0
- package/dist/cards-rNX1M4Xn.js.map +1 -0
- package/dist/cascader-B1MiqkBq.js +439 -0
- package/dist/cascader-B1MiqkBq.js.map +1 -0
- package/dist/chart-Darg_0Kv.js +162 -0
- package/dist/chart-Darg_0Kv.js.map +1 -0
- package/dist/{checkbox-AWIhNhmo.js → checkbox-fflqmODR.js} +9 -5
- package/dist/checkbox-fflqmODR.js.map +1 -0
- package/dist/choice-inputs-zUgBZfE4.js +777 -0
- package/dist/choice-inputs-zUgBZfE4.js.map +1 -0
- package/dist/{code-block-DeneBJR_.js → code-block-BzmM9Eig.js} +9 -9
- package/dist/code-block-BzmM9Eig.js.map +1 -0
- package/dist/{collapsible-C-_l4hpy.js → collapsible-BkAI_XD4.js} +5 -5
- package/dist/{collapsible-C-_l4hpy.js.map → collapsible-BkAI_XD4.js.map} +1 -1
- package/dist/{collection-content-By56pLog.js → collection-content-gDh1v6O4.js} +3 -3
- package/dist/{collection-content-By56pLog.js.map → collection-content-gDh1v6O4.js.map} +1 -1
- package/dist/combobox-BEx0vJgF.js +362 -0
- package/dist/combobox-BEx0vJgF.js.map +1 -0
- package/dist/{combobox-Dxo2fAYQ.js → combobox-BwzoXFSG.js} +254 -247
- package/dist/combobox-BwzoXFSG.js.map +1 -0
- package/dist/{command-C25VMRII.js → command-CrlPIL6m.js} +12 -12
- package/dist/command-CrlPIL6m.js.map +1 -0
- package/dist/{comments-DD0DUPwJ.js → comments-Dmpp0YIc.js} +380 -379
- package/dist/comments-Dmpp0YIc.js.map +1 -0
- package/dist/components/base/action-menu/action-menu.d.ts +9 -1
- package/dist/components/base/buttons/button-group.recipes.d.ts +2 -2
- package/dist/components/base/cards/card.recipes.d.ts +16 -7
- package/dist/components/base/cards/partials/card-primitives.d.ts +9 -2
- package/dist/components/base/cards/partials/smart-card-header-row.d.ts +3 -1
- package/dist/components/base/cards/smart-card.types.d.ts +11 -7
- package/dist/components/base/cascader/cascader-panel.d.ts +76 -0
- package/dist/components/base/chart/chart.d.ts +6 -1
- package/dist/components/base/choice-inputs/checkbox.d.ts +7 -2
- package/dist/components/base/choice-inputs/choice-types.d.ts +1 -1
- package/dist/components/base/choice-inputs/switch-card.d.ts +7 -0
- package/dist/components/base/choice-inputs/toggle-field.d.ts +7 -0
- package/dist/components/base/combobox/index.d.ts +4 -2
- package/dist/components/base/date-pickers/datepicker.types.d.ts +7 -0
- package/dist/components/base/feedback/alert.d.ts +1 -1
- package/dist/components/base/forms/controlled-form-field.d.ts +6 -6
- package/dist/components/base/forms/form-field.d.ts +5 -3
- package/dist/components/base/forms/form-sizing.d.ts +7 -7
- package/dist/components/base/forms/index.d.ts +1 -0
- package/dist/components/base/forms/server-errors.d.ts +16 -0
- package/dist/components/base/forms/workflow.d.ts +11 -2
- package/dist/components/base/forms-numeric/coordinates-input.d.ts +4 -0
- package/dist/components/base/metadata-list/metadata-list.d.ts +3 -1
- package/dist/components/base/navigation/dropdown-menu.d.ts +1 -1
- package/dist/components/base/navigation/page-header/page-header-commands.d.ts +14 -7
- package/dist/components/base/navigation/page-header/page-header.d.ts +13 -4
- package/dist/components/base/popover/popover.d.ts +3 -3
- package/dist/components/base/popover/popover.recipes.d.ts +10 -6
- package/dist/components/base/sheet/sheet.d.ts +2 -0
- package/dist/components/base/sidebar/index.d.ts +1 -1
- package/dist/components/base/sidebar/sidebar.d.ts +27 -5
- package/dist/components/base/structure/composition.recipes.d.ts +17 -0
- package/dist/components/base/text-inputs/hooks/use-form-field-state.d.ts +7 -2
- package/dist/components/base/text-inputs/input.d.ts +4 -2
- package/dist/components/base/text-inputs/textarea.d.ts +4 -2
- package/dist/components/base/toaster/index.d.ts +1 -0
- package/dist/components/base/value-inputs/date-time-input.d.ts +2 -1
- package/dist/components/base/value-inputs/slider.d.ts +5 -1
- package/dist/components/base/value-inputs/tags-input.d.ts +5 -1
- package/dist/components/blocks/ai/ai-file-tree/types.d.ts +1 -1
- package/dist/components/blocks/ai/ai-tool-call/types.d.ts +3 -1
- package/dist/components/blocks/ai/ai.strings.d.ts +2 -0
- package/dist/components/blocks/analytics/metric-comparison/metric-comparison.d.ts +2 -0
- package/dist/components/blocks/analytics/usage-card/usage-card.strings.d.ts +2 -0
- package/dist/components/blocks/commerce/cart-summary/cart-summary.d.ts +1 -1
- package/dist/components/blocks/commerce/cart-summary/types.d.ts +3 -1
- package/dist/components/blocks/commerce/discount-stack/discount-stack.d.ts +1 -1
- package/dist/components/blocks/commerce/discount-stack/types.d.ts +3 -1
- package/dist/components/blocks/commerce/fulfillment-group/fulfillment-group.d.ts +1 -1
- package/dist/components/blocks/commerce/fulfillment-group/types.d.ts +11 -2
- package/dist/components/blocks/commerce/index.d.ts +7 -6
- package/dist/components/blocks/commerce/inventory-section/inventory-section.d.ts +1 -1
- package/dist/components/blocks/commerce/inventory-section/types.d.ts +7 -2
- package/dist/components/blocks/commerce/invoice-header/index.d.ts +2 -1
- package/dist/components/blocks/commerce/invoice-header/invoice-header.d.ts +1 -1
- package/dist/components/blocks/commerce/invoice-header/types.d.ts +20 -6
- package/dist/components/blocks/commerce/invoice-line-items/invoice-line-items.d.ts +1 -1
- package/dist/components/blocks/commerce/invoice-line-items/types.d.ts +24 -7
- package/dist/components/blocks/commerce/invoice-mini/index.d.ts +2 -1
- package/dist/components/blocks/commerce/invoice-mini/types.d.ts +7 -1
- package/dist/components/blocks/commerce/order-customer/order-customer.d.ts +1 -1
- package/dist/components/blocks/commerce/order-customer/types.d.ts +9 -1
- package/dist/components/blocks/commerce/refund-status/refund-status.d.ts +1 -1
- package/dist/components/blocks/commerce/refund-status/types.d.ts +3 -1
- package/dist/components/blocks/commerce/shipment-tracking/shipment-tracking.d.ts +1 -1
- package/dist/components/blocks/commerce/shipment-tracking/types.d.ts +3 -1
- package/dist/components/blocks/commerce/tax-breakdown/tax-breakdown.d.ts +1 -1
- package/dist/components/blocks/commerce/tax-breakdown/types.d.ts +3 -1
- package/dist/components/features/activities/activities.types.d.ts +8 -2
- package/dist/components/features/activities/partials/activity-marker.d.ts +7 -3
- package/dist/components/features/activities/partials/activity-row.d.ts +24 -0
- package/dist/components/features/event-calendar/colors.d.ts +2 -2
- package/dist/components/features/event-calendar/event-calendar.types.d.ts +4 -0
- package/dist/components/features/filters/facets/select-facet.d.ts +2 -1
- package/dist/components/features/filters/filters.strings.d.ts +4 -0
- package/dist/components/features/filters/filters.types.d.ts +3 -0
- package/dist/components/features/filters/partials/filter-editor-header.d.ts +16 -0
- package/dist/components/features/filters/partials/filter-popover-content.d.ts +1 -1
- package/dist/components/features/filters/partials/nested-filter-options.d.ts +21 -0
- package/dist/components/features/filters/partials/regular-filter-content.d.ts +2 -1
- package/dist/components/features/filters/utils/filter-utils.d.ts +21 -3
- package/dist/components/features/global-search/global-search.types.d.ts +2 -0
- package/dist/components/features/global-search/partials/global-search-input.d.ts +10 -0
- package/dist/components/features/global-search/partials/global-search-result-row.d.ts +7 -1
- package/dist/components/features/media-library/media-library.strings.d.ts +2 -0
- package/dist/components/features/media-library/media-library.types.d.ts +14 -4
- package/dist/components/features/media-library/partials/media-library-partials.d.ts +1 -1
- package/dist/components/features/products/components/product-options-matrix.d.ts +1 -1
- package/dist/components/features/products/components/product-variant-editor.d.ts +1 -1
- package/dist/components/features/products/components/product-variants-manager.d.ts +1 -1
- package/dist/components/features/products/products.strings.d.ts +9 -1
- package/dist/components/features/products/products.types.d.ts +43 -6
- package/dist/components/features/resource/resource.d.ts +5 -1
- package/dist/components/features/resource/resource.types.d.ts +12 -1
- package/dist/components/features/rich-text-editor/partials/image-uploads.d.ts +1 -1
- package/dist/components/features/rich-text-editor/rich-text-editor.recipes.d.ts +12 -2
- package/dist/components/features/table/partials/resource-cell.d.ts +22 -5
- package/dist/components/features/table/table.types.d.ts +1 -1
- package/dist/components/layout/app-shell/app-shell.types.d.ts +35 -6
- package/dist/components/layout/app-shell/index.d.ts +1 -1
- package/dist/components/layout/app-shell/shell-regions.d.ts +27 -0
- package/dist/components/layout/app-shell/sidebar-inset-layout.d.ts +1 -1
- package/dist/components/layout/app-shell/stacked-layout.d.ts +1 -1
- package/dist/components/layout/app-shell/topbar-sidebar-layout.d.ts +1 -1
- package/dist/components/layout/containers/two-column-layout.d.ts +2 -0
- package/dist/components/layout/header/header.recipes.d.ts +6 -0
- package/dist/components/layout/header/header.types.d.ts +6 -6
- package/dist/components/layout/navigation/section-nav/section-nav.d.ts +5 -0
- package/dist/components/layout/page/index.d.ts +2 -1
- package/dist/components/layout/page/page.d.ts +1 -0
- package/dist/components/layout/page/page.strings.d.ts +11 -0
- package/dist/components/layout/page/page.types.d.ts +69 -17
- package/dist/components/layout/page/partials/page-header.d.ts +1 -1
- package/dist/components/layout/settings/settings.d.ts +9 -2
- package/dist/components/layout/sidebar/app-sidebar.d.ts +1 -1
- package/dist/components/layout/sidebar/components/sidebar-grouped-navigation.d.ts +1 -1
- package/dist/components/layout/sidebar/components/sidebar-navigation.d.ts +1 -1
- package/dist/components/layout/sidebar/index.d.ts +2 -1
- package/dist/components/layout/sidebar/sidebar-navigation-rail.d.ts +36 -0
- package/dist/components/layout/sidebar/sidebar.context.d.ts +5 -7
- package/dist/components/layout/sidebar/sidebar.types.d.ts +48 -10
- package/dist/components/layout/sidebar/sidebar.utils.d.ts +9 -1
- package/dist/components/layout/workspace/workspace.types.d.ts +8 -1
- package/dist/components/typography/text.styles.d.ts +26 -1
- package/dist/components/ui/alert-dialog.d.ts +2 -2
- package/dist/components/ui/combobox.d.ts +1 -1
- package/dist/components/ui/dialog.d.ts +2 -2
- package/dist/components/ui/overlay-layer.d.ts +18 -0
- package/dist/components/ui/sheet.d.ts +5 -1
- package/dist/components/ui/sidebar-state.d.ts +82 -0
- package/dist/components/ui/sidebar.d.ts +5 -17
- package/dist/composition.recipes-COdiFhxB.js +19 -0
- package/dist/composition.recipes-COdiFhxB.js.map +1 -0
- package/dist/containers-CIxwbZt5.js +112 -0
- package/dist/containers-CIxwbZt5.js.map +1 -0
- package/dist/{control-group-recipes-DBXmfh2E.js → control-group-recipes-fMuFo0H7.js} +6 -6
- package/dist/control-group-recipes-fMuFo0H7.js.map +1 -0
- package/dist/{copyable-ClBuk_UK.js → copyable-DIznBqiT.js} +7 -7
- package/dist/{copyable-ClBuk_UK.js.map → copyable-DIznBqiT.js.map} +1 -1
- package/dist/{data-display-DT_khPl_.js → data-display-BdgCAw8T.js} +277 -285
- package/dist/data-display-BdgCAw8T.js.map +1 -0
- package/dist/{date-DCrsDTZc.js → date-Cq8ioE4o.js} +5 -5
- package/dist/{date-DCrsDTZc.js.map → date-Cq8ioE4o.js.map} +1 -1
- package/dist/{date-picker-B2K3ucWn.js → date-picker-RlIzKom_.js} +501 -480
- package/dist/date-picker-RlIzKom_.js.map +1 -0
- package/dist/date-pickers-DY-CpXS1.js +179 -0
- package/dist/date-pickers-DY-CpXS1.js.map +1 -0
- package/dist/{display-hAc1HjpI.js → display-KsZLw9Ov.js} +9 -9
- package/dist/{display-hAc1HjpI.js.map → display-KsZLw9Ov.js.map} +1 -1
- package/dist/dropdown-menu-CLs8Lihj.js +194 -0
- package/dist/dropdown-menu-CLs8Lihj.js.map +1 -0
- package/dist/features/activities.js +302 -253
- package/dist/features/activities.js.map +1 -1
- package/dist/features/ai-chat.js +15 -14
- package/dist/features/ai-chat.js.map +1 -1
- package/dist/features/appointment-calendar.js +222 -220
- package/dist/features/appointment-calendar.js.map +1 -1
- package/dist/features/async-preview.js +11 -10
- package/dist/features/async-preview.js.map +1 -1
- package/dist/features/combobox.js +1 -1
- package/dist/features/comments.js +1 -1
- package/dist/features/data-view.js +13 -13
- package/dist/features/data-view.js.map +1 -1
- package/dist/features/email-composer.js +249 -249
- package/dist/features/email-composer.js.map +1 -1
- package/dist/features/event-calendar.js +264 -269
- package/dist/features/event-calendar.js.map +1 -1
- package/dist/features/filters.js +1 -1
- package/dist/features/global-search.js +167 -138
- package/dist/features/global-search.js.map +1 -1
- package/dist/features/kanban.js +6 -7
- package/dist/features/kanban.js.map +1 -1
- package/dist/features/map.js +269 -260
- package/dist/features/map.js.map +1 -1
- package/dist/features/media-library.js +827 -777
- package/dist/features/media-library.js.map +1 -1
- package/dist/features/mentions.js +1 -1
- package/dist/features/overlays.js +1 -1
- package/dist/features/products.js +1246 -1169
- package/dist/features/products.js.map +1 -1
- package/dist/features/questionnaire.js +13 -13
- package/dist/features/questionnaire.js.map +1 -1
- package/dist/features/resource-assignment.js +8 -8
- package/dist/features/resource.js +127 -123
- package/dist/features/resource.js.map +1 -1
- package/dist/features/rich-text-editor.js +2 -2
- package/dist/features/schema-form.js +254 -253
- package/dist/features/schema-form.js.map +1 -1
- package/dist/features/suggestions.js +3 -3
- package/dist/features/sync.js +3 -3
- package/dist/features/table.js +1 -1
- package/dist/features/theme-tweaker.js +95 -96
- package/dist/features/theme-tweaker.js.map +1 -1
- package/dist/{feedback-BYIhtitP.js → feedback-Dm4izqFn.js} +81 -80
- package/dist/feedback-Dm4izqFn.js.map +1 -0
- package/dist/{field-affordance-recipes-DZhnIL_Y.js → field-affordance-recipes-DWcNcOjh.js} +4 -4
- package/dist/field-affordance-recipes-DWcNcOjh.js.map +1 -0
- package/dist/{filters-DJVAveHA.js → filters-DzgSJpMK.js} +693 -645
- package/dist/filters-DzgSJpMK.js.map +1 -0
- package/dist/{form-field-CwH9y77i.js → form-field-BriWAXyg.js} +14 -8
- package/dist/form-field-BriWAXyg.js.map +1 -0
- package/dist/{form-sizing-BdP_Mf5p.js → form-sizing-Bs2qFUeE.js} +4 -4
- package/dist/form-sizing-Bs2qFUeE.js.map +1 -0
- package/dist/forms-C0RGnJTe.js +216 -0
- package/dist/forms-C0RGnJTe.js.map +1 -0
- package/dist/{forms-numeric-B_jraJaQ.js → forms-numeric-DIhghX1Q.js} +295 -291
- package/dist/forms-numeric-DIhghX1Q.js.map +1 -0
- package/dist/{header-BSfIEWbI.js → header-DMMxG__r.js} +67 -63
- package/dist/header-DMMxG__r.js.map +1 -0
- package/dist/header.recipes-BigdJjw1.js +6 -0
- package/dist/header.recipes-BigdJjw1.js.map +1 -0
- package/dist/hooks/useFormErrors.d.ts +1 -1
- package/dist/{hooks-ClH9thn4.js → hooks-DB6CJ4MR.js} +26 -14
- package/dist/hooks-DB6CJ4MR.js.map +1 -0
- package/dist/{icon-badge-DYKIwvOV.js → icon-badge-DZF-YjQp.js} +4 -4
- package/dist/{icon-badge-DYKIwvOV.js.map → icon-badge-DZF-YjQp.js.map} +1 -1
- package/dist/index.js +4 -5
- package/dist/input-CvyDQlY7.js +289 -0
- package/dist/input-CvyDQlY7.js.map +1 -0
- package/dist/input-DMBwAZmr.js +21 -0
- package/dist/input-DMBwAZmr.js.map +1 -0
- package/dist/item-DjCcM18E.js +277 -0
- package/dist/item-DjCcM18E.js.map +1 -0
- package/dist/layout/app-shell.js +1 -1
- package/dist/layout/auth.js +1 -1
- package/dist/layout/containers.js +1 -1
- package/dist/layout/header.js +1 -1
- package/dist/layout/hooks.js +1 -1
- package/dist/layout/navigation.js +1 -1
- package/dist/layout/page.js +2 -2
- package/dist/layout/settings.js +1 -1
- package/dist/layout/sidebar.js +3 -3
- package/dist/layout/workspace.js +1 -1
- package/dist/layout.js +11 -11
- package/dist/lib/affordance-geometry.generated.d.ts +41 -35
- package/dist/lib/control-group-recipes.d.ts +5 -5
- package/dist/lib/field-affordance-recipes.d.ts +2 -2
- package/dist/lib/motion-recipes.d.ts +3 -0
- package/dist/lib/navigation-row-recipes.d.ts +7 -7
- package/dist/lib/overlay-anatomy-recipes.d.ts +3 -3
- package/dist/lib/portal-recipes.d.ts +3 -0
- package/dist/lib/token-merge.generated.d.ts +13 -10
- package/dist/lib/ui-provider/types.d.ts +1 -1
- package/dist/lib/utils.js +1 -1
- package/dist/map-demo-tile.svg +1 -1
- package/dist/{media-gallery-3zIVLMWH.js → media-gallery-C-vChPmE.js} +6 -6
- package/dist/{media-gallery-3zIVLMWH.js.map → media-gallery-C-vChPmE.js.map} +1 -1
- package/dist/{mentions-CGlEJ15c.js → mentions-0_4nWuI-.js} +10 -10
- package/dist/{mentions-CGlEJ15c.js.map → mentions-0_4nWuI-.js.map} +1 -1
- package/dist/{message-DZ_ruyUV.js → message-g8p7eVtN.js} +7 -7
- package/dist/message-g8p7eVtN.js.map +1 -0
- package/dist/metadata-list-C3MkU-Om.js +443 -0
- package/dist/metadata-list-C3MkU-Om.js.map +1 -0
- package/dist/{motion-recipes-ChBg4HBq.js → motion-recipes-DitYv6P2.js} +10 -2
- package/dist/motion-recipes-DitYv6P2.js.map +1 -0
- package/dist/navigation-C90-oQFq.js +255 -0
- package/dist/navigation-C90-oQFq.js.map +1 -0
- package/dist/{navigation-BzhHI3lo.js → navigation-DaRjthVk.js} +101 -82
- package/dist/navigation-DaRjthVk.js.map +1 -0
- package/dist/{navigation-row-recipes-BFLCk_Aq.js → navigation-row-recipes-BGVLvU_O.js} +9 -9
- package/dist/navigation-row-recipes-BGVLvU_O.js.map +1 -0
- package/dist/{number-DbV1Dija.js → number-CRU3kGuk.js} +5 -5
- package/dist/{number-DbV1Dija.js.map → number-CRU3kGuk.js.map} +1 -1
- package/dist/overflow-tab-bar-IVFVQFV6.js +172 -0
- package/dist/overflow-tab-bar-IVFVQFV6.js.map +1 -0
- package/dist/overlays-BSgCtM2v.js +1013 -0
- package/dist/overlays-BSgCtM2v.js.map +1 -0
- package/dist/page-DUUKDAjo.js +331 -0
- package/dist/page-DUUKDAjo.js.map +1 -0
- package/dist/page-header-commands-7P3sfE9u.js +193 -0
- package/dist/page-header-commands-7P3sfE9u.js.map +1 -0
- package/dist/{pagination-BL25hu2s.js → pagination-CgCjElfu.js} +7 -7
- package/dist/{pagination-BL25hu2s.js.map → pagination-CgCjElfu.js.map} +1 -1
- package/dist/popover-B85RenIl.js +201 -0
- package/dist/popover-B85RenIl.js.map +1 -0
- package/dist/{popover-menu-Cn9rUkdc.js → popover-menu-DlnQ2zlR.js} +7 -7
- package/dist/{popover-menu-Cn9rUkdc.js.map → popover-menu-DlnQ2zlR.js.map} +1 -1
- package/dist/{popover.recipes-D3j8R8Yp.js → popover.recipes-DdiEp0rd.js} +18 -11
- package/dist/popover.recipes-DdiEp0rd.js.map +1 -0
- package/dist/portal-recipes-BpsV_iRw.js +9 -0
- package/dist/portal-recipes-BpsV_iRw.js.map +1 -0
- package/dist/{primitives-BFK08r9M.js → primitives-ByWgGUgt.js} +6 -6
- package/dist/{primitives-BFK08r9M.js.map → primitives-ByWgGUgt.js.map} +1 -1
- package/dist/primitives.js +4 -4
- package/dist/{progress-B1VnCT0K.js → progress-BJhQP9WX.js} +9 -9
- package/dist/{progress-B1VnCT0K.js.map → progress-BJhQP9WX.js.map} +1 -1
- package/dist/{receipt-BVSOH3Xo.js → receipt-tOc7B3hw.js} +7 -7
- package/dist/receipt-tOc7B3hw.js.map +1 -0
- package/dist/{rich-text-GOUyKQ-s.js → rich-text-Dth5E882.js} +4 -4
- package/dist/{rich-text-GOUyKQ-s.js.map → rich-text-Dth5E882.js.map} +1 -1
- package/dist/{rich-text-editor-cRLC-eBK.js → rich-text-editor-C1fOjBTd.js} +429 -430
- package/dist/rich-text-editor-C1fOjBTd.js.map +1 -0
- package/dist/{rich-text-editor-toolbar-DfXg0-VC.js → rich-text-editor-toolbar-laT3tpBT.js} +33 -29
- package/dist/rich-text-editor-toolbar-laT3tpBT.js.map +1 -0
- package/dist/{scroll-area-DcI-M1UT.js → scroll-area-B0dB2cBi.js} +2 -2
- package/dist/{scroll-area-DcI-M1UT.js.map → scroll-area-B0dB2cBi.js.map} +1 -1
- package/dist/{separator-Bom7NsGO.js → separator-Bb17I4pu.js} +2 -2
- package/dist/{separator-Bom7NsGO.js.map → separator-Bb17I4pu.js.map} +1 -1
- package/dist/{separator-BDV-Y648.js → separator-Ct_WSWTJ.js} +3 -3
- package/dist/{separator-BDV-Y648.js.map → separator-Ct_WSWTJ.js.map} +1 -1
- package/dist/services/actions.js +1 -1
- package/dist/settings-Bar5wiSo.js +167 -0
- package/dist/settings-Bar5wiSo.js.map +1 -0
- package/dist/sheet-BGPERY55.js +250 -0
- package/dist/sheet-BGPERY55.js.map +1 -0
- package/dist/sidebar-D-JYMMbY.js +686 -0
- package/dist/sidebar-D-JYMMbY.js.map +1 -0
- package/dist/sidebar-f9gRWXIR.js +1001 -0
- package/dist/sidebar-f9gRWXIR.js.map +1 -0
- package/dist/skeleton-BapcJd9C.js +265 -0
- package/dist/skeleton-BapcJd9C.js.map +1 -0
- package/dist/smart-card-CW0hjetE.js +617 -0
- package/dist/smart-card-CW0hjetE.js.map +1 -0
- package/dist/{structure-BmisBK-3.js → structure-DuxKl954.js} +3 -3
- package/dist/{structure-BmisBK-3.js.map → structure-DuxKl954.js.map} +1 -1
- package/dist/{structure.recipes-EPz60cU8.js → structure.recipes-BB4-n58V.js} +2 -2
- package/dist/{structure.recipes-EPz60cU8.js.map → structure.recipes-BB4-n58V.js.map} +1 -1
- package/dist/style.css +1 -1
- package/dist/{table-DsfzoUia.js → table-B4TE7iOV.js} +3 -3
- package/dist/{table-DsfzoUia.js.map → table-B4TE7iOV.js.map} +1 -1
- package/dist/{table-BweFy_P9.js → table-CFOWY9pQ.js} +394 -388
- package/dist/table-CFOWY9pQ.js.map +1 -0
- package/dist/{tabs-CDocW5D1.js → tabs-e7ko9S2o.js} +11 -11
- package/dist/tabs-e7ko9S2o.js.map +1 -0
- package/dist/{text-inputs-f9Ia-aCi.js → text-inputs-B1culiSK.js} +107 -104
- package/dist/text-inputs-B1culiSK.js.map +1 -0
- package/dist/{text.styles-g4He6UIW.js → text.styles-B64My8RZ.js} +25 -10
- package/dist/text.styles-B64My8RZ.js.map +1 -0
- package/dist/theme.css +192 -149
- package/dist/{timelines-CwM_9IhD.js → timelines-D3Sl9ueq.js} +13 -12
- package/dist/timelines-D3Sl9ueq.js.map +1 -0
- package/dist/tooltip-8wz5S_Zr.js +113 -0
- package/dist/tooltip-8wz5S_Zr.js.map +1 -0
- package/dist/{tree-WnKQzwbq.js → tree-CzUJHnFs.js} +9 -9
- package/dist/{tree-WnKQzwbq.js.map → tree-CzUJHnFs.js.map} +1 -1
- package/dist/typography/rich-text.js +1 -1
- package/dist/typography-BQnine1k.js +117 -0
- package/dist/typography-BQnine1k.js.map +1 -0
- package/dist/typography.js +1 -1
- package/dist/ui-provider-C-74NVLc.js +178 -0
- package/dist/ui-provider-C-74NVLc.js.map +1 -0
- package/dist/ui-provider.js +2 -3
- package/dist/{upload-CXyxmQBP.js → upload-BOW6U2tI.js} +25 -24
- package/dist/upload-BOW6U2tI.js.map +1 -0
- package/dist/{upload-queue-Boscubn9.js → upload-queue-qUXn1ykN.js} +16 -14
- package/dist/upload-queue-qUXn1ykN.js.map +1 -0
- package/dist/{usage-card-BhO5eqkX.js → usage-card-BeSLY0i0.js} +145 -137
- package/dist/usage-card-BeSLY0i0.js.map +1 -0
- package/dist/{utils-BRhFDwuU.js → utils-B6kKb8jB.js} +303 -144
- package/dist/utils-B6kKb8jB.js.map +1 -0
- package/dist/value-inputs-skdVB2zh.js +980 -0
- package/dist/value-inputs-skdVB2zh.js.map +1 -0
- package/dist/{value-typography-BP8OG6h5.js → value-typography-uAmWUAL1.js} +2 -2
- package/dist/{value-typography-BP8OG6h5.js.map → value-typography-uAmWUAL1.js.map} +1 -1
- package/dist/workspace-DHeHv0qw.js +411 -0
- package/dist/workspace-DHeHv0qw.js.map +1 -0
- package/docs/consumer-skill/SKILL.md +15 -129
- package/docs/consumer-skill/agents/themelia-ui-designer.md +96 -0
- package/docs/consumer-skill/examples/app-layout.tsx +74 -13
- package/docs/consumer-skill/examples/authentication.tsx +77 -0
- package/docs/consumer-skill/examples/commerce-lines.tsx +271 -0
- package/docs/consumer-skill/examples/custom-composition.tsx +21 -0
- package/docs/consumer-skill/examples/detail.tsx +5 -4
- package/docs/consumer-skill/examples/edit.tsx +30 -38
- package/docs/consumer-skill/examples/editable-sections.tsx +38 -0
- package/docs/consumer-skill/examples/index-table.tsx +4 -4
- package/docs/consumer-skill/examples/manifest.json +88 -1
- package/docs/consumer-skill/examples/product-media.tsx +41 -0
- package/docs/consumer-skill/examples/product-parts.tsx +81 -0
- package/docs/consumer-skill/examples/rhf-bindings.tsx +86 -0
- package/docs/consumer-skill/examples/rhf-edit-page.tsx +141 -0
- package/docs/consumer-skill/examples/rhf-overlay-form.tsx +105 -0
- package/docs/consumer-skill/examples/rhf-transport.tsx +28 -0
- package/docs/consumer-skill/examples/token-domain-structure.tsx +68 -0
- package/docs/consumer-skill/references/agents-md.md +23 -11
- package/docs/consumer-skill/references/analytics.md +18 -1
- package/docs/consumer-skill/references/authentication.md +42 -0
- package/docs/consumer-skill/references/commerce-composition.md +97 -0
- package/docs/consumer-skill/references/component-contracts.json +325201 -0
- package/docs/consumer-skill/references/composition-mapping.md +106 -0
- package/docs/consumer-skill/references/composition.md +198 -46
- package/docs/consumer-skill/references/critique-rubric.md +98 -0
- package/docs/consumer-skill/references/custom-composition.md +261 -0
- package/docs/consumer-skill/references/data-reading.md +79 -0
- package/docs/consumer-skill/references/design-contract.md +43 -9
- package/docs/consumer-skill/references/discovery.md +109 -8
- package/docs/consumer-skill/references/domain-structures.md +124 -0
- package/docs/consumer-skill/references/installation.md +51 -36
- package/docs/consumer-skill/references/integration.md +41 -2
- package/docs/consumer-skill/references/product-composition.md +94 -0
- package/docs/consumer-skill/references/public-api.json +4390 -475
- package/docs/consumer-skill/references/review.md +47 -5
- package/docs/consumer-skill/references/rhf-workflows.md +93 -0
- package/docs/consumer-skill/references/screen-archetypes.md +34 -0
- package/docs/consumer-skill/references/screen-decisions.md +126 -0
- package/docs/consumer-skill/references/theming.md +25 -27
- package/docs/consumer-skill/references/tokens.json +20 -11
- package/docs/consumer-skill/references/view-craft.md +153 -0
- package/docs/consumer-skill/references/view-spec.md +73 -0
- package/docs/consumer-skill/references/workflows.md +9 -2
- package/docs/consumer-skill/scripts/composition-policy.mjs +39 -3
- package/docs/consumer-skill/scripts/composition-schema.d.mts +2 -0
- package/docs/consumer-skill/scripts/composition-schema.mjs +32 -0
- package/docs/consumer-skill/scripts/composition.mjs +254 -0
- package/docs/consumer-skill/scripts/discovery.mjs +96 -10
- package/docs/consumer-skill/scripts/inspect-component.mjs +11 -0
- package/docs/consumer-skill/scripts/inspect-tokens.mjs +11 -0
- package/docs/consumer-skill/scripts/inspection.mjs +193 -0
- package/docs/consumer-skill/scripts/map-composition.mjs +12 -0
- package/docs/consumer-skill/scripts/setup-skills.mjs +111 -0
- package/docs/consumer-skill/scripts/surface-policy.mjs +5 -5
- package/docs/consumer-skills/themelia-components/SKILL.md +31 -0
- package/docs/consumer-skills/themelia-composition/SKILL.md +34 -0
- package/docs/consumer-skills/themelia-design/SKILL.md +55 -0
- package/docs/consumer-skills/themelia-forms/SKILL.md +12 -0
- package/docs/consumer-skills/themelia-foundation/SKILL.md +15 -0
- package/docs/consumer-skills/themelia-tokenization/SKILL.md +42 -0
- package/package.json +4 -2
- package/templates/README.md +39 -0
- package/templates/admin-shell-template.tsx +382 -0
- package/templates/analytics-operations-template.tsx +376 -0
- package/templates/customers-template.tsx +509 -0
- package/templates/index.ts +29 -0
- package/templates/orders-template.tsx +1102 -0
- package/templates/products-template.tsx +834 -0
- package/templates/user-permissions-template.tsx +450 -0
- package/dist/accordion-Cjb6sx0G.js.map +0 -1
- package/dist/action-menu-CllU52Mm.js +0 -280
- package/dist/action-menu-CllU52Mm.js.map +0 -1
- package/dist/affordance-geometry.generated-tF36bREk.js.map +0 -1
- package/dist/ai-artifact-DvTwDVyL.js.map +0 -1
- package/dist/app-shell-C4FhvCqF.js +0 -236
- package/dist/app-shell-C4FhvCqF.js.map +0 -1
- package/dist/auth-BGua8ouh.js.map +0 -1
- package/dist/avatar-DAPlDvhJ.js.map +0 -1
- package/dist/base/cascader.js.map +0 -1
- package/dist/batch-action-bar-DnUJ21NQ.js.map +0 -1
- package/dist/button-DVzspB_G.js.map +0 -1
- package/dist/buttons-DPZekJFu.js.map +0 -1
- package/dist/cards-BaBZR-AU.js +0 -68
- package/dist/cards-BaBZR-AU.js.map +0 -1
- package/dist/chart-ZOYyK2xG.js +0 -155
- package/dist/chart-ZOYyK2xG.js.map +0 -1
- package/dist/checkbox-AWIhNhmo.js.map +0 -1
- package/dist/choice-inputs-CNHvARCJ.js +0 -770
- package/dist/choice-inputs-CNHvARCJ.js.map +0 -1
- package/dist/code-block-DeneBJR_.js.map +0 -1
- package/dist/combobox-DaHESuiL.js +0 -355
- package/dist/combobox-DaHESuiL.js.map +0 -1
- package/dist/combobox-Dxo2fAYQ.js.map +0 -1
- package/dist/command-C25VMRII.js.map +0 -1
- package/dist/comments-DD0DUPwJ.js.map +0 -1
- package/dist/containers-Mu1LMGV1.js +0 -103
- package/dist/containers-Mu1LMGV1.js.map +0 -1
- package/dist/control-group-recipes-DBXmfh2E.js.map +0 -1
- package/dist/data-display-DT_khPl_.js.map +0 -1
- package/dist/date-picker-B2K3ucWn.js.map +0 -1
- package/dist/date-pickers-Ds8gLaoP.js +0 -175
- package/dist/date-pickers-Ds8gLaoP.js.map +0 -1
- package/dist/dropdown-menu-Cl4X7L5S.js +0 -193
- package/dist/dropdown-menu-Cl4X7L5S.js.map +0 -1
- package/dist/feedback-BYIhtitP.js.map +0 -1
- package/dist/field-affordance-recipes-DZhnIL_Y.js.map +0 -1
- package/dist/filters-DJVAveHA.js.map +0 -1
- package/dist/form-field-CwH9y77i.js.map +0 -1
- package/dist/form-sizing-BdP_Mf5p.js.map +0 -1
- package/dist/forms-B9G_xwzi.js +0 -194
- package/dist/forms-B9G_xwzi.js.map +0 -1
- package/dist/forms-numeric-B_jraJaQ.js.map +0 -1
- package/dist/header-BSfIEWbI.js.map +0 -1
- package/dist/hooks-ClH9thn4.js.map +0 -1
- package/dist/input-CZw8iU99.js +0 -21
- package/dist/input-CZw8iU99.js.map +0 -1
- package/dist/input-DQdEd0YD.js +0 -263
- package/dist/input-DQdEd0YD.js.map +0 -1
- package/dist/item-K5_ghvze.js +0 -275
- package/dist/item-K5_ghvze.js.map +0 -1
- package/dist/message-DZ_ruyUV.js.map +0 -1
- package/dist/metadata-list-8N16ZMW_.js +0 -405
- package/dist/metadata-list-8N16ZMW_.js.map +0 -1
- package/dist/motion-recipes-ChBg4HBq.js.map +0 -1
- package/dist/navigation-Bnotn4AQ.js +0 -145
- package/dist/navigation-Bnotn4AQ.js.map +0 -1
- package/dist/navigation-BzhHI3lo.js.map +0 -1
- package/dist/navigation-row-recipes-BFLCk_Aq.js.map +0 -1
- package/dist/overflow-tab-bar-CWY7Nogn.js +0 -282
- package/dist/overflow-tab-bar-CWY7Nogn.js.map +0 -1
- package/dist/overlays-tMkF-VAN.js +0 -1003
- package/dist/overlays-tMkF-VAN.js.map +0 -1
- package/dist/page-CL9YeH_V.js +0 -224
- package/dist/page-CL9YeH_V.js.map +0 -1
- package/dist/page-header-commands-DOb6WsfD.js +0 -126
- package/dist/page-header-commands-DOb6WsfD.js.map +0 -1
- package/dist/popover-gnTOhbFM.js +0 -198
- package/dist/popover-gnTOhbFM.js.map +0 -1
- package/dist/popover.recipes-D3j8R8Yp.js.map +0 -1
- package/dist/receipt-BVSOH3Xo.js.map +0 -1
- package/dist/rich-text-editor-cRLC-eBK.js.map +0 -1
- package/dist/rich-text-editor-toolbar-DfXg0-VC.js.map +0 -1
- package/dist/settings-ybnjVwbd.js +0 -171
- package/dist/settings-ybnjVwbd.js.map +0 -1
- package/dist/sheet-ijlsjq-n.js +0 -213
- package/dist/sheet-ijlsjq-n.js.map +0 -1
- package/dist/sidebar-Db6gkh3U.js +0 -619
- package/dist/sidebar-Db6gkh3U.js.map +0 -1
- package/dist/sidebar-DkTO4VTO.js +0 -512
- package/dist/sidebar-DkTO4VTO.js.map +0 -1
- package/dist/skeleton-Byjnt7qr.js +0 -264
- package/dist/skeleton-Byjnt7qr.js.map +0 -1
- package/dist/smart-card-DiC5aac3.js +0 -604
- package/dist/smart-card-DiC5aac3.js.map +0 -1
- package/dist/table-BweFy_P9.js.map +0 -1
- package/dist/tabs-CDocW5D1.js.map +0 -1
- package/dist/text-inputs-f9Ia-aCi.js.map +0 -1
- package/dist/text.styles-g4He6UIW.js.map +0 -1
- package/dist/theme-scope-BJ6eE2xt.js +0 -50
- package/dist/theme-scope-BJ6eE2xt.js.map +0 -1
- package/dist/timelines-CwM_9IhD.js.map +0 -1
- package/dist/tooltip-yiRJUFjp.js +0 -111
- package/dist/tooltip-yiRJUFjp.js.map +0 -1
- package/dist/typography-UTnveOEy.js +0 -117
- package/dist/typography-UTnveOEy.js.map +0 -1
- package/dist/ui-provider-C_U0Xma4.js +0 -136
- package/dist/ui-provider-C_U0Xma4.js.map +0 -1
- package/dist/upload-CXyxmQBP.js.map +0 -1
- package/dist/upload-queue-Boscubn9.js.map +0 -1
- package/dist/usage-card-BhO5eqkX.js.map +0 -1
- package/dist/use-mobile-vaLKwgyf.js +0 -15
- package/dist/use-mobile-vaLKwgyf.js.map +0 -1
- package/dist/utils-BRhFDwuU.js.map +0 -1
- package/dist/value-inputs-TaX4OjNk.js +0 -963
- package/dist/value-inputs-TaX4OjNk.js.map +0 -1
- package/dist/workspace-B-OEm3Kf.js +0 -408
- package/dist/workspace-B-OEm3Kf.js.map +0 -1
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# View craft
|
|
2
|
+
|
|
3
|
+
Themelia already sets type, colour, spacing and surfaces. Craft is what remains:
|
|
4
|
+
deciding what a view shows, how much emphasis each part gets and how the parts
|
|
5
|
+
are arranged, so the user understands the screen at a glance. Each rule below is
|
|
6
|
+
a decision you can explain and check on the rendered result. Most improvements
|
|
7
|
+
remove something.
|
|
8
|
+
|
|
9
|
+
Start from the [data brief](data-reading.md#write-the-data-brief). Owner
|
|
10
|
+
selection, surfaces and inset ownership follow
|
|
11
|
+
[screen decisions](screen-decisions.md); this reference does not restate them.
|
|
12
|
+
|
|
13
|
+
## One focal point
|
|
14
|
+
|
|
15
|
+
Each view, and each region in it, has one thing the eye reaches first: the
|
|
16
|
+
figure that answers the question, the subject's identity and state, or the queue
|
|
17
|
+
of records that need action. Choose it from the brief. When two things compete,
|
|
18
|
+
demote one: a smaller text role, secondary tone, or a supporting region.
|
|
19
|
+
|
|
20
|
+
Check it by looking at a reduced screenshot: the focal point is still the
|
|
21
|
+
strongest mass. It sits in the first viewport at both 390 and 1440 wide.
|
|
22
|
+
|
|
23
|
+
## Spend emphasis on purpose
|
|
24
|
+
|
|
25
|
+
Emphasis works only by contrast, so it is a budget:
|
|
26
|
+
|
|
27
|
+
- **Commands.** One solid primary command per page or surface. Other commands
|
|
28
|
+
are outline or ghost, or sit in the same scope's overflow menu.
|
|
29
|
+
- **Text.** At most three levels in a region: title (`Heading`), body and values
|
|
30
|
+
(`Text`), supporting copy (`Text` secondary, smaller size). A fourth level
|
|
31
|
+
signals a hierarchy problem, not a missing style.
|
|
32
|
+
- **Tone.** `success`, `warning`, `destructive` and `info` mark a status that
|
|
33
|
+
changes what the user does. Kinds, categories and counts stay neutral. When
|
|
34
|
+
everything is coloured, nothing reads as status.
|
|
35
|
+
- **Size.** One hero figure size per region; peer figures share one size.
|
|
36
|
+
|
|
37
|
+
Adding emphasis in one place means removing it from another.
|
|
38
|
+
|
|
39
|
+
## Choose the encoding from the fact
|
|
40
|
+
|
|
41
|
+
| The fact | Encoding | Owner family (confirm with the finder and inspector) |
|
|
42
|
+
| --- | --- | --- |
|
|
43
|
+
| A figure the user must judge | Figure with its baseline delta; a sparkline when the path matters | `Metric`, `MetricTrendChip`, `MetricSparkline`; `MetricComparison` for current versus previous |
|
|
44
|
+
| Several peer figures for one period | One strip, not a grid of identical cards | `MetricBar`; `MetricGrid` with a quiet variant inside one surface |
|
|
45
|
+
| A series whose shape is the message | Chart with one emphasised series and minimal axes | `ChartCard` with Recharts children |
|
|
46
|
+
| Part of a whole, or use against a limit | Bar with the figure as its label | `Progress`, `UsageCard` |
|
|
47
|
+
| Facts about one record | Label and value list; one inline line for two to four short facts | `MetadataList` (inline or stacked), `InlineStat` |
|
|
48
|
+
| A lifecycle with a current stage | Steps with only the current stage emphasised | `StepsHorizontal`, `Timeline` |
|
|
49
|
+
| Events and history | Newest first, grouped by day | `Timeline`, `features/activities` |
|
|
50
|
+
| Many records compared on shared columns | Table whose columns are what users compare or sort | `DataTable`, `DataView` |
|
|
51
|
+
| Records that differ in shape | List rows: identity, one line of context, state, action | `Item`, `ItemGroup` |
|
|
52
|
+
| A money ledger | Right-aligned amounts, one rule above the grand total | `SummaryPanel` with `AmountRow`, `OrderSummary` |
|
|
53
|
+
| Nothing to show yet | What is missing and the next action | `EmptyState` |
|
|
54
|
+
|
|
55
|
+
Read [metrics and statistics](analytics.md) for the exact metric owner. A chart
|
|
56
|
+
must answer a question you can write in one sentence; otherwise show the figure.
|
|
57
|
+
Do not chart three values. Prefer a sparkline in a strip to a large chart that
|
|
58
|
+
repeats what the figures already say.
|
|
59
|
+
|
|
60
|
+
## Spend words carefully
|
|
61
|
+
|
|
62
|
+
- Label a value only when it is ambiguous. A ledger amount needs "Total"; an
|
|
63
|
+
order number in a header does not need "Order number".
|
|
64
|
+
- Do not repeat a column header inside its cells or a section title in each row.
|
|
65
|
+
- State a unit once, in the header or the value.
|
|
66
|
+
- A sentence fragment often reads better than three label and value pairs:
|
|
67
|
+
"2 of 3 shipped · 1 reserved at Plovdiv".
|
|
68
|
+
- Omit zero and not-applicable counters, or collapse them into one "Nothing
|
|
69
|
+
outstanding" line.
|
|
70
|
+
- Do not explain the interface on the page. A description states scope or
|
|
71
|
+
as-of time, not instructions.
|
|
72
|
+
|
|
73
|
+
## Density follows the audience
|
|
74
|
+
|
|
75
|
+
- **Queues** for operators: compact rows, more records per screen, identity,
|
|
76
|
+
state and amount; detail on open.
|
|
77
|
+
- **Overviews**: fewer, larger figures with room around them, each answering a
|
|
78
|
+
question.
|
|
79
|
+
- **Records**: the subject leads; evidence and history follow.
|
|
80
|
+
|
|
81
|
+
Change global density through the provider and density roots described in
|
|
82
|
+
[theming](theming.md). Do not shrink text roles to fit more data; remove
|
|
83
|
+
low-value content first.
|
|
84
|
+
|
|
85
|
+
## Rhythm groups content
|
|
86
|
+
|
|
87
|
+
- Gaps grow outward: within a group, between groups, between sections. Equal
|
|
88
|
+
gaps everywhere mean nothing groups. Use `Stack` and `Grid` gap roles.
|
|
89
|
+
- Few edges: peer regions share content start edges; numbers right-align on one
|
|
90
|
+
axis; labels share one axis.
|
|
91
|
+
- Few frames: one `Card` per independent subject. Continuous sections of one
|
|
92
|
+
subject share one surface and are separated by space, not rules or nested
|
|
93
|
+
boxes.
|
|
94
|
+
- Narrow widths keep the decision content first in reading order. Only data
|
|
95
|
+
tables that own horizontal scrolling may scroll sideways.
|
|
96
|
+
|
|
97
|
+
## Numbers and copy
|
|
98
|
+
|
|
99
|
+
- Numbers use tabular figures through the value primitives (`Money`, `Number`,
|
|
100
|
+
`Percent`, `Date`, `RelativeTime`) with one precision per column.
|
|
101
|
+
- Recency reads as relative time with the exact date available; documents show
|
|
102
|
+
absolute dates.
|
|
103
|
+
- A delta states direction and baseline: "↓ 0.9% vs September".
|
|
104
|
+
- Sentence case everywhere. Buttons are a verb and an object ("Record payment").
|
|
105
|
+
Empty states say what is missing and what to do. Errors say what happened and
|
|
106
|
+
how to recover.
|
|
107
|
+
|
|
108
|
+
## Modern patterns and their owners
|
|
109
|
+
|
|
110
|
+
- **Overview.** A header with the period control, one figure strip (three to
|
|
111
|
+
five figures with deltas or sparklines), one chart answering the main
|
|
112
|
+
question, and a "needs attention" list that links to records. Not a wall of
|
|
113
|
+
identical cards.
|
|
114
|
+
- **Index.** Search plus the work queues users actually work; rows lead with
|
|
115
|
+
identity and the deciding column; bulk actions appear with a selection;
|
|
116
|
+
export states how many rows match.
|
|
117
|
+
- **Record.** The page header carries identity, status badges in `titleSuffix`,
|
|
118
|
+
one metadata line (date · channel · customer) and the primary command. The
|
|
119
|
+
main column holds the subject's working content, an aside holds context
|
|
120
|
+
(customer, payment, notes) and history comes last.
|
|
121
|
+
- **Editing.** `InlineEdit` for one field; `ActionDrawer` for a focused
|
|
122
|
+
multi-field edit that keeps the record visible; `ActionDialog` for short
|
|
123
|
+
creation; `ConfirmDialog` for destructive commands.
|
|
124
|
+
- **Progressive disclosure.** `Collapsible` or `Accordion` for evidence and
|
|
125
|
+
technical detail; overflow menus for rare commands.
|
|
126
|
+
- **Loading.** Owners' loading states and `Skeleton` shaped like the content,
|
|
127
|
+
not spinners in empty boxes.
|
|
128
|
+
|
|
129
|
+
Inspect each owner's declaration before using it; a pattern names a direction,
|
|
130
|
+
not a prop list.
|
|
131
|
+
|
|
132
|
+
## Anti-patterns seen in trials
|
|
133
|
+
|
|
134
|
+
- A grid of identical metric cards whose figures have no baseline.
|
|
135
|
+
- A facts card that repeats the header's identifier, status and date.
|
|
136
|
+
- Every lifecycle counter on every line, zeros included.
|
|
137
|
+
- Column headers repeated in cells; units on every value.
|
|
138
|
+
- Filler cards added to balance a column's whitespace.
|
|
139
|
+
- Decorative icons on titles, rows or badges; tinted icon tiles.
|
|
140
|
+
- Status tones on kinds or categories; three badges on one row saying related
|
|
141
|
+
things.
|
|
142
|
+
- A storefront hero image inside an operational editor.
|
|
143
|
+
- A framed box inside a card for sub-content that is not a distinct object.
|
|
144
|
+
- A chart of three values, or a chart whose question cannot be stated.
|
|
145
|
+
- Prose explaining what the screen does.
|
|
146
|
+
|
|
147
|
+
## When an owner limits the view
|
|
148
|
+
|
|
149
|
+
When an owner cannot express the hierarchy the data needs, compose its public
|
|
150
|
+
parts or app markup with token-backed utilities
|
|
151
|
+
([domain structures](domain-structures.md)) and report the gap by owner and
|
|
152
|
+
prop. Never override anatomy classes, add colours or add tokens to make a view
|
|
153
|
+
look designed.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# View spec
|
|
2
|
+
|
|
3
|
+
Write this before JSX for a new view or a substantial redesign. Keep it short
|
|
4
|
+
enough to read in a minute; it is a working note, not an approval document. Every
|
|
5
|
+
line should change a later decision. Save it beside the app's other design notes
|
|
6
|
+
so a reviewer can compare the result with the intent.
|
|
7
|
+
|
|
8
|
+
```md
|
|
9
|
+
# <View name> (<route>)
|
|
10
|
+
|
|
11
|
+
## Decision
|
|
12
|
+
<who> decides <what>, <how often>. Success: <what they can do faster or with more confidence>.
|
|
13
|
+
|
|
14
|
+
## Data brief
|
|
15
|
+
<the data brief from data-reading.md>
|
|
16
|
+
|
|
17
|
+
## Ranked information
|
|
18
|
+
- Must (first viewport at 390 and 1440): …
|
|
19
|
+
- Should (visible without interaction): …
|
|
20
|
+
- Could (behind disclosure, overflow or a detail view): …
|
|
21
|
+
- Omit: … (why: zero, not applicable, duplicated elsewhere, not used for this decision)
|
|
22
|
+
|
|
23
|
+
## Focal point
|
|
24
|
+
<the one element the eye reaches first, and why>
|
|
25
|
+
|
|
26
|
+
## Wireframes
|
|
27
|
+
1440:
|
|
28
|
+
┌──────────────────────────────────────────────┐
|
|
29
|
+
│ Title · status [secondary] [Primary]│
|
|
30
|
+
│ meta · meta · meta │
|
|
31
|
+
├──────────────────────────────┬───────────────┤
|
|
32
|
+
│ main subject │ context │
|
|
33
|
+
│ … │ … │
|
|
34
|
+
└──────────────────────────────┴───────────────┘
|
|
35
|
+
|
|
36
|
+
390:
|
|
37
|
+
┌────────────────────┐
|
|
38
|
+
│ Title · status │
|
|
39
|
+
│ [Primary] [⋯] │
|
|
40
|
+
│ main subject │
|
|
41
|
+
│ context │
|
|
42
|
+
└────────────────────┘
|
|
43
|
+
|
|
44
|
+
## Encodings
|
|
45
|
+
| Fact | Encoding | Why |
|
|
46
|
+
| --- | --- | --- |
|
|
47
|
+
|
|
48
|
+
## Owners
|
|
49
|
+
| Region | Owner | Exact import | Verified with |
|
|
50
|
+
| --- | --- | --- | --- |
|
|
51
|
+
(found with find-components.mjs and map-composition.mjs; props checked with inspect-component.mjs)
|
|
52
|
+
|
|
53
|
+
## Commands
|
|
54
|
+
<command: scope, emphasis (primary, secondary, overflow), eligibility and disabled reason>
|
|
55
|
+
|
|
56
|
+
## States
|
|
57
|
+
Loading: … Empty: … Error and retry: … Accepted: … Long text and narrow parent: …
|
|
58
|
+
|
|
59
|
+
## Open questions and data gaps
|
|
60
|
+
…
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Notes
|
|
64
|
+
|
|
65
|
+
- Draw wireframes in text so they survive any editor and diff. Boxes show
|
|
66
|
+
regions and order, not pixel sizes.
|
|
67
|
+
- The ranked list is where most quality comes from. If "must" has more than
|
|
68
|
+
five items, the decision is not specific enough yet.
|
|
69
|
+
- Fill "Owners" only after running the finder and inspector. An owner that does
|
|
70
|
+
not fit is recorded with the reason, and the region becomes app markup over
|
|
71
|
+
public parts.
|
|
72
|
+
- After building, score the result with the [critique rubric](critique-rubric.md)
|
|
73
|
+
and note any departure from this spec with its reason.
|
|
@@ -122,8 +122,15 @@ definitions; the app then chooses appropriate destination focus.
|
|
|
122
122
|
Use `Page` from `themelia-react-ui/layout/page` for the record route's identity,
|
|
123
123
|
back navigation, content bounds, commands and section tabs. Pass the record's
|
|
124
124
|
main command as `header.primaryAction`, supporting commands as
|
|
125
|
-
`header.secondaryActions` and sections as `header.tabs`;
|
|
126
|
-
|
|
125
|
+
`header.secondaryActions` and separately selected/routed sections as `header.tabs`;
|
|
126
|
+
continuous sections can stay in the body. One status badge goes in
|
|
127
|
+
`header.slots.titleSuffix` as a composed Badge. Place supporting facts in
|
|
128
|
+
`header.slots.afterDescription` as a MetadataList when useful. The four header
|
|
129
|
+
slots (`beforeTitle`, `titleSuffix`, `afterDescription`, `actions`) accept content
|
|
130
|
+
or a callback receiving `{ renderLink, defaultContent }`; an actions callback
|
|
131
|
+
can wrap or return `defaultContent` to retain responsive commands. A replacement
|
|
132
|
+
owns its behavior. Below 40rem of actual header width, all commands, including
|
|
133
|
+
primary, share one More actions trigger. `PageHeader` serves a custom shell; `PageActions` is
|
|
127
134
|
deprecated. The [layout recipe](../examples/app-layout.tsx) demonstrates that
|
|
128
135
|
route boundary; the [detail recipe](../examples/detail.tsx) demonstrates the
|
|
129
136
|
record route and an independently framed subject with MetadataList and value
|
|
@@ -184,6 +184,25 @@ export function analyzeComposition(text, { filename, index, ts }) {
|
|
|
184
184
|
if (ts.isJsxElement(element.parent)) element.parent.children.forEach(collect);
|
|
185
185
|
return found;
|
|
186
186
|
};
|
|
187
|
+
// Static region children only: inspect known passive wrappers and providers,
|
|
188
|
+
// never assume an opaque app component renders its lexical children.
|
|
189
|
+
const regionElements = (opening, deep = false) => {
|
|
190
|
+
const found = [];
|
|
191
|
+
const collect = (child, scope = opening, subject) => {
|
|
192
|
+
if (ts.isJsxFragment(child)) { child.children.forEach(item => collect(item, scope, subject)); return; }
|
|
193
|
+
if (ts.isJsxExpression(child)) { const value = resolve(child.expression); if (value) collect(value, scope, subject); return; }
|
|
194
|
+
const node = ts.isJsxElement(child) ? child.openingElement : ts.isJsxSelfClosingElement(child) ? child : undefined;
|
|
195
|
+
if (!node) return;
|
|
196
|
+
const owner = ownerFor(node.tagName);
|
|
197
|
+
found.push({node, owner, tag: node.tagName.getText(source), scope, subject});
|
|
198
|
+
const provider = isOwner(owner, 'features/filters', ['FilterProvider']);
|
|
199
|
+
const card = isOwner(owner, 'base/cards', ['Card', 'SmartCard']);
|
|
200
|
+
// Cards delimit independent subjects; providers delimit query state.
|
|
201
|
+
if (deep && ts.isJsxElement(child) && (/^(div|section|form)$/.test(node.tagName.getText(source)) || isOwner(owner, 'base/structure', ['Stack', 'Grid', 'AdaptiveGrid', 'GridCell']) || card || provider)) child.children.forEach(item => collect(item, provider ? node : scope, card ? node : subject));
|
|
202
|
+
};
|
|
203
|
+
if (ts.isJsxElement(opening.parent)) opening.parent.children.forEach(child => collect(child));
|
|
204
|
+
return found;
|
|
205
|
+
};
|
|
187
206
|
const renderedText = element => ts.isJsxElement(element) ? element.children.map(child => ts.isJsxText(child) ? child.text : ts.isJsxExpression(child) ? literal(child.expression) ?? '' : '').join(' ').trim() : '';
|
|
188
207
|
const slotBadge = expression => {
|
|
189
208
|
const node = resolve(expression);
|
|
@@ -342,7 +361,7 @@ export function analyzeComposition(text, { filename, index, ts }) {
|
|
|
342
361
|
if (modalRole || scrim) add(node, 'raw-overlay-primitive', 'warning', `A hand-rolled ${modalRole ? 'dialog' : 'full-screen layer'} must own focus, dismissal, pending state and theming itself. ${overlayRemedy}`);
|
|
343
362
|
if (/^(?:input|textarea)$/.test(node.tagName.getText(source))) {
|
|
344
363
|
const props = new Map(attributes(node));
|
|
345
|
-
if (props.has('className') && classStrings(props.get('className')).flatMap(value=>value.split(/\s+/)).some(value=>replacesSharedRadius(value,'
|
|
364
|
+
if (props.has('className') && classStrings(props.get('className')).flatMap(value=>value.split(/\s+/)).some(value=>replacesSharedRadius(value,'radius'))) add(node,'shared-radius','error','Native editable controls follow bounded rounded-radius; change the global theme root, not per-field corners.');
|
|
346
365
|
if (props.has('style') && properties(props.get('style')).some(([name])=>radiusProperty.test(name))) add(node,'shared-radius','error','Native editable corners belong to the global radius root, not inline styles.');
|
|
347
366
|
}
|
|
348
367
|
if (nativeDisplayNames.test(node.tagName.getText(source))) {
|
|
@@ -375,7 +394,7 @@ export function analyzeComposition(text, { filename, index, ts }) {
|
|
|
375
394
|
const focusDrift = focusRingDrift(tokens);
|
|
376
395
|
if ((anatomy || forwarder && name === 'contentClassName') && classes.some(value => paddingClass.test(value))) add(node, 'surface-padding', 'error', `${owner.symbol}: use inset/type/density and public regions; padding overrides replace owned anatomy.`);
|
|
377
396
|
if (control && name === 'className' && classes.some(value => fixedControlClass.test(value))) add(node, 'control-anatomy', 'error', `${owner.symbol}: use its size/variant props; preserve shared control height, inset, corners and typography.`);
|
|
378
|
-
if ((name === 'className' && (control || sharedCorners) || name === 'contentClassName' && forwarder) && classes.some(value=>replacesSharedRadius(value, control && name === 'className' ? '
|
|
397
|
+
if ((name === 'className' && (control || sharedCorners) || name === 'contentClassName' && forwarder) && classes.some(value=>replacesSharedRadius(value, control && name === 'className' ? 'radius' : cornerRole(family,owner.symbol)))) add(node,'shared-radius','error',`${owner.symbol}: outer shells use rounded-surface; controls and collection rows use bounded rounded-radius. Preserve the owning role and joined-edge resets.`);
|
|
379
398
|
if (display && classes.some(addsCopyMargin)) add(node,'copy-margin','error',`${owner.symbol}: typography is margin-free; use textGap on the headed owner or gap on Stack/Grid.`);
|
|
380
399
|
if (textContent && name === 'className' && tokens.some(token=>fadedText(token)||textOpacityDrift(token))) add(node,'faded-text','error',`${owner.symbol}: use its semantic type instead of adding an opacity tier to the text. Presence endpoints and shared disabled state remain valid.`);
|
|
381
400
|
if ((control || family === 'buttons' && owner.symbol === 'TextButton') && name === 'className' && focusDrift.length) add(node, 'focus-ring-recipe', 'error', `${owner.symbol}: keep the shared focus indicator (a ring-3 halo of ring-ring/50 around a full-strength ring edge); ${[...new Set(focusDrift)].join(', ')} redraws it.`);
|
|
@@ -404,6 +423,23 @@ export function analyzeComposition(text, { filename, index, ts }) {
|
|
|
404
423
|
}
|
|
405
424
|
if (isOwner(owner, 'base/feedback', ['Empty'])) add(node, 'reinvented-empty-state', 'warning', 'Empty exposes lower-level anatomy. Compare EmptyState from blocks/data-display for a routine no-data/no-results region; keep Empty when custom anatomy is required.');
|
|
406
425
|
if (isOwner(owner, 'base/sheet', ['Sheet', 'SheetContent']) || isOwner(owner, 'base/display', ['Dialog', 'DialogContent', 'AlertDialog', 'AlertDialogContent'])) add(node, 'raw-overlay-primitive', 'warning', 'This lower-level modal needs application lifecycle coordination. Prefer ActionDialog, ConfirmDialog or ActionDrawer from features/overlays when their workflow fits.');
|
|
426
|
+
if (isOwner(owner, 'features/table', ['DataTable'])) {
|
|
427
|
+
const parent = parents.at(-1);
|
|
428
|
+
let ancestor = ts.isJsxOpeningElement(node) ? node.parent.parent : node.parent;
|
|
429
|
+
while (ancestor && !ts.isJsxElement(ancestor)) ancestor = ancestor.parent;
|
|
430
|
+
const directOwner = ancestor && ownerFor(ancestor.openingElement.tagName);
|
|
431
|
+
if (isOwner(directOwner, 'base/cards', ['Card', 'SmartCard']) && isOwner(parent?.owner, 'base/cards', ['Card', 'SmartCard']) && literal(parent.props.get('padding')) !== 'none' && !parent.unknownProps) add(node, 'uncontained-table-in-card', 'warning', 'DataTable owns its surface, topbar and edge-to-edge header band. Render it directly in Page or a layout region; a deliberate Card host must use padding="none".');
|
|
432
|
+
}
|
|
433
|
+
const routeOwner = isOwner(owner, 'layout/page', ['Page']) || isOwner(owner, 'layout', ['Page']);
|
|
434
|
+
if (routeOwner) {
|
|
435
|
+
const children = regionElements(node);
|
|
436
|
+
const contents = regionElements(node, true);
|
|
437
|
+
const tables = contents.filter(child => isOwner(child.owner, 'features/table', ['DataTable']));
|
|
438
|
+
for (const child of contents) if (isOwner(child.owner, 'features/filters', ['FilterLayout']) && tables.some(table => table.scope === child.scope && (!child.subject || table.subject === child.subject))) add(child.node, 'detached-filter-layout', 'warning', 'Pass FilterLayout into DataTable topbarContent with variant="compact", or use DataView so filters and table chrome share one surface.');
|
|
439
|
+
const hasAsideSlot = properties(props.get('slots'), false).some(([name]) => name === 'aside');
|
|
440
|
+
const onlyForms = children.length > 0 && children.every(child => child.tag === 'form' || isOwner(child.owner, 'base/forms', ['FormSection']));
|
|
441
|
+
if (onlyForms && !unknownProps && !['narrow', 'maxWidth', 'aside'].some(name => props.has(name)) && !hasAsideSlot) add(node, 'wide-single-column-form', 'warning', 'A single-column form should use Page narrow (48rem centered), an explicit maxWidth, or a supporting aside so fields do not stretch across the default route measure.');
|
|
442
|
+
}
|
|
407
443
|
if (owner.symbol === 'Page' && literal(props.get('gutter')) === 'none' && !parents.some(parent => parent.framed)) add(node, 'page-gutter', 'warning', 'Page gutter="none" delegates inline padding to a real parent. PageViewport owns scrolling, not padding; verify the parent inset.');
|
|
408
444
|
if (owner.symbol === 'Card' && framed && literal(props.get('padding')) === 'sm' && !parents.some(parent => parent.framed)) add(node, 'compact-card', 'warning', 'Standalone Card padding="sm" halves the normal inset. Omit padding for ordinary subjects; retain sm only for an intentional compact composition.');
|
|
409
445
|
const peers = parents.findLast(parent => parent.structure);
|
|
@@ -432,7 +468,7 @@ export function analyzeComposition(text, { filename, index, ts }) {
|
|
|
432
468
|
const title = literal(props.get('title')) ?? literal(new Map(header).get('title'));
|
|
433
469
|
if (title && parents.some(parent => parent.title === title)) add(node, 'duplicate-heading', 'warning', `The heading "${title}" repeats its ancestor. Keep one route/subject title unless a distinct task warrants repetition.`);
|
|
434
470
|
const structure = owner.import === `${index.package}/base/structure` && Object.hasOwn(structureDefaults, owner.symbol);
|
|
435
|
-
return { framed, title, structure, cards: structure ? peerCards(node) : new Set(), smallGap: structure ? smallGap(props, owner.symbol, unknownProps) : false };
|
|
471
|
+
return { owner, props, unknownProps, framed, title, structure, cards: structure ? peerCards(node) : new Set(), smallGap: structure ? smallGap(props, owner.symbol, unknownProps) : false };
|
|
436
472
|
}
|
|
437
473
|
visit(source);
|
|
438
474
|
return { findings, unresolved };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const record = value => value !== null && typeof value === 'object' && !Array.isArray(value) && [Object.prototype, null].includes(Object.getPrototypeOf(value));
|
|
2
|
+
const fail = (condition, message) => { if (!condition) throw new Error(`Invalid composition mapping: ${message}`); };
|
|
3
|
+
const name = value => typeof value === 'string' && /^[A-Za-z][A-Za-z0-9]*$/.test(value);
|
|
4
|
+
const uniqueList = (value, valid, maximum) => Array.isArray(value) && value.length <= maximum && value.every(valid) && new Set(value).size === value.length;
|
|
5
|
+
const scalar = value => value === null || typeof value === 'boolean' || (typeof value === 'string' && value.length <= 500) || (typeof value === 'number' && Number.isFinite(value));
|
|
6
|
+
const kinds = ['shell', 'page', 'layout', 'surface', 'content', 'workflow'];
|
|
7
|
+
const placements = ['region', 'toolbar', 'cell', 'overlay'];
|
|
8
|
+
const surfaces = ['none', 'parent', 'self', 'variant'];
|
|
9
|
+
|
|
10
|
+
/** One literal metadata shape shared by authored catalogue and copied tools. */
|
|
11
|
+
export function validateMappings(mappings) {
|
|
12
|
+
fail(Array.isArray(mappings) && mappings.length <= 64, 'expected an array of at most 64 mappings');
|
|
13
|
+
for (const mapping of mappings) {
|
|
14
|
+
fail(record(mapping), 'expected a plain mapping object');
|
|
15
|
+
fail(Object.keys(mapping).every(key => ['symbol', 'roles', 'kind', 'placement', 'surface', 'slots', 'props', 'routeSlot'].includes(key)), 'unknown mapping field');
|
|
16
|
+
fail(typeof mapping.symbol === 'string' && /^[A-Z][A-Za-z0-9]*$/.test(mapping.symbol), 'symbol must be an exact public component name');
|
|
17
|
+
fail(uniqueList(mapping.roles, value => typeof value === 'string' && /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(value), 8) && mapping.roles.length > 0, `${mapping.symbol}: use 1..8 distinct kebab-case roles`);
|
|
18
|
+
fail(kinds.includes(mapping.kind), `${mapping.symbol}: invalid kind`);
|
|
19
|
+
fail(placements.includes(mapping.placement), `${mapping.symbol}: invalid placement`);
|
|
20
|
+
fail(surfaces.includes(mapping.surface), `${mapping.symbol}: invalid surface`);
|
|
21
|
+
if (mapping.slots !== undefined) fail(uniqueList(mapping.slots, name, 16), `${mapping.symbol}: invalid or duplicate slots`);
|
|
22
|
+
if (mapping.routeSlot !== undefined) fail(mapping.kind === 'shell' && name(mapping.routeSlot) && (mapping.slots ?? []).includes(mapping.routeSlot), `${mapping.symbol}: routeSlot must name a mapped shell insertion slot`);
|
|
23
|
+
if (mapping.props !== undefined) {
|
|
24
|
+
fail(record(mapping.props) && Object.keys(mapping.props).length <= 16, `${mapping.symbol}: props must be a small literal object`);
|
|
25
|
+
for (const [key, value] of Object.entries(mapping.props)) {
|
|
26
|
+
fail(/^[A-Za-z][\w-]*$/.test(key) && !['__proto__', 'constructor', 'prototype'].includes(key) && scalar(value), `${mapping.symbol}: invalid literal prop ${key}`);
|
|
27
|
+
fail(!(mapping.slots ?? []).includes(key), `${mapping.symbol}: ${key} cannot be both an insertion slot and a fixed prop`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return mappings;
|
|
32
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import {validateIndex} from './discovery.mjs';
|
|
2
|
+
import {validateComponentContracts} from './inspection.mjs';
|
|
3
|
+
import {validateMappings} from './composition-schema.mjs';
|
|
4
|
+
|
|
5
|
+
const fail = (condition, message) => { if (!condition) throw new Error(message); };
|
|
6
|
+
const compare = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
7
|
+
const unique = values => [...new Set(values)].sort(compare);
|
|
8
|
+
const patterns = ['record-detail', 'collection', 'edit', 'analytics', 'embedded'];
|
|
9
|
+
const roleKey = value => value.trim().toLowerCase().replace(/\s+/g, '-');
|
|
10
|
+
const identity = mapping => `${mapping.import}#${mapping.symbol}`;
|
|
11
|
+
const stopWords = new Set(['and', 'for', 'the', 'with', 'from', 'into', 'show', 'list', 'page']);
|
|
12
|
+
const roleTokens = value => value.toLowerCase().split(/[^a-z0-9]+/).filter(token => token.length > 2 && !stopWords.has(token)).map(token => token.length > 3 && token.endsWith('s') ? token.slice(0, -1) : token);
|
|
13
|
+
|
|
14
|
+
/** Nearby mapped roles for a phrase that matched none. Hints only: the need stays unresolved. */
|
|
15
|
+
function suggestRoles(mappings, request, limit = 5) {
|
|
16
|
+
if (/^[A-Z][A-Za-z0-9]*$/.test(request) || /[\/#]/.test(request)) return [];
|
|
17
|
+
const wanted = new Set(roleTokens(request));
|
|
18
|
+
return unique(mappings.flatMap(mapping => mapping.roles))
|
|
19
|
+
.map(role => ({role, shared: roleTokens(role).filter(token => wanted.has(token)).length}))
|
|
20
|
+
.filter(candidate => candidate.shared > 0)
|
|
21
|
+
.sort((a, b) => b.shared - a.shared || a.role.length - b.role.length || compare(a.role, b.role))
|
|
22
|
+
.slice(0, limit).map(candidate => candidate.role);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function publicProps(contract, contracts) {
|
|
26
|
+
const inherited = contracts.nativeProfiles[contract.inheritedProfile] ?? {};
|
|
27
|
+
return Object.assign(Object.create(null), Object.fromEntries(Object.entries(inherited).map(([key, value]) => [key, typeof value === 'string' ? contracts.nativeProperties[value] : value])), contract.props);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Resolve authored rows against the same qualified contracts as API inspection. */
|
|
31
|
+
export function validateCompositionMappings(index, contracts) {
|
|
32
|
+
validateIndex(index);
|
|
33
|
+
validateComponentContracts(contracts, index);
|
|
34
|
+
const byContract = new Map(contracts.contracts.map(contract => [identity(contract), contract]));
|
|
35
|
+
const seen = new Set();
|
|
36
|
+
const mappings = [];
|
|
37
|
+
for (const family of index.families) for (const guidance of family.guidance) {
|
|
38
|
+
for (const mapping of validateMappings(guidance.composition?.mappings ?? [])) {
|
|
39
|
+
const key = `${family.import}#${mapping.symbol}`;
|
|
40
|
+
fail(!seen.has(key), `Duplicate composition mapping: ${key}`); seen.add(key);
|
|
41
|
+
fail(family.symbols.includes(mapping.symbol), `Composition mapping is not a public export: ${key}`);
|
|
42
|
+
fail(!Object.hasOwn(family.deprecated ?? {}, mapping.symbol), `Composition mapping uses a deprecated export: ${key}`);
|
|
43
|
+
const contract = byContract.get(key);
|
|
44
|
+
fail(contract, `Composition mapping has no callable component contract: ${key}`);
|
|
45
|
+
const props = publicProps(contract, contracts);
|
|
46
|
+
for (const slot of mapping.slots ?? []) {
|
|
47
|
+
fail(props[slot], `Unknown composition slot ${slot} on ${key}`);
|
|
48
|
+
fail(/\b(?:ReactNode|ReactElement)\b|JSX\.Element/.test(props[slot].type), `Composition slot ${slot} on ${key} is not a documented React insertion prop`);
|
|
49
|
+
}
|
|
50
|
+
for (const prop of Object.keys(mapping.props ?? {})) fail(props[prop], `Unknown composition prop ${prop} on ${key}`);
|
|
51
|
+
const recipes = (family.recipes ?? []).filter(recipe => recipe.symbols.includes(mapping.symbol));
|
|
52
|
+
mappings.push({...mapping, import: family.import, declaration: family.declaration, optionalPeers: family.optionalPeers,
|
|
53
|
+
guidance, nextReads: {declaration: family.declaration,
|
|
54
|
+
inspect: `node scripts/inspect-component.mjs ${mapping.symbol} --import ${family.import}`,
|
|
55
|
+
references: unique([...(family.references ?? []), ...recipes.flatMap(recipe => recipe.references)]),
|
|
56
|
+
examples: recipes.map(({file, exports, optionalPeers = []}) => ({file, exports, optionalPeers})),
|
|
57
|
+
}});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return mappings.sort((a, b) => compare(identity(a), identity(b)));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function validateOptions(options) {
|
|
64
|
+
const value = {...options, main: options.main ?? [], aside: options.aside ?? [], limit: options.limit ?? 3};
|
|
65
|
+
fail(Number.isInteger(value.limit) && value.limit >= 1 && value.limit <= 10, 'Invalid limit: use 1..10');
|
|
66
|
+
fail(Number(value.roles === true) + Number(value.role !== undefined) + Number(value.pattern !== undefined) === 1, 'Choose one mode: --roles, --role or --pattern');
|
|
67
|
+
for (const region of ['main', 'aside']) fail(Array.isArray(value[region]) && value[region].length <= 24 && value[region].every(need => typeof need === 'string' && need.trim().length > 0 && need.length <= 300), `Invalid ${region} needs; use up to 24 explicit role tags or qualified selectors`);
|
|
68
|
+
if (value.pattern !== undefined) {
|
|
69
|
+
fail(patterns.includes(value.pattern), `Invalid pattern: choose ${patterns.join(', ')}`);
|
|
70
|
+
fail(value.main.length + value.aside.length > 0, 'A plan needs at least one --main or --aside requirement');
|
|
71
|
+
} else {
|
|
72
|
+
fail(value.main.length + value.aside.length === 0, 'Main/aside needs require --pattern');
|
|
73
|
+
if (value.role !== undefined) fail(typeof value.role === 'string' && value.role.trim().length > 0, 'A nonempty role is required');
|
|
74
|
+
}
|
|
75
|
+
if (value.parent !== undefined) fail(value.pattern === 'embedded' && typeof value.parent === 'string' && /^[A-Z][A-Za-z0-9]*$/.test(value.parent), '--parent must be an exact component name with --pattern embedded');
|
|
76
|
+
if (value.publicImport !== undefined) fail(value.parent !== undefined && typeof value.publicImport === 'string' && value.publicImport.length > 0, '--import qualifies an explicit embedded --parent');
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function parseCompositionArguments(args) {
|
|
81
|
+
const options = {main: [], aside: [], limit: 3};
|
|
82
|
+
const seen = new Set();
|
|
83
|
+
for (let index = 0; index < args.length; index++) {
|
|
84
|
+
const arg = args[index];
|
|
85
|
+
fail(['--roles', '--role', '--pattern', '--main', '--aside', '--parent', '--import', '--limit'].includes(arg), `Unknown argument: ${arg}`);
|
|
86
|
+
fail(['--main', '--aside'].includes(arg) || !seen.has(arg), `Duplicate argument: ${arg}`); seen.add(arg);
|
|
87
|
+
if (arg === '--roles') { options.roles = true; continue; }
|
|
88
|
+
const value = args[++index];
|
|
89
|
+
fail(typeof value === 'string' && value.trim() && !value.startsWith('--'), `Missing value for ${arg}`);
|
|
90
|
+
if (arg === '--main' || arg === '--aside') options[arg.slice(2)].push(value.trim());
|
|
91
|
+
else if (arg === '--limit') { fail(/^[1-9]\d*$/.test(value), 'Invalid limit: use 1..10'); options.limit = Number(value); }
|
|
92
|
+
else options[arg === '--import' ? 'publicImport' : arg.slice(2)] = value.trim();
|
|
93
|
+
}
|
|
94
|
+
validateOptions(options);
|
|
95
|
+
return options;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function candidate(mapping) {
|
|
99
|
+
return {import: mapping.import, symbol: mapping.symbol, roles: mapping.roles, kind: mapping.kind,
|
|
100
|
+
placement: mapping.placement, surface: mapping.surface, slots: mapping.slots ?? [], props: mapping.props ?? {},
|
|
101
|
+
...(mapping.routeSlot ? {routeSlot: mapping.routeSlot} : {}),
|
|
102
|
+
optionalPeers: mapping.optionalPeers, applicationOwns: mapping.guidance.appOwned,
|
|
103
|
+
chooseWhen: mapping.guidance.chooseWhen, avoidWhen: mapping.guidance.avoidWhen, nextReads: mapping.nextReads};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Exact roles and explicit regions yield an advisory structure, never guessed APIs. */
|
|
107
|
+
export function mapComposition(index, contracts, rawOptions) {
|
|
108
|
+
const options = validateOptions(rawOptions);
|
|
109
|
+
const mappings = validateCompositionMappings(index, contracts);
|
|
110
|
+
const base = {package: index.package, version: index.version, advisory: true,
|
|
111
|
+
baselinePeers: unique((index.peers ?? []).filter(peer => !peer.optional).map(peer => peer.name)),
|
|
112
|
+
peerScope: 'requiredPeers covers selected-family optional peers; baselinePeers apply to every consumer'};
|
|
113
|
+
const matches = request => request.includes('#')
|
|
114
|
+
? mappings.filter(mapping => identity(mapping) === request)
|
|
115
|
+
: /^[A-Z][A-Za-z0-9]*$/.test(request) || /[\/#]/.test(request) ? []
|
|
116
|
+
: mappings.filter(mapping => mapping.roles.includes(roleKey(request)));
|
|
117
|
+
if (options.roles) {
|
|
118
|
+
const roles = unique(mappings.flatMap(mapping => mapping.roles)).map(role => ({role, owners: mappings.filter(mapping => mapping.roles.includes(role)).length}));
|
|
119
|
+
return {...base, mode: 'roles', total: roles.length, roles};
|
|
120
|
+
}
|
|
121
|
+
if (options.role !== undefined) {
|
|
122
|
+
const found = matches(options.role);
|
|
123
|
+
const suggestions = found.length ? [] : suggestRoles(mappings, options.role);
|
|
124
|
+
return {...base, mode: 'lookup', status: found.length ? 'mapped' : 'unresolved', query: options.role, total: found.length,
|
|
125
|
+
results: found.slice(0, options.limit).map(candidate), omittedResults: Math.max(0, found.length - options.limit),
|
|
126
|
+
...(suggestions.length ? {suggestedRoles: suggestions} : {})};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const unresolved = [], warnings = [], selected = [];
|
|
130
|
+
const node = (mapping, decision = 'none') => {
|
|
131
|
+
selected.push(mapping);
|
|
132
|
+
return {import: mapping.import, symbol: mapping.symbol, kind: mapping.kind, surface: mapping.surface,
|
|
133
|
+
surfaceDecision: decision, props: {...mapping.props}, slots: {}};
|
|
134
|
+
};
|
|
135
|
+
const issue = (request, region, reason, found = [], suggest = false) => {
|
|
136
|
+
// Only a phrase that matched no role gets hints; structural failures of a
|
|
137
|
+
// mapped role already name their candidates.
|
|
138
|
+
const suggestions = suggest ? suggestRoles(mappings, request) : [];
|
|
139
|
+
unresolved.push({request, region, reason, totalCandidates: found.length, candidates: found.slice(0, options.limit).map(candidate), ...(suggestions.length ? {suggestedRoles: suggestions} : {})});
|
|
140
|
+
};
|
|
141
|
+
const resolve = (request, region, found = matches(request)) => {
|
|
142
|
+
if (found.length !== 1) { issue(request, region, found.length ? 'Role is ambiguous; select an exact import#symbol or a more specific mapped role.' : 'No exact mapping covers this requirement. Retry with one of suggestedRoles when it names the same job, or use --roles or the component finder; do not infer an API.', found, !found.length); return null; }
|
|
143
|
+
return found[0];
|
|
144
|
+
};
|
|
145
|
+
const insertion = (mapping, slot, request, region) => {
|
|
146
|
+
if (!(mapping.slots ?? []).includes(slot)) { issue(request, region, `${mapping.symbol} has no mapped ${slot} insertion slot. Inspect its API rather than nesting into an invented prop.`, [mapping]); return false; }
|
|
147
|
+
return true;
|
|
148
|
+
};
|
|
149
|
+
const structure = (role, kind, slots, region = 'structure') => {
|
|
150
|
+
const mapping = resolve(role, region);
|
|
151
|
+
if (!mapping) return null;
|
|
152
|
+
if (mapping.kind !== kind || mapping.placement !== 'region' || (kind === 'surface' && mapping.surface !== 'self')) {
|
|
153
|
+
issue(role, region, `The ${role} mapping does not establish the required ${kind} boundary. Inspect the authored mapping.`, [mapping]); return null;
|
|
154
|
+
}
|
|
155
|
+
if (!slots.every(slot => insertion(mapping, slot, role, region))) return null;
|
|
156
|
+
return mapping;
|
|
157
|
+
};
|
|
158
|
+
let rootMapping = null, root = null, host = null, mainSlot = 'main', asideSlot = 'aside';
|
|
159
|
+
if (options.pattern === 'embedded') {
|
|
160
|
+
if (options.parent) {
|
|
161
|
+
rootMapping = resolve(options.parent, 'structure', mappings.filter(mapping => mapping.symbol === options.parent && (!options.publicImport || mapping.import === options.publicImport)));
|
|
162
|
+
if (rootMapping && rootMapping.placement !== 'region') { issue(options.parent, 'structure', 'This parent belongs to a renderer or overlay boundary, not a subject region.', [rootMapping]); rootMapping = null; }
|
|
163
|
+
if (rootMapping) {
|
|
164
|
+
root = host = node(rootMapping, rootMapping.surface === 'self' ? 'existing-surface' : rootMapping.surface === 'variant' ? 'inspect-variant-host' : 'existing-host');
|
|
165
|
+
mainSlot = (rootMapping.slots ?? []).includes('children') ? 'children' : 'main';
|
|
166
|
+
if (rootMapping.surface === 'variant') warnings.push({code: 'variant-host-surface', message: `${rootMapping.symbol} may already provide framing in its current variant or state. Inspect its contract and existing ancestors before composing another surface.`});
|
|
167
|
+
}
|
|
168
|
+
} else warnings.push({code: 'host-ownership-unverified', message: 'Embedded requirements retain the caller\'s host. Confirm its insertion slot, framing, padding and gaps; no Page or new surface was added.'});
|
|
169
|
+
} else {
|
|
170
|
+
rootMapping = structure('route', 'page', ['children']);
|
|
171
|
+
if (rootMapping) {
|
|
172
|
+
root = host = node(rootMapping);
|
|
173
|
+
if (!options.aside.length && ['edit', 'record-detail'].includes(options.pattern)) {
|
|
174
|
+
const contract = contracts.contracts.find(contract => identity(contract) === identity(rootMapping));
|
|
175
|
+
if (publicProps(contract, contracts).narrow) root.props.narrow = true;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
mainSlot = 'children';
|
|
179
|
+
if (rootMapping && options.aside.length && !(rootMapping.slots ?? []).includes('aside')) {
|
|
180
|
+
const layout = structure('main-support-layout', 'layout', ['main', 'aside']);
|
|
181
|
+
if (layout && insertion(rootMapping, 'children', 'main-support-layout', 'structure')) {
|
|
182
|
+
host = node(layout); root.slots.children = [host]; rootMapping = layout; mainSlot = 'main';
|
|
183
|
+
} else host = null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
let resolved = 0;
|
|
188
|
+
const regions = {main: [], aside: []};
|
|
189
|
+
const toolbars = [], placements = [], contextualized = new Set();
|
|
190
|
+
for (const region of ['main', 'aside']) for (const request of options[region]) {
|
|
191
|
+
const mapping = resolve(request, region);
|
|
192
|
+
if (!mapping) continue;
|
|
193
|
+
if (mapping.placement === 'toolbar') { toolbars.push({mapping, request, region}); continue; }
|
|
194
|
+
if (mapping.placement !== 'region') { issue(request, region, mapping.placement === 'cell' ? 'This owner belongs in a table/collection renderer, not directly in a subject region.' : 'This owner belongs at the app/feature overlay boundary; open it through callbacks.', [mapping]); continue; }
|
|
195
|
+
const slot = region === 'main' ? mainSlot : asideSlot;
|
|
196
|
+
if (['page', 'shell'].includes(mapping.kind) && !(options.pattern === 'embedded' && rootMapping?.routeSlot === slot && mapping.kind === 'page')) { issue(request, region, 'A page or shell is not an independent content subject. Only an explicit route-host slot may receive a Page; keep one route title/gutter owner.', [mapping]); continue; }
|
|
197
|
+
if ((!host && options.pattern !== 'embedded') || (!host && options.parent)) { issue(request, region, 'The structural owner is unresolved, so this requirement has not been placed.', [mapping]); continue; }
|
|
198
|
+
if (host && !insertion(rootMapping, slot, request, region)) continue;
|
|
199
|
+
let child;
|
|
200
|
+
const insideSurface = rootMapping?.surface === 'self';
|
|
201
|
+
if (mapping.surface === 'parent' && host && !insideSurface && options.pattern !== 'embedded') {
|
|
202
|
+
const frame = structure('subject-surface', 'surface', ['children'], region);
|
|
203
|
+
if (!frame) continue;
|
|
204
|
+
child = node(frame, 'recommended-frame');
|
|
205
|
+
child.slots.children = [node(mapping, 'uses-parent')];
|
|
206
|
+
} else {
|
|
207
|
+
child = node(mapping, mapping.surface === 'self' ? 'owns-surface' : mapping.surface === 'parent' ? insideSurface ? 'uses-parent' : 'caller-surface-unverified' : mapping.surface === 'variant' ? 'inspect-variant' : 'none');
|
|
208
|
+
}
|
|
209
|
+
if (mapping.surface === 'variant') warnings.push({code: 'variant-surface', request, region, message: `${mapping.symbol} owns framing only in some variants or states. Inspect its contract before adding a surface.`});
|
|
210
|
+
if (mapping.surface === 'parent' && options.pattern === 'embedded' && host && !insideSurface) warnings.push({code: 'host-surface-unverified', request, region, message: 'The explicit host does not establish a known surface boundary. Confirm its ancestors before adding framing; no extra Card was added.'});
|
|
211
|
+
if (insideSurface && mapping.surface === 'self') warnings.push({code: 'nested-surface-review', request, region, message: `${mapping.symbol} already owns a surface. Review its supported embedded variant or use an unframed host; no props were guessed.`});
|
|
212
|
+
else if (rootMapping?.surface === 'variant' && mapping.surface === 'self') warnings.push({code: 'nested-surface-review', request, region, message: `${mapping.symbol} owns a surface and its host may also frame it. Inspect both variants before keeping nested surfaces; no embedded props were guessed.`});
|
|
213
|
+
placements.push({mapping, child, region, slot});
|
|
214
|
+
regions[region].push(child);
|
|
215
|
+
if (host) (host.slots[slot] ??= []).push(child);
|
|
216
|
+
resolved++;
|
|
217
|
+
}
|
|
218
|
+
// Toolbars are placed after subjects so request order cannot affect the tree.
|
|
219
|
+
// FilterLayout's context and the table seam are both authored, validated APIs.
|
|
220
|
+
for (const {mapping, request, region} of toolbars) {
|
|
221
|
+
const targets = placements.filter(item => item.region === region && item.mapping.roles.includes('comparison-table') && (item.mapping.slots ?? []).includes('topbarContent'));
|
|
222
|
+
if (targets.length !== 1) { issue(request, region, 'A toolbar needs exactly one mapped comparison table with a topbarContent slot in this region.', targets.map(item => item.mapping)); continue; }
|
|
223
|
+
const target = targets[0];
|
|
224
|
+
let provider;
|
|
225
|
+
if (mapping.symbol === 'FilterLayout') {
|
|
226
|
+
const contexts = mappings.filter(row => row.import === mapping.import && row.symbol === 'FilterProvider' && row.roles.includes('filter-context'));
|
|
227
|
+
if (contexts.length !== 1 || contexts[0].kind !== 'layout' || contexts[0].placement !== 'region' || !(contexts[0].slots ?? []).includes('children')) {
|
|
228
|
+
issue(request, region, 'FilterLayout needs its mapped FilterProvider context with a children insertion contract.', contexts); continue;
|
|
229
|
+
}
|
|
230
|
+
provider = contexts[0];
|
|
231
|
+
}
|
|
232
|
+
const toolbar = node(mapping, 'uses-parent');
|
|
233
|
+
(target.child.slots.topbarContent ??= []).push(toolbar);
|
|
234
|
+
if (provider && !contextualized.has(target.child)) {
|
|
235
|
+
contextualized.add(target.child);
|
|
236
|
+
const wrapped = node(provider, 'context-provider');
|
|
237
|
+
wrapped.slots.children = [target.child];
|
|
238
|
+
const replace = children => { const position = children.indexOf(target.child); if (position >= 0) children[position] = wrapped; };
|
|
239
|
+
replace(regions[region]);
|
|
240
|
+
if (host) replace(host.slots[target.slot] ?? []);
|
|
241
|
+
// Keep subsequent toolbar insertion addressed to the table, not its context.
|
|
242
|
+
}
|
|
243
|
+
resolved++;
|
|
244
|
+
}
|
|
245
|
+
const requested = options.main.length + options.aside.length;
|
|
246
|
+
const selectedMappings = [...new Map(selected.map(mapping => [identity(mapping), mapping])).values()];
|
|
247
|
+
if (options.pattern === 'edit') warnings.push({code: 'native-form-required', message: 'Provide the application-owned native form, validation and awaited submit/save/discard lifecycle. This structural plan does not create form state or transport.'});
|
|
248
|
+
return {...base, mode: 'plan', status: unresolved.length ? resolved ? 'partial' : 'unresolved' : 'mapped', pattern: options.pattern, patternScope: 'structural-context-only',
|
|
249
|
+
tree: root, ...(root ? {} : {regions}), coverage: {requested, resolved, unresolved: requested - resolved}, unresolved,
|
|
250
|
+
requiredPeers: unique(selectedMappings.flatMap(mapping => mapping.optionalPeers)),
|
|
251
|
+
applicationOwns: unique(selectedMappings.flatMap(mapping => mapping.guidance.appOwned)), warnings,
|
|
252
|
+
nextReads: selectedMappings.map(mapping => ({import: mapping.import, symbol: mapping.symbol, ...mapping.nextReads})),
|
|
253
|
+
limits: 'This is an advisory role/slot map. Supply application data, state, callbacks and content; inspect APIs and execute visual/interaction checks before delivery.'};
|
|
254
|
+
}
|