themelia-react-ui 1.0.1 → 2.0.0
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 +417 -0
- package/README.md +67 -16
- package/dist/{accordion-B-5Focs8.js → accordion-CQXVcd7Q.js} +15 -15
- package/dist/accordion-CQXVcd7Q.js.map +1 -0
- package/dist/action-menu-BTpyPWJ2.js +264 -0
- package/dist/action-menu-BTpyPWJ2.js.map +1 -0
- package/dist/affordance-geometry.generated-CCIXb2A1.js +68 -0
- package/dist/affordance-geometry.generated-CCIXb2A1.js.map +1 -0
- package/dist/{ai-artifact-B5tPpXnS.js → ai-artifact-2PQ2fItc.js} +206 -215
- package/dist/ai-artifact-2PQ2fItc.js.map +1 -0
- package/dist/{app-shell-at8Kt4l_.js → app-shell-BE_j8-AT.js} +11 -10
- package/dist/app-shell-BE_j8-AT.js.map +1 -0
- package/dist/{attachment-BO2R47wn.js → attachment-q69dlt5Y.js} +14 -14
- package/dist/attachment-q69dlt5Y.js.map +1 -0
- package/dist/auth-M3vGMgDR.js +723 -0
- package/dist/auth-M3vGMgDR.js.map +1 -0
- package/dist/{avatar-Bpzty83c.js → avatar-djdaaQ_s.js} +15 -13
- package/dist/avatar-djdaaQ_s.js.map +1 -0
- package/dist/{badge-BZXZT9wd.js → badge-0tATj-tW.js} +9 -9
- package/dist/badge-0tATj-tW.js.map +1 -0
- 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/buttons.js +3 -2
- package/dist/base/cards.js +3 -3
- package/dist/base/carousel.js +33 -35
- package/dist/base/carousel.js.map +1 -1
- package/dist/base/cascader.js +246 -222
- package/dist/base/cascader.js.map +1 -1
- 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 +1 -1
- package/dist/base/command.js +1 -1
- package/dist/base/copyable.js +1 -1
- package/dist/base/date-pickers.js +3 -3
- package/dist/base/display.js +7 -7
- package/dist/base/feedback.js +4 -4
- package/dist/base/forms-numeric.js +1 -1
- package/dist/base/forms.js +2 -2
- package/dist/base/inline-edit.js +94 -92
- package/dist/base/inline-edit.js.map +1 -1
- package/dist/base/item.js +1 -1
- package/dist/base/media-gallery.js +1 -1
- package/dist/base/navigation.js +6 -6
- package/dist/base/popover-menu.js +1 -1
- package/dist/base/popover.js +1 -1
- package/dist/base/qr-code.js +7 -4
- package/dist/base/qr-code.js.map +1 -1
- package/dist/base/repeaters.js +53 -53
- package/dist/base/repeaters.js.map +1 -1
- package/dist/base/sheet.js +1 -1
- package/dist/base/sidebar.js +1 -1
- package/dist/base/signature-pad.js +84 -82
- package/dist/base/signature-pad.js.map +1 -1
- package/dist/base/skeleton.js +1 -1
- package/dist/base/spinner.js +1 -1
- 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 +7 -7
- 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/blocks/admin.js +228 -219
- package/dist/blocks/admin.js.map +1 -1
- package/dist/blocks/ai.js +182 -212
- package/dist/blocks/ai.js.map +1 -1
- package/dist/blocks/analytics/usage-card.js +1 -1
- package/dist/blocks/analytics.js +665 -620
- package/dist/blocks/analytics.js.map +1 -1
- package/dist/blocks/collaboration.js +2 -2
- package/dist/blocks/commerce/receipt.js +1 -1
- package/dist/blocks/commerce.js +1439 -1553
- package/dist/blocks/commerce.js.map +1 -1
- package/dist/blocks/data-display.js +1 -1
- package/dist/blocks/onboarding.js +6 -6
- package/dist/blocks/onboarding.js.map +1 -1
- package/dist/blocks/planning.js +88 -97
- package/dist/blocks/planning.js.map +1 -1
- package/dist/blocks/timelines.js +1 -1
- package/dist/{breadcrumbs-DdQ_I5ra.js → breadcrumbs-BG_FfMK4.js} +10 -9
- package/dist/breadcrumbs-BG_FfMK4.js.map +1 -0
- package/dist/button-BS3KDg00.js +674 -0
- package/dist/button-BS3KDg00.js.map +1 -0
- package/dist/{button-BS6l5JUw.js → button-R9CCoH7A.js} +3 -3
- package/dist/button-R9CCoH7A.js.map +1 -0
- package/dist/buttons-DS0ZIwJ_.js +99 -0
- package/dist/buttons-DS0ZIwJ_.js.map +1 -0
- package/dist/{cards-Cum56XOF.js → cards-QoJ4syG9.js} +12 -12
- package/dist/{cards-Cum56XOF.js.map → cards-QoJ4syG9.js.map} +1 -1
- package/dist/chart-DwYRKKc-.js +152 -0
- package/dist/chart-DwYRKKc-.js.map +1 -0
- package/dist/checkbox-B7ygNTYp.js +35 -0
- package/dist/checkbox-B7ygNTYp.js.map +1 -0
- package/dist/choice-inputs-DrJ17tpL.js +752 -0
- package/dist/choice-inputs-DrJ17tpL.js.map +1 -0
- package/dist/{code-block-xHKbAJDj.js → code-block-CD0B7M93.js} +27 -27
- package/dist/code-block-CD0B7M93.js.map +1 -0
- package/dist/{collapsible-DsnktFNv.js → collapsible-oMUGM4tr.js} +3 -3
- package/dist/{collapsible-DsnktFNv.js.map → collapsible-oMUGM4tr.js.map} +1 -1
- package/dist/{collection-content-C4fthJV_.js → collection-content-Ys-OZOkY.js} +3 -3
- package/dist/{collection-content-C4fthJV_.js.map → collection-content-Ys-OZOkY.js.map} +1 -1
- package/dist/combobox-6dvVny2q.js +272 -0
- package/dist/combobox-6dvVny2q.js.map +1 -0
- package/dist/combobox-CB7039j7.js +865 -0
- package/dist/combobox-CB7039j7.js.map +1 -0
- package/dist/{command-C0xe7u9B.js → command-kPMPjIxJ.js} +35 -31
- package/dist/command-kPMPjIxJ.js.map +1 -0
- package/dist/{comments-DviPLnwy.js → comments-You0Bzbr.js} +434 -409
- package/dist/comments-You0Bzbr.js.map +1 -0
- package/dist/components/base/accordion/accordion-variants.d.ts +3 -3
- package/dist/components/base/action-menu/action-menu.d.ts +7 -2
- package/dist/components/base/avatar/stacked-avatars.d.ts +5 -4
- package/dist/components/base/badge/badge.d.ts +1 -1
- package/dist/components/base/badge/badge.recipes.d.ts +4 -4
- package/dist/components/base/buttons/base-button.d.ts +7 -0
- package/dist/components/base/buttons/button-group.recipes.d.ts +2 -2
- package/dist/components/base/buttons/button.recipes.d.ts +10 -19
- package/dist/components/base/buttons/button.variants.d.ts +1 -0
- package/dist/components/base/buttons/loader-button.d.ts +3 -1
- package/dist/components/base/buttons/text-button.d.ts +2 -2
- package/dist/components/base/buttons/use-pending-button-focus.d.ts +3 -0
- package/dist/components/base/cards/card.recipes.d.ts +13 -5
- package/dist/components/base/cards/partials/card-primitives.d.ts +9 -4
- package/dist/components/base/cards/smart-card.types.d.ts +1 -1
- package/dist/components/base/carousel/carousel.d.ts +3 -1
- package/dist/components/base/chart/chart.d.ts +7 -7
- package/dist/components/base/choice-inputs/checkbox.d.ts +6 -7
- package/dist/components/base/choice-inputs/choice-types.d.ts +10 -1
- package/dist/components/base/choice-inputs/pill-radio-group.d.ts +9 -0
- package/dist/components/base/choice-inputs/select-popup.d.ts +9 -1
- package/dist/components/base/choice-inputs/switch-card.d.ts +1 -1
- package/dist/components/base/date-pickers/date-picker-field.d.ts +23 -0
- package/dist/components/base/date-pickers/date-picker.recipes.d.ts +5 -18
- package/dist/components/base/date-pickers/datepicker.types.d.ts +16 -3
- package/dist/components/base/display/alert-dialog.d.ts +8 -2
- package/dist/components/base/display/content-block.d.ts +4 -2
- package/dist/components/base/display/date-block.d.ts +1 -1
- package/dist/components/base/display/dialog.d.ts +12 -5
- package/dist/components/base/display/surface-title.d.ts +6 -1
- package/dist/components/base/feedback/alert.d.ts +1 -1
- package/dist/components/base/feedback/state-display.d.ts +5 -4
- package/dist/components/base/forms/form-field.d.ts +1 -0
- package/dist/components/base/forms/form-sizing.d.ts +35 -3
- package/dist/components/base/forms/workflow.d.ts +2 -0
- package/dist/components/base/forms-numeric/coordinates-input.d.ts +3 -0
- package/dist/components/base/forms-numeric/rounding-mode-select.d.ts +4 -1
- package/dist/components/base/navigation/dropdown-menu.d.ts +2 -2
- package/dist/components/base/navigation/language-switcher.d.ts +4 -1
- package/dist/components/base/navigation/overflow-tab-bar.d.ts +7 -0
- package/dist/components/base/navigation/page-header/page-header.d.ts +4 -1
- package/dist/components/base/navigation/tabs.d.ts +3 -5
- package/dist/components/base/popover/popover.recipes.d.ts +3 -3
- package/dist/components/base/qr-code/qr-code.d.ts +2 -0
- package/dist/components/base/sheet/index.d.ts +29 -1
- package/dist/components/base/sheet/sheet.d.ts +14 -5
- package/dist/components/base/spinner/spinner.d.ts +3 -2
- package/dist/components/base/structure/structure.types.d.ts +1 -1
- package/dist/components/base/table/index.d.ts +1 -1
- package/dist/components/base/text-inputs/clear-native-field.d.ts +2 -0
- package/dist/components/base/text-inputs/input-group.d.ts +2 -2
- package/dist/components/base/text-inputs/input-trailing-action.d.ts +3 -0
- package/dist/components/base/upload/upload.recipes.d.ts +11 -0
- package/dist/components/base/value-inputs/date-time-input.d.ts +4 -2
- package/dist/components/base/value-inputs/tags-input.d.ts +2 -0
- package/dist/components/blocks/admin/api-key-list/api-key-list.types.d.ts +1 -1
- package/dist/components/blocks/analytics/format-metric-value.d.ts +11 -1
- package/dist/components/blocks/analytics/index.d.ts +1 -1
- package/dist/components/blocks/analytics/types.d.ts +1 -1
- package/dist/components/blocks/analytics/use-metric-format-options.d.ts +3 -0
- package/dist/components/blocks/collaboration/message/message.recipes.d.ts +12 -0
- package/dist/components/blocks/commerce/address-card/address-card.d.ts +1 -1
- package/dist/components/blocks/commerce/address-card/types.d.ts +3 -1
- package/dist/components/blocks/commerce/booking-calendar-event-card/booking-calendar-event-card.d.ts +4 -0
- package/dist/components/blocks/commerce/booking-calendar-event-card/booking-calendar-event-card.types.d.ts +2 -2
- package/dist/components/blocks/commerce/discount-stack/types.d.ts +1 -1
- package/dist/components/blocks/commerce/inventory-section/types.d.ts +4 -1
- package/dist/components/blocks/commerce/loyalty-points/types.d.ts +1 -1
- package/dist/components/blocks/commerce/order-status/types.d.ts +4 -0
- package/dist/components/blocks/commerce/payment-method/payment-method.d.ts +1 -1
- package/dist/components/blocks/commerce/payment-method/types.d.ts +3 -1
- package/dist/components/blocks/commerce/seo-listing/seo-listing-card.d.ts +1 -1
- package/dist/components/blocks/commerce/seo-listing/types.d.ts +3 -1
- package/dist/components/blocks/commerce/vendor-profile/types.d.ts +4 -1
- package/dist/components/blocks/data-display/dense-info-card/dense-info.strings.d.ts +4 -1
- package/dist/components/blocks/data-display/empty-state/partials/illustrations/document-stack.d.ts +1 -4
- package/dist/components/blocks/data-display/empty-state/partials/illustrations/inbox-clean.d.ts +1 -4
- package/dist/components/blocks/data-display/empty-state/partials/illustrations/index.d.ts +3 -3
- package/dist/components/blocks/data-display/empty-state/partials/illustrations/stacked-cards.d.ts +1 -6
- package/dist/components/blocks/data-display/inline-stat/inline-stat.d.ts +4 -1
- package/dist/components/features/combobox/components/error-message.d.ts +1 -1
- package/dist/components/features/comments/hooks/use-attachment-upload.d.ts +5 -0
- package/dist/components/features/filters/facets/search-facet.d.ts +0 -12
- package/dist/components/features/filters/facets/tags-facet.d.ts +1 -1
- package/dist/components/features/global-search/partials/global-search-idle-state.d.ts +1 -1
- package/dist/components/features/media-library/media-library.d.ts +1 -1
- package/dist/components/features/media-library/media-library.types.d.ts +9 -1
- package/dist/components/features/media-library/partials/media-library-partials.d.ts +1 -1
- package/dist/components/features/overlays/alert-dialog.d.ts +1 -1
- package/dist/components/features/overlays/drawer.d.ts +1 -1
- package/dist/components/features/overlays/hooks/use-overlay-actions.d.ts +2 -2
- package/dist/components/features/overlays/hooks/use-overlay-visibility.d.ts +11 -4
- package/dist/components/features/overlays/hooks/use-pending-overlay-focus.d.ts +2 -0
- package/dist/components/features/overlays/overlay-tone-icon.d.ts +37 -0
- package/dist/components/features/overlays/overlays.types.d.ts +46 -22
- package/dist/components/features/products/components/product-variants-manager.d.ts +1 -1
- package/dist/components/features/products/partials/index.d.ts +1 -1
- package/dist/components/features/products/partials/product-metric-tile.d.ts +6 -0
- package/dist/components/features/products/partials/product-tone.d.ts +11 -1
- package/dist/components/features/products/products.types.d.ts +2 -1
- package/dist/components/features/rich-text-editor/rich-text-editor.recipes.d.ts +2 -1
- package/dist/components/features/table/data-table.d.ts +1 -1
- package/dist/components/features/table/partials/cell-renderers.d.ts +1 -1
- package/dist/components/features/table/table.types.d.ts +1 -1
- package/dist/components/features/theme-tweaker/theme-provider-settings.d.ts +3 -1
- package/dist/components/layout/auth/auth.types.d.ts +1 -0
- package/dist/components/layout/page/index.d.ts +1 -1
- package/dist/components/layout/page/page.d.ts +0 -1
- package/dist/components/layout/page/page.types.d.ts +62 -6
- package/dist/components/layout/page/partials/page-header-commands.d.ts +13 -0
- package/dist/components/layout/page/partials/page-header.d.ts +1 -1
- package/dist/components/layout/sidebar/sidebar.context.d.ts +1 -1
- package/dist/components/layout/sidebar/sidebar.types.d.ts +4 -0
- package/dist/components/layout/workspace/workspace.types.d.ts +3 -0
- package/dist/components/primitives/date.d.ts +16 -3
- package/dist/components/primitives/dimensions.d.ts +6 -0
- package/dist/components/primitives/duration.d.ts +6 -0
- package/dist/components/primitives/file-size.d.ts +6 -0
- package/dist/components/primitives/index.d.ts +1 -1
- package/dist/components/primitives/initials.d.ts +11 -2
- package/dist/components/primitives/link.d.ts +7 -0
- package/dist/components/primitives/money.d.ts +29 -7
- package/dist/components/primitives/number.d.ts +17 -0
- package/dist/components/primitives/value-typography.d.ts +54 -0
- package/dist/components/primitives/value.d.ts +31 -6
- package/dist/components/typography/heading.d.ts +13 -2
- package/dist/components/typography/index.d.ts +1 -1
- package/dist/components/typography/label.d.ts +3 -0
- package/dist/components/typography/running-text.d.ts +12 -0
- package/dist/components/typography/text-link.d.ts +23 -1
- package/dist/components/typography/text.d.ts +12 -0
- package/dist/components/typography/text.styles.d.ts +37 -5
- package/dist/components/ui/button.d.ts +1 -1
- package/dist/components/ui/checkbox.d.ts +5 -0
- package/dist/components/ui/overlay.recipes.d.ts +15 -2
- package/dist/components/ui/popover.d.ts +1 -1
- package/dist/components/ui/select.d.ts +3 -1
- package/dist/components/ui/sidebar.d.ts +1 -1
- package/dist/components/ui/switch.d.ts +5 -0
- package/dist/components/ui/table.d.ts +2 -1
- package/dist/container.recipes-D9t8MHR3.js +27 -0
- package/dist/container.recipes-D9t8MHR3.js.map +1 -0
- package/dist/{containers-6s-ucRQQ.js → containers-D_L4mTVb.js} +34 -57
- package/dist/containers-D_L4mTVb.js.map +1 -0
- package/dist/control-group-recipes-DBXmfh2E.js +21 -0
- package/dist/control-group-recipes-DBXmfh2E.js.map +1 -0
- package/dist/{copyable-B7SBXJMr.js → copyable-DDjTeW_j.js} +6 -6
- package/dist/{copyable-B7SBXJMr.js.map → copyable-DDjTeW_j.js.map} +1 -1
- package/dist/data-display-3xWQUnMA.js +1335 -0
- package/dist/data-display-3xWQUnMA.js.map +1 -0
- package/dist/date-picker-DrDFj8Lo.js +1527 -0
- package/dist/date-picker-DrDFj8Lo.js.map +1 -0
- package/dist/date-pickers-CnFCb0n2.js +174 -0
- package/dist/date-pickers-CnFCb0n2.js.map +1 -0
- package/dist/date-uf3z37Nm.js +144 -0
- package/dist/date-uf3z37Nm.js.map +1 -0
- package/dist/{display-CFM5myut.js → display-DrdvPwF0.js} +25 -25
- package/dist/display-DrdvPwF0.js.map +1 -0
- package/dist/{dropdown-menu-BlnyHr-9.js → dropdown-menu-CURh59jb.js} +22 -15
- package/dist/dropdown-menu-CURh59jb.js.map +1 -0
- package/dist/features/activities.js +177 -163
- package/dist/features/activities.js.map +1 -1
- package/dist/features/ai-chat.js +187 -183
- package/dist/features/ai-chat.js.map +1 -1
- package/dist/features/async-preview.js +9 -9
- 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 +25 -23
- package/dist/features/data-view.js.map +1 -1
- package/dist/features/email-composer/engine.js +1 -1
- package/dist/features/email-composer.js +353 -355
- package/dist/features/email-composer.js.map +1 -1
- package/dist/features/event-calendar.js +346 -354
- package/dist/features/event-calendar.js.map +1 -1
- package/dist/features/filters.js +1 -1
- package/dist/features/global-search.js +225 -227
- package/dist/features/global-search.js.map +1 -1
- package/dist/features/kanban.js +29 -29
- package/dist/features/kanban.js.map +1 -1
- package/dist/features/map.js +59 -58
- package/dist/features/map.js.map +1 -1
- package/dist/features/media-library.js +898 -900
- 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 +1412 -1444
- package/dist/features/products.js.map +1 -1
- package/dist/features/questionnaire.js +51 -49
- package/dist/features/questionnaire.js.map +1 -1
- package/dist/features/resource-assignment.js +103 -97
- package/dist/features/resource-assignment.js.map +1 -1
- package/dist/features/resource.js +13 -13
- package/dist/features/resource.js.map +1 -1
- package/dist/features/rich-text-editor.js +2 -2
- package/dist/features/schema-form.js +187 -183
- package/dist/features/schema-form.js.map +1 -1
- package/dist/features/suggestions.js +3 -3
- package/dist/features/sync.js +7 -7
- package/dist/features/sync.js.map +1 -1
- package/dist/features/table.js +1 -1
- package/dist/features/theme-tweaker.js +508 -503
- package/dist/features/theme-tweaker.js.map +1 -1
- package/dist/{feedback-DWUkgEjR.js → feedback-C7nR0UtT.js} +30 -31
- package/dist/feedback-C7nR0UtT.js.map +1 -0
- package/dist/field-affordance-recipes-CkOCdE2y.js +46 -0
- package/dist/field-affordance-recipes-CkOCdE2y.js.map +1 -0
- package/dist/{field-state-recipes-CBTRt9Bt.js → field-state-recipes-C-MFBX_8.js} +5 -5
- package/dist/field-state-recipes-C-MFBX_8.js.map +1 -0
- package/dist/{filters-BhsUtsxW.js → filters-Da-gNPsE.js} +760 -765
- package/dist/filters-Da-gNPsE.js.map +1 -0
- package/dist/{form-field-Bn7ZH1rA.js → form-field-VZRULhUK.js} +20 -12
- package/dist/form-field-VZRULhUK.js.map +1 -0
- package/dist/form-sizing-uWP4lzVh.js +126 -0
- package/dist/form-sizing-uWP4lzVh.js.map +1 -0
- package/dist/{forms-B7pzS25o.js → forms-DDeEnXSY.js} +38 -38
- package/dist/forms-DDeEnXSY.js.map +1 -0
- package/dist/forms-numeric-D3CO60IF.js +905 -0
- package/dist/forms-numeric-D3CO60IF.js.map +1 -0
- package/dist/{header-C59QZyxV.js → header-2ycNqGOR.js} +42 -37
- package/dist/header-2ycNqGOR.js.map +1 -0
- package/dist/{hooks-CM1Vp9Pe.js → hooks-DQ5SPwZw.js} +2 -2
- package/dist/{hooks-CM1Vp9Pe.js.map → hooks-DQ5SPwZw.js.map} +1 -1
- package/dist/{http-CLTVJc5I.js → http-CdEIHyQf.js} +2 -2
- package/dist/http-CdEIHyQf.js.map +1 -0
- package/dist/{icon-badge-Dv0RYQwj.js → icon-badge-DKlaKXoo.js} +4 -4
- package/dist/{icon-badge-Dv0RYQwj.js.map → icon-badge-DKlaKXoo.js.map} +1 -1
- package/dist/index.js +2 -2
- package/dist/input-BoLm9Hks.js +20 -0
- package/dist/input-BoLm9Hks.js.map +1 -0
- package/dist/input-DhQdBnV3.js +255 -0
- package/dist/input-DhQdBnV3.js.map +1 -0
- package/dist/item-xzh8Df65.js +264 -0
- package/dist/item-xzh8Df65.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 +1 -1
- package/dist/layout/settings.js +1 -1
- package/dist/layout/sidebar.js +2 -2
- package/dist/layout/workspace.js +1 -1
- package/dist/layout.js +10 -10
- package/dist/lib/affordance-geometry.generated.d.ts +46 -10
- package/dist/lib/control-group-recipes.d.ts +22 -0
- package/dist/lib/field-affordance-recipes.d.ts +70 -2
- package/dist/lib/field-state-recipes.d.ts +6 -4
- package/dist/lib/format.d.ts +9 -0
- package/dist/lib/money-format.d.ts +30 -0
- package/dist/lib/navigation-row-recipes.d.ts +3 -2
- package/dist/lib/overlay-anatomy-recipes.d.ts +2 -2
- package/dist/lib/surface-recipes.d.ts +13 -8
- package/dist/lib/token-merge.generated.d.ts +144 -2
- package/dist/lib/track-recipes.d.ts +1 -1
- package/dist/lib/ui-provider/provider.d.ts +7 -2
- package/dist/lib/ui-provider/store.d.ts +6 -2
- package/dist/lib/ui-provider/types.d.ts +9 -4
- package/dist/lib/use-prefers-reduced-motion.d.ts +2 -0
- package/dist/lib/utils.js +1 -1
- package/dist/media-gallery-DQUQ0m3O.js +201 -0
- package/dist/media-gallery-DQUQ0m3O.js.map +1 -0
- package/dist/{mentions-B8Ko-fhS.js → mentions-DwnO1hh4.js} +40 -39
- package/dist/{mentions-B8Ko-fhS.js.map → mentions-DwnO1hh4.js.map} +1 -1
- package/dist/message-YfFmbXNP.js +127 -0
- package/dist/message-YfFmbXNP.js.map +1 -0
- package/dist/{navigation-BUIn2Qxx.js → navigation-Cbp5V8gG.js} +27 -28
- package/dist/navigation-Cbp5V8gG.js.map +1 -0
- package/dist/navigation-Cs1npsPm.js +300 -0
- package/dist/navigation-Cs1npsPm.js.map +1 -0
- package/dist/{navigation-row-recipes-GVh11KiT.js → navigation-row-recipes-DlU-4QYO.js} +3 -3
- package/dist/navigation-row-recipes-DlU-4QYO.js.map +1 -0
- package/dist/number-CKRBYmZK.js +352 -0
- package/dist/number-CKRBYmZK.js.map +1 -0
- package/dist/overlay.recipes-CNO2S9V6.js +34 -0
- package/dist/overlay.recipes-CNO2S9V6.js.map +1 -0
- package/dist/overlays-DhZO7N-I.js +994 -0
- package/dist/overlays-DhZO7N-I.js.map +1 -0
- package/dist/page-C6R_8jFt.js +325 -0
- package/dist/page-C6R_8jFt.js.map +1 -0
- package/dist/{page-header-DQRa0jug.js → page-header-hUTzcRZX.js} +12 -12
- package/dist/page-header-hUTzcRZX.js.map +1 -0
- package/dist/{pagination-C29vvt3I.js → pagination-Cm0jqa3I.js} +21 -21
- package/dist/pagination-Cm0jqa3I.js.map +1 -0
- package/dist/{popover-CoLDEg19.js → popover-Dcv_9jxr.js} +23 -22
- package/dist/popover-Dcv_9jxr.js.map +1 -0
- package/dist/{popover-menu-GLIQTJTx.js → popover-menu-DnRFKnHP.js} +6 -6
- package/dist/{popover-menu-GLIQTJTx.js.map → popover-menu-DnRFKnHP.js.map} +1 -1
- package/dist/{popover.recipes-zgBhMlUu.js → popover.recipes-wmbvL8Ah.js} +5 -5
- package/dist/popover.recipes-wmbvL8Ah.js.map +1 -0
- package/dist/primitives-BHrwQXFC.js +379 -0
- package/dist/primitives-BHrwQXFC.js.map +1 -0
- package/dist/primitives.js +5 -5
- package/dist/{progress-BOe8l_FZ.js → progress-Br9WTFya.js} +12 -11
- package/dist/progress-Br9WTFya.js.map +1 -0
- package/dist/{receipt-BM8xsGFy.js → receipt-CV89q1fs.js} +8 -8
- package/dist/{receipt-BM8xsGFy.js.map → receipt-CV89q1fs.js.map} +1 -1
- package/dist/rich-text-editor-CF8QYt5p.js +574 -0
- package/dist/rich-text-editor-CF8QYt5p.js.map +1 -0
- package/dist/{rich-text-editor.recipes-DxvOE0Lp.js → rich-text-editor.recipes-CnrbmTS1.js} +35 -26
- package/dist/rich-text-editor.recipes-CnrbmTS1.js.map +1 -0
- package/dist/{rich-text-B7sBAdDG.js → rich-text-j-FGAeZW.js} +4 -4
- package/dist/{rich-text-B7sBAdDG.js.map → rich-text-j-FGAeZW.js.map} +1 -1
- package/dist/{scroll-area-BheMQ270.js → scroll-area-DRdr16Pt.js} +3 -3
- package/dist/scroll-area-DRdr16Pt.js.map +1 -0
- package/dist/{separator-DO0EbCEm.js → separator-BMEvwnSX.js} +2 -2
- package/dist/{separator-DO0EbCEm.js.map → separator-BMEvwnSX.js.map} +1 -1
- package/dist/{separator-Gk_WXH-l.js → separator-Do5GWDYi.js} +4 -4
- package/dist/separator-Do5GWDYi.js.map +1 -0
- package/dist/services/actions/action-adapters.d.ts +4 -0
- package/dist/services/actions.js +8 -3
- package/dist/services/actions.js.map +1 -1
- package/dist/{settings-BnCyyPOJ.js → settings-DHk0L2MF.js} +20 -19
- package/dist/settings-DHk0L2MF.js.map +1 -0
- package/dist/sheet-DRFNY1_D.js +207 -0
- package/dist/sheet-DRFNY1_D.js.map +1 -0
- package/dist/sidebar-Cb8WzOvE.js +511 -0
- package/dist/sidebar-Cb8WzOvE.js.map +1 -0
- package/dist/{sidebar-DPR7fmqN.js → sidebar-Crc9M9JV.js} +220 -201
- package/dist/sidebar-Crc9M9JV.js.map +1 -0
- package/dist/{skeleton-CmT1ppqb.js → skeleton-DVMXr15h.js} +5 -5
- package/dist/{skeleton-CmT1ppqb.js.map → skeleton-DVMXr15h.js.map} +1 -1
- package/dist/smart-card-Bek-TwUp.js +599 -0
- package/dist/smart-card-Bek-TwUp.js.map +1 -0
- package/dist/{spinner-BY9HbUEh.js → spinner-CmTAbEp0.js} +8 -6
- package/dist/spinner-CmTAbEp0.js.map +1 -0
- package/dist/{structure-MvKVq-oL.js → structure-hzCW-XsV.js} +3 -3
- package/dist/{structure-MvKVq-oL.js.map → structure-hzCW-XsV.js.map} +1 -1
- package/dist/{structure.recipes-Bqhf9oIh.js → structure.recipes-EPz60cU8.js} +37 -19
- package/dist/structure.recipes-EPz60cU8.js.map +1 -0
- package/dist/style.css +1 -1
- package/dist/surface-recipes-DOdoBXGm.js +28 -0
- package/dist/surface-recipes-DOdoBXGm.js.map +1 -0
- package/dist/table-D6zKeYQX.js +162 -0
- package/dist/table-D6zKeYQX.js.map +1 -0
- package/dist/table-Dn4XSrSB.js +1444 -0
- package/dist/table-Dn4XSrSB.js.map +1 -0
- package/dist/tabs-Dk8E_knt.js +189 -0
- package/dist/tabs-Dk8E_knt.js.map +1 -0
- package/dist/text-inputs-BFpku0Qd.js +331 -0
- package/dist/text-inputs-BFpku0Qd.js.map +1 -0
- package/dist/{text.styles-BwB7vAF2.js → text.styles-g4He6UIW.js} +26 -15
- package/dist/text.styles-g4He6UIW.js.map +1 -0
- package/dist/theme.css +90 -124
- package/dist/themes/green.css +24 -0
- package/dist/{timelines-B9ICkQcV.js → timelines-CxuAFIc2.js} +97 -104
- package/dist/timelines-CxuAFIc2.js.map +1 -0
- package/dist/track-recipes-CDcKmndW.js +10 -0
- package/dist/track-recipes-CDcKmndW.js.map +1 -0
- package/dist/{tree-BdWSaoFT.js → tree-BotPlyiD.js} +13 -13
- package/dist/{tree-BdWSaoFT.js.map → tree-BotPlyiD.js.map} +1 -1
- package/dist/typography/rich-text.js +1 -1
- package/dist/typography-CAr3dQhX.js +116 -0
- package/dist/typography-CAr3dQhX.js.map +1 -0
- package/dist/typography.js +2 -2
- package/dist/{ui-provider-CftIB6TY.js → ui-provider-D2_TeLqw.js} +22 -22
- package/dist/ui-provider-D2_TeLqw.js.map +1 -0
- package/dist/ui-provider.js +1 -1
- package/dist/{upload-D8Za5x_c.js → upload-B0WHW7ak.js} +278 -274
- package/dist/upload-B0WHW7ak.js.map +1 -0
- package/dist/{upload-queue-hIIYBLS7.js → upload-queue-BHZ_wBfd.js} +72 -75
- package/dist/upload-queue-BHZ_wBfd.js.map +1 -0
- package/dist/{usage-card-DnmnJ58D.js → usage-card-U0YBLTNT.js} +31 -33
- package/dist/usage-card-U0YBLTNT.js.map +1 -0
- package/dist/utils-Cospvlj1.js +4445 -0
- package/dist/utils-Cospvlj1.js.map +1 -0
- package/dist/value-inputs-CjfzGMsN.js +854 -0
- package/dist/value-inputs-CjfzGMsN.js.map +1 -0
- package/dist/value-typography-Deb3yQZg.js +116 -0
- package/dist/value-typography-Deb3yQZg.js.map +1 -0
- package/dist/{workspace-C1PZmk8L.js → workspace-YoFjXi6i.js} +97 -105
- package/dist/workspace-YoFjXi6i.js.map +1 -0
- package/docs/consumer-skill/SKILL.md +123 -119
- package/docs/consumer-skill/examples/analytics.tsx +34 -0
- package/docs/consumer-skill/examples/app-layout.tsx +42 -14
- package/docs/consumer-skill/examples/detail.tsx +20 -5
- package/docs/consumer-skill/examples/edit.tsx +1 -1
- package/docs/consumer-skill/examples/layout-blocks.tsx +20 -0
- package/docs/consumer-skill/examples/manifest.json +37 -3
- package/docs/consumer-skill/examples/scoped-directory.tsx +2 -2
- package/docs/consumer-skill/examples/website.tsx +56 -0
- package/docs/consumer-skill/references/analytics.md +70 -0
- package/docs/consumer-skill/references/composition.md +195 -9
- package/docs/consumer-skill/references/design-contract.md +33 -7
- package/docs/consumer-skill/references/discovery.md +80 -5
- package/docs/consumer-skill/references/installation.md +22 -0
- package/docs/consumer-skill/references/integration.md +46 -1
- package/docs/consumer-skill/references/public-api.json +2083 -237
- package/docs/consumer-skill/references/review.md +74 -6
- package/docs/consumer-skill/references/theming.md +59 -8
- package/docs/consumer-skill/references/tokens.json +180 -9
- package/docs/consumer-skill/references/workflows.md +34 -0
- package/docs/consumer-skill/scripts/check-composition.mjs +1 -1
- package/docs/consumer-skill/scripts/composition-policy.mjs +271 -18
- package/docs/consumer-skill/scripts/discovery.mjs +69 -9
- package/docs/consumer-skill/scripts/pin-button-defaults.mjs +95 -0
- package/docs/consumer-skill/scripts/surface-policy.mjs +44 -0
- package/package.json +11 -6
- package/dist/accordion-B-5Focs8.js.map +0 -1
- package/dist/action-menu-Cx3SdfbN.js +0 -265
- package/dist/action-menu-Cx3SdfbN.js.map +0 -1
- package/dist/affordance-geometry.generated-D8Jcfb0G.js +0 -32
- package/dist/affordance-geometry.generated-D8Jcfb0G.js.map +0 -1
- package/dist/ai-artifact-B5tPpXnS.js.map +0 -1
- package/dist/app-shell-at8Kt4l_.js.map +0 -1
- package/dist/attachment-BO2R47wn.js.map +0 -1
- package/dist/auth-OFwWSAap.js +0 -704
- package/dist/auth-OFwWSAap.js.map +0 -1
- package/dist/avatar-Bpzty83c.js.map +0 -1
- package/dist/badge-BZXZT9wd.js.map +0 -1
- package/dist/breadcrumbs-DdQ_I5ra.js.map +0 -1
- package/dist/button-BS6l5JUw.js.map +0 -1
- package/dist/buttons-Co6GavvM.js +0 -674
- package/dist/buttons-Co6GavvM.js.map +0 -1
- package/dist/chart-DVfcwQu6.js +0 -149
- package/dist/chart-DVfcwQu6.js.map +0 -1
- package/dist/checkbox-BavDRjF7.js +0 -34
- package/dist/checkbox-BavDRjF7.js.map +0 -1
- package/dist/choice-inputs-CC7XqPQO.js +0 -619
- package/dist/choice-inputs-CC7XqPQO.js.map +0 -1
- package/dist/code-block-xHKbAJDj.js.map +0 -1
- package/dist/combobox-DEbdr-3u.js +0 -273
- package/dist/combobox-DEbdr-3u.js.map +0 -1
- package/dist/combobox-xv3yJbzl.js +0 -850
- package/dist/combobox-xv3yJbzl.js.map +0 -1
- package/dist/command-C0xe7u9B.js.map +0 -1
- package/dist/comments-DviPLnwy.js.map +0 -1
- package/dist/containers-6s-ucRQQ.js.map +0 -1
- package/dist/data-display-cWIRdeaf.js +0 -1129
- package/dist/data-display-cWIRdeaf.js.map +0 -1
- package/dist/date-BFNMnrS5.js +0 -140
- package/dist/date-BFNMnrS5.js.map +0 -1
- package/dist/date-picker-CA6ogMQB.js +0 -1521
- package/dist/date-picker-CA6ogMQB.js.map +0 -1
- package/dist/date-pickers-BPIKLdsD.js +0 -224
- package/dist/date-pickers-BPIKLdsD.js.map +0 -1
- package/dist/display-CFM5myut.js.map +0 -1
- package/dist/dropdown-menu-BlnyHr-9.js.map +0 -1
- package/dist/feedback-DWUkgEjR.js.map +0 -1
- package/dist/field-affordance-recipes-IjzPrCTw.js +0 -17
- package/dist/field-affordance-recipes-IjzPrCTw.js.map +0 -1
- package/dist/field-state-recipes-CBTRt9Bt.js.map +0 -1
- package/dist/filters-BhsUtsxW.js.map +0 -1
- package/dist/form-field-Bn7ZH1rA.js.map +0 -1
- package/dist/form-sizing-BWs70_a2.js +0 -100
- package/dist/form-sizing-BWs70_a2.js.map +0 -1
- package/dist/format-iamDSqs2.js +0 -110
- package/dist/format-iamDSqs2.js.map +0 -1
- package/dist/forms-B7pzS25o.js.map +0 -1
- package/dist/forms-numeric-BUBxELBv.js +0 -867
- package/dist/forms-numeric-BUBxELBv.js.map +0 -1
- package/dist/header-C59QZyxV.js.map +0 -1
- package/dist/http-CLTVJc5I.js.map +0 -1
- package/dist/input-WwYQZJzG.js +0 -202
- package/dist/input-WwYQZJzG.js.map +0 -1
- package/dist/input-ci9fIgFH.js +0 -20
- package/dist/input-ci9fIgFH.js.map +0 -1
- package/dist/item-pJ0GsOiJ.js +0 -257
- package/dist/item-pJ0GsOiJ.js.map +0 -1
- package/dist/media-gallery-DhDRgnVZ.js +0 -200
- package/dist/media-gallery-DhDRgnVZ.js.map +0 -1
- package/dist/message-COsdEvXT.js +0 -108
- package/dist/message-COsdEvXT.js.map +0 -1
- package/dist/navigation-BUIn2Qxx.js.map +0 -1
- package/dist/navigation-DBEu4POU.js +0 -267
- package/dist/navigation-DBEu4POU.js.map +0 -1
- package/dist/navigation-row-recipes-GVh11KiT.js.map +0 -1
- package/dist/number-BNT_BIzJ.js +0 -269
- package/dist/number-BNT_BIzJ.js.map +0 -1
- package/dist/overlay.recipes-BxW5FIfK.js +0 -6
- package/dist/overlay.recipes-BxW5FIfK.js.map +0 -1
- package/dist/overlays-B11wZka6.js +0 -982
- package/dist/overlays-B11wZka6.js.map +0 -1
- package/dist/page-BV6ioQg3.js +0 -202
- package/dist/page-BV6ioQg3.js.map +0 -1
- package/dist/page-header-DQRa0jug.js.map +0 -1
- package/dist/pagination-C29vvt3I.js.map +0 -1
- package/dist/popover-CoLDEg19.js.map +0 -1
- package/dist/popover.recipes-zgBhMlUu.js.map +0 -1
- package/dist/primitives-DKPOGVpj.js +0 -330
- package/dist/primitives-DKPOGVpj.js.map +0 -1
- package/dist/progress-BOe8l_FZ.js.map +0 -1
- package/dist/rich-text-editor-CoVrTiSv.js +0 -575
- package/dist/rich-text-editor-CoVrTiSv.js.map +0 -1
- package/dist/rich-text-editor.recipes-DxvOE0Lp.js.map +0 -1
- package/dist/scroll-area-BheMQ270.js.map +0 -1
- package/dist/select-B2mb-qN2.js +0 -108
- package/dist/select-B2mb-qN2.js.map +0 -1
- package/dist/separator-Gk_WXH-l.js.map +0 -1
- package/dist/settings-BnCyyPOJ.js.map +0 -1
- package/dist/sheet-B-q914Ad.js +0 -205
- package/dist/sheet-B-q914Ad.js.map +0 -1
- package/dist/sidebar-C_2p3Tfj.js +0 -501
- package/dist/sidebar-C_2p3Tfj.js.map +0 -1
- package/dist/sidebar-DPR7fmqN.js.map +0 -1
- package/dist/smart-card-CYLn46e6.js +0 -578
- package/dist/smart-card-CYLn46e6.js.map +0 -1
- package/dist/spinner-BY9HbUEh.js.map +0 -1
- package/dist/structure.recipes-Bqhf9oIh.js.map +0 -1
- package/dist/surface-recipes-CQfRr-VL.js +0 -27
- package/dist/surface-recipes-CQfRr-VL.js.map +0 -1
- package/dist/table-BS39xnlq.js +0 -1424
- package/dist/table-BS39xnlq.js.map +0 -1
- package/dist/table-DnlAJ-xR.js +0 -146
- package/dist/table-DnlAJ-xR.js.map +0 -1
- package/dist/tabs-B5cNMWiP.js +0 -187
- package/dist/tabs-B5cNMWiP.js.map +0 -1
- package/dist/text-inputs-Dh9XGyiS.js +0 -331
- package/dist/text-inputs-Dh9XGyiS.js.map +0 -1
- package/dist/text.styles-BwB7vAF2.js.map +0 -1
- package/dist/timelines-B9ICkQcV.js.map +0 -1
- package/dist/track-recipes-LLPNVwFg.js +0 -10
- package/dist/track-recipes-LLPNVwFg.js.map +0 -1
- package/dist/typography-Cv8MT-82.js +0 -96
- package/dist/typography-Cv8MT-82.js.map +0 -1
- package/dist/ui-provider-CftIB6TY.js.map +0 -1
- package/dist/upload-D8Za5x_c.js.map +0 -1
- package/dist/upload-queue-hIIYBLS7.js.map +0 -1
- package/dist/usage-card-DnmnJ58D.js.map +0 -1
- package/dist/utils-C2EKeIw3.js +0 -72
- package/dist/utils-C2EKeIw3.js.map +0 -1
- package/dist/value-inputs-DX2dbxjj.js +0 -824
- package/dist/value-inputs-DX2dbxjj.js.map +0 -1
- package/dist/workspace-C1PZmk8L.js.map +0 -1
|
@@ -1,14 +1,46 @@
|
|
|
1
|
-
import { surfaceParts, surfaceForwarders, paddingClass, paddingProperty, utility } from './surface-policy.mjs';
|
|
1
|
+
import { surfaceParts, surfaceForwarders, paddingClass, paddingProperty, utility, attenuatedFocusRing, sharedCornerParts, displayNames, nativeDisplayNames, marginProperty, radiusProperty, replacesSharedRadius, cornerRole, addsCopyMargin } from './surface-policy.mjs';
|
|
2
2
|
|
|
3
|
-
const controlNames = /^(?:Base)?(?:Button|ButtonLink|IconButton)$|^(Input|InputGroupInput|Textarea|SelectTrigger|ComboboxTrigger)$/;
|
|
3
|
+
const controlNames = /^(?:Base)?(?:Button|ButtonLink|IconButton)$|^(Input|InputGroup|InputGroupInput|InputGroupTextarea|Textarea|PasswordInput|Select|SelectTrigger|ComboboxInput|ComboboxInputTrigger|ComboboxTrigger)$/;
|
|
4
4
|
const controlFamilies = new Set(['buttons', 'text-inputs', 'choice-inputs', 'combobox']);
|
|
5
5
|
const fixedControlClass = /^(?:h|min-h|size|p[xytrblse]?)-|^rounded-(?:xs|sm|md|lg|xl|[2-9]xl|\[)|^text-(?:xs|sm|base|lg|xl|[2-9]xl|\[(?:length:|[\d.]+(?:px|rem|em)))|^(?:font|leading|tracking)-/;
|
|
6
|
-
const controlStyle = /^(height|minHeight|fontSize|fontWeight|lineHeight|letterSpacing|borderRadius)$/;
|
|
6
|
+
const controlStyle = /^(height|minHeight|fontSize|fontWeight|fontFamily|lineHeight|letterSpacing|borderRadius)$/;
|
|
7
|
+
const fixedTextClass = /^text-(?:xs|sm|base|lg|xl|[2-9]xl|\[(?:length:|[\d.]+(?:px|rem|em)))|^(?:font|leading|tracking)-/;
|
|
8
|
+
const textStyle = /^(fontSize|fontWeight|fontFamily|lineHeight|letterSpacing)$/;
|
|
9
|
+
const smallPeerGaps = new Set(['none', 'normal', 'dense', '2xs', 'xs', 'sm', 'md']);
|
|
10
|
+
const structureDefaults = { Stack: 'md', Grid: 'xl', AdaptiveGrid: 'xl' };
|
|
11
|
+
const breakpoints = ['base', 'sm', 'md', 'lg', 'xl', '2xl'];
|
|
12
|
+
const paletteHues = 'slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose|mauve|olive|mist|taupe';
|
|
13
|
+
const rawPaletteClass = new RegExp(`^!?(?:bg|text|border(?:-[xytrblse])?|ring|ring-offset|outline|fill|stroke|from|via|to|divide|decoration|caret|accent|placeholder)-(?:(?:${paletteHues})-(?:50|[1-9]00|950)|black|white)(?:/\\d{1,3})?$`);
|
|
14
|
+
const overlayOwners = ['ActionDialog', 'ConfirmDialog', 'ActionDrawer'];
|
|
15
|
+
|
|
16
|
+
/** Bind this source only; never resolve imports, read libraries or run app code. */
|
|
17
|
+
export function sourceBindings(source, ts) {
|
|
18
|
+
const options = { noResolve: true, noLib: true, allowJs: true, jsx: ts.JsxEmit.Preserve };
|
|
19
|
+
const host = ts.createCompilerHost(options);
|
|
20
|
+
host.getSourceFile = filename => filename === source.fileName ? source : undefined;
|
|
21
|
+
const checker = ts.createProgram([source.fileName], options, host).getTypeChecker();
|
|
22
|
+
return node => {
|
|
23
|
+
if (!node) return undefined;
|
|
24
|
+
if (ts.isIdentifier(node) && ts.isShorthandPropertyAssignment(node.parent) && node.parent.name === node) return checker.getShorthandAssignmentValueSymbol(node.parent);
|
|
25
|
+
return checker.getSymbolAtLocation(node);
|
|
26
|
+
};
|
|
27
|
+
}
|
|
7
28
|
|
|
8
29
|
/** Small static review: TypeScript is supplied by the app, never a runtime peer. */
|
|
30
|
+
const overlayRemedy = 'Use ActionDialog, ConfirmDialog or ActionDrawer with useOverlayVisibility from themelia-react-ui/features/overlays; they own focus, dismissal, pending state and portal theming.';
|
|
31
|
+
const modalLibrary = /^(?:@radix-ui\/react-(?:alert-)?dialog|react-modal|vaul|@base-ui(?:-components)?\/react\/(?:alert-)?dialog)$/;
|
|
32
|
+
const modalExports = { '@headlessui/react': /^(?:Dialog|DialogPanel)$/, 'react-aria-components': /^(?:Dialog|Modal|ModalOverlay)$/ };
|
|
33
|
+
const importsModal = (node, ts) => {
|
|
34
|
+
const specifier = node.moduleSpecifier.text;
|
|
35
|
+
if (modalLibrary.test(specifier)) return true;
|
|
36
|
+
const names = node.importClause?.namedBindings;
|
|
37
|
+
return !!modalExports[specifier] && !!names && ts.isNamedImports(names) && names.elements.some(item => modalExports[specifier].test((item.propertyName ?? item.name).text));
|
|
38
|
+
};
|
|
39
|
+
|
|
9
40
|
export function analyzeComposition(text, { filename, index, ts }) {
|
|
10
41
|
const kind = /\.jsx?$/.test(filename) ? ts.ScriptKind.JSX : filename.endsWith('.tsx') ? ts.ScriptKind.TSX : ts.ScriptKind.TS;
|
|
11
42
|
const source = ts.createSourceFile(filename, text, ts.ScriptTarget.Latest, true, kind);
|
|
43
|
+
const symbolFor = sourceBindings(source, ts);
|
|
12
44
|
const families = new Map(index.families.map(owner => [owner.import, owner]));
|
|
13
45
|
const bindings = new Map();
|
|
14
46
|
const namespaces = new Map();
|
|
@@ -33,7 +65,7 @@ export function analyzeComposition(text, { filename, index, ts }) {
|
|
|
33
65
|
const resolve = (node, visiting = new Set()) => {
|
|
34
66
|
if (!node || visiting.has(node)) return undefined;
|
|
35
67
|
visiting.add(node);
|
|
36
|
-
if (ts.isIdentifier(node)) return resolve(declarations.get(node
|
|
68
|
+
if (ts.isIdentifier(node)) return resolve(declarations.get(symbolFor(node)), visiting);
|
|
37
69
|
if (ts.isParenthesizedExpression(node) || ts.isAsExpression(node) || ts.isSatisfiesExpression(node)) return resolve(node.expression, visiting);
|
|
38
70
|
return node;
|
|
39
71
|
};
|
|
@@ -60,6 +92,20 @@ export function analyzeComposition(text, { filename, index, ts }) {
|
|
|
60
92
|
if (!node || visited.has(node)) { if (expression) unknown(expression); return []; }
|
|
61
93
|
visited.add(node);
|
|
62
94
|
if (ts.isStringLiteralLike(node)) return [node.text];
|
|
95
|
+
if (ts.isTemplateExpression(node)) {
|
|
96
|
+
// Keep complete static class tokens. A token split by a dynamic span is
|
|
97
|
+
// unresolved rather than an invented class from one of its fragments.
|
|
98
|
+
const chunks = [node.head.text];
|
|
99
|
+
for (const span of node.templateSpans) {
|
|
100
|
+
const resolved = resolve(span.expression);
|
|
101
|
+
if (resolved && (ts.isStringLiteralLike(resolved) || ts.isNumericLiteral(resolved))) chunks[chunks.length - 1] += resolved.text + span.literal.text;
|
|
102
|
+
else { unknown(span.expression); chunks.push(span.literal.text); }
|
|
103
|
+
}
|
|
104
|
+
return chunks.map((chunk, index) => {
|
|
105
|
+
const value = index > 0 ? chunk.replace(/^\S*/, '') : chunk;
|
|
106
|
+
return index < chunks.length - 1 ? value.replace(/\S*$/, '') : value;
|
|
107
|
+
});
|
|
108
|
+
}
|
|
63
109
|
if (ts.isConditionalExpression(node)) return [...classStrings(node.whenTrue, visited), ...classStrings(node.whenFalse, visited)];
|
|
64
110
|
if (ts.isBinaryExpression(node) && [ts.SyntaxKind.AmpersandAmpersandToken, ts.SyntaxKind.BarBarToken, ts.SyntaxKind.QuestionQuestionToken].includes(node.operatorToken.kind)) return classStrings(node.right, visited);
|
|
65
111
|
if (ts.isArrayLiteralExpression(node)) return node.elements.flatMap(item => classStrings(item, visited));
|
|
@@ -83,12 +129,14 @@ export function analyzeComposition(text, { filename, index, ts }) {
|
|
|
83
129
|
for (const item of elements) {
|
|
84
130
|
const symbol = (item.propertyName ?? item.name).text;
|
|
85
131
|
if (!family.symbols.includes(symbol)) add(item, 'public-symbol', 'error', `${specifier} does not export ${symbol}; inspect its declaration.`);
|
|
86
|
-
if (
|
|
132
|
+
else if (family.deprecated?.[symbol]) add(item, 'deprecated-owner', 'warning', `${symbol} is deprecated: ${family.deprecated[symbol]}`);
|
|
133
|
+
if (ts.isImportDeclaration(node)) bindings.set(symbolFor(item.name), { symbol, import: specifier });
|
|
87
134
|
}
|
|
88
|
-
if (names && ts.isNamespaceImport(names)) namespaces.set(names.name
|
|
135
|
+
if (names && ts.isNamespaceImport(names)) namespaces.set(symbolFor(names.name), specifier);
|
|
89
136
|
}
|
|
90
137
|
function gather(node) {
|
|
91
|
-
if (ts.
|
|
138
|
+
if (ts.isImportDeclaration(node) && ts.isStringLiteralLike(node.moduleSpecifier) && importsModal(node, ts)) add(node, 'raw-overlay-primitive', 'warning', `${node.moduleSpecifier.text} builds a second dialog system. ${overlayRemedy}`);
|
|
139
|
+
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer) declarations.set(symbolFor(node.name), node.initializer);
|
|
92
140
|
if ((ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) && node.moduleSpecifier && ts.isStringLiteralLike(node.moduleSpecifier)) importOwner(node, node.moduleSpecifier.text);
|
|
93
141
|
if (ts.isImportTypeNode(node) && ts.isLiteralTypeNode(node.argument) && ts.isStringLiteralLike(node.argument.literal)) importOwner(node, node.argument.literal.text);
|
|
94
142
|
if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword && ts.isStringLiteralLike(node.arguments[0])) importOwner(node, node.arguments[0].text);
|
|
@@ -97,10 +145,11 @@ export function analyzeComposition(text, { filename, index, ts }) {
|
|
|
97
145
|
gather(source);
|
|
98
146
|
for (const diagnostic of source.parseDiagnostics) add(source, 'syntax', 'error', ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'));
|
|
99
147
|
const ownerFor = tag => {
|
|
100
|
-
if (
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
148
|
+
if (ts.isIdentifier(tag)) return bindings.get(symbolFor(tag));
|
|
149
|
+
if (ts.isPropertyAccessExpression(tag) && ts.isIdentifier(tag.expression)) {
|
|
150
|
+
const specifier = namespaces.get(symbolFor(tag.expression));
|
|
151
|
+
return specifier ? { symbol: tag.name.text, import: specifier } : undefined;
|
|
152
|
+
}
|
|
104
153
|
};
|
|
105
154
|
const attributes = node => node.attributes.properties.flatMap(attribute => {
|
|
106
155
|
if (ts.isJsxSpreadAttribute(attribute)) return properties(attribute.expression);
|
|
@@ -108,6 +157,151 @@ export function analyzeComposition(text, { filename, index, ts }) {
|
|
|
108
157
|
const expression = ts.isJsxExpression(attribute.initializer) ? attribute.initializer.expression : attribute.initializer;
|
|
109
158
|
return [[attribute.name.getText(source), expression]];
|
|
110
159
|
});
|
|
160
|
+
const isOwner = (owner, suffix, names) => owner?.import === `${index.package}/${suffix}` && names.includes(owner.symbol);
|
|
161
|
+
const framedOwner = (owner, props) => {
|
|
162
|
+
if (literal(props.get('surface')) === 'flat') return false;
|
|
163
|
+
if (isOwner(owner, 'base/cards', ['Card', 'SmartCard']) || isOwner(owner, 'features/table', ['DataTable']) || isOwner(owner, 'features/data-view', ['DataView'])) return true;
|
|
164
|
+
if (isOwner(owner, 'blocks/analytics', ['Metric'])) return literal(props.get('variant')) === 'card';
|
|
165
|
+
if (isOwner(owner, 'blocks/analytics', ['MetricGrid'])) return !props.has('variant') || literal(props.get('variant')) === 'card';
|
|
166
|
+
return isOwner(owner, 'blocks/analytics/usage-card', ['UsageCard']) || isOwner(owner, 'blocks/analytics', ['UsageCard']) || isOwner(owner, 'blocks/planning', ['TaskCard']);
|
|
167
|
+
};
|
|
168
|
+
// Examine lexical JSX only. Names ending in Card are not evidence of framing;
|
|
169
|
+
// ChartCard is card-free; Receipt's flat paper has no ordinary Card frame.
|
|
170
|
+
const contentElements = element => {
|
|
171
|
+
const found = [];
|
|
172
|
+
function collect(child) {
|
|
173
|
+
if (ts.isJsxElement(child) || ts.isJsxSelfClosingElement(child)) {
|
|
174
|
+
const opening = ts.isJsxElement(child) ? child.openingElement : child;
|
|
175
|
+
const owner = ownerFor(opening.tagName);
|
|
176
|
+
found.push({ opening, owner, props: new Map(attributes(opening)), element: child });
|
|
177
|
+
if (isOwner(owner, 'base/cards', ['Card', 'SmartCard'])) return;
|
|
178
|
+
if (ts.isJsxElement(child)) child.children.forEach(collect);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (ts.isJsxFragment(child)) child.children.forEach(collect);
|
|
182
|
+
else if (ts.isJsxExpression(child) && child.expression) { const resolved = resolve(child.expression); if (resolved) collect(resolved); }
|
|
183
|
+
}
|
|
184
|
+
if (ts.isJsxElement(element.parent)) element.parent.children.forEach(collect);
|
|
185
|
+
return found;
|
|
186
|
+
};
|
|
187
|
+
const renderedText = element => ts.isJsxElement(element) ? element.children.map(child => ts.isJsxText(child) ? child.text : ts.isJsxExpression(child) ? literal(child.expression) ?? '' : '').join(' ').trim() : '';
|
|
188
|
+
const slotBadge = expression => {
|
|
189
|
+
const node = resolve(expression);
|
|
190
|
+
if (!node) return false;
|
|
191
|
+
if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node)) {
|
|
192
|
+
const opening = ts.isJsxElement(node) ? node.openingElement : node;
|
|
193
|
+
const owner = ownerFor(opening.tagName);
|
|
194
|
+
if (isOwner(owner, 'base/badge', ['Badge'])) return true;
|
|
195
|
+
if (owner?.import === `${index.package}/base/buttons` || /^(button|a)$/.test(opening.tagName.getText(source))) return false;
|
|
196
|
+
if (!/^(div|span)$/.test(opening.tagName.getText(source)) && !isOwner(owner, 'base/structure', ['Stack', 'Grid'])) return false;
|
|
197
|
+
return ts.isJsxElement(node) && node.children.some(slotBadge);
|
|
198
|
+
}
|
|
199
|
+
if (ts.isJsxFragment(node)) return node.children.some(slotBadge);
|
|
200
|
+
if (ts.isJsxExpression(node)) return slotBadge(node.expression);
|
|
201
|
+
return false;
|
|
202
|
+
};
|
|
203
|
+
// Buttons placed directly, conditionally or inside a passive row of a page
|
|
204
|
+
// header's actions region. Unknown local components are custom controls.
|
|
205
|
+
const commandButtons = expression => {
|
|
206
|
+
const node = resolve(expression);
|
|
207
|
+
if (!node) return false;
|
|
208
|
+
if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node)) {
|
|
209
|
+
const opening = ts.isJsxElement(node) ? node.openingElement : node;
|
|
210
|
+
const owner = ownerFor(opening.tagName);
|
|
211
|
+
if (isOwner(owner, 'base/buttons', ['Button', 'TextButton', 'BaseButton', 'BaseButtonLink']) || isOwner(owner, 'base/forms', ['SubmitStateButton']) || opening.tagName.getText(source) === 'button') return true;
|
|
212
|
+
if (!/^(div|span)$/.test(opening.tagName.getText(source)) && !isOwner(owner, 'base/structure', ['Stack', 'Grid'])) return false;
|
|
213
|
+
return ts.isJsxElement(node) && node.children.some(commandButtons);
|
|
214
|
+
}
|
|
215
|
+
if (ts.isJsxFragment(node)) return node.children.some(commandButtons);
|
|
216
|
+
if (ts.isJsxExpression(node)) return commandButtons(node.expression);
|
|
217
|
+
if (ts.isConditionalExpression(node)) return commandButtons(node.whenTrue) || commandButtons(node.whenFalse);
|
|
218
|
+
if (ts.isBinaryExpression(node) && [ts.SyntaxKind.AmpersandAmpersandToken, ts.SyntaxKind.BarBarToken, ts.SyntaxKind.QuestionQuestionToken].includes(node.operatorToken.kind)) return commandButtons(node.right);
|
|
219
|
+
return false;
|
|
220
|
+
};
|
|
221
|
+
// Static class tokens only; dynamic parts are skipped rather than reported.
|
|
222
|
+
const staticClasses = (expression, visited = new Set()) => {
|
|
223
|
+
const node = resolve(expression);
|
|
224
|
+
if (!node || visited.has(node)) return [];
|
|
225
|
+
visited.add(node);
|
|
226
|
+
if (ts.isStringLiteralLike(node)) return [node.text];
|
|
227
|
+
if (ts.isTemplateExpression(node)) return [node.head.text, ...node.templateSpans.map(span => span.literal.text)];
|
|
228
|
+
if (ts.isConditionalExpression(node)) return [...staticClasses(node.whenTrue, visited), ...staticClasses(node.whenFalse, visited)];
|
|
229
|
+
if (ts.isBinaryExpression(node) && [ts.SyntaxKind.AmpersandAmpersandToken, ts.SyntaxKind.BarBarToken, ts.SyntaxKind.QuestionQuestionToken].includes(node.operatorToken.kind)) return staticClasses(node.right, visited);
|
|
230
|
+
if (ts.isArrayLiteralExpression(node)) return node.elements.flatMap(item => staticClasses(item, visited));
|
|
231
|
+
if (ts.isCallExpression(node) && /^(cn|clsx|classNames|twMerge)$/.test(node.expression.getText(source))) return node.arguments.flatMap(item => staticClasses(item, visited));
|
|
232
|
+
return [];
|
|
233
|
+
};
|
|
234
|
+
const classTokens = opening => {
|
|
235
|
+
const attribute = opening.attributes.properties.find(property => ts.isJsxAttribute(property) && property.name.getText(source) === 'className');
|
|
236
|
+
if (!attribute?.initializer) return [];
|
|
237
|
+
const expression = ts.isJsxExpression(attribute.initializer) ? attribute.initializer.expression : attribute.initializer;
|
|
238
|
+
// Template chunks may split a token; keep only complete-looking words.
|
|
239
|
+
return staticClasses(expression).flatMap(value => value.split(/\s+/)).filter(token => token && !token.startsWith('-') && !token.endsWith('-')).map(utility);
|
|
240
|
+
};
|
|
241
|
+
// tone="primary" is solid unless a buttonStyle says otherwise; untoned Buttons are neutral.
|
|
242
|
+
const solidPrimary = (opening, owner) => {
|
|
243
|
+
if (!isOwner(owner, 'base/buttons', ['Button'])) return false;
|
|
244
|
+
const props = new Map(attributes(opening));
|
|
245
|
+
const style = props.has('buttonStyle') ? literal(props.get('buttonStyle')) : 'solid';
|
|
246
|
+
return literal(props.get('tone')) === 'primary' && style === 'solid';
|
|
247
|
+
};
|
|
248
|
+
// Overlays are separate surfaces with their own primary command.
|
|
249
|
+
const solidPrimaryCount = root => {
|
|
250
|
+
let count = 0;
|
|
251
|
+
const walk = child => {
|
|
252
|
+
if (ts.isJsxElement(child) || ts.isJsxSelfClosingElement(child)) {
|
|
253
|
+
const opening = ts.isJsxElement(child) ? child.openingElement : child;
|
|
254
|
+
const owner = ownerFor(opening.tagName);
|
|
255
|
+
if (isOwner(owner, 'features/overlays', overlayOwners)) return;
|
|
256
|
+
if (solidPrimary(opening, owner)) count++;
|
|
257
|
+
}
|
|
258
|
+
ts.forEachChild(child, walk);
|
|
259
|
+
};
|
|
260
|
+
ts.forEachChild(root, walk);
|
|
261
|
+
return count;
|
|
262
|
+
};
|
|
263
|
+
const peerCards = element => {
|
|
264
|
+
if (!ts.isJsxElement(element.parent)) return new Set();
|
|
265
|
+
const cards = [];
|
|
266
|
+
function collect(child) {
|
|
267
|
+
if (ts.isJsxFragment(child)) { child.children.forEach(collect); return; }
|
|
268
|
+
const opening = ts.isJsxElement(child) ? child.openingElement : ts.isJsxSelfClosingElement(child) ? child : undefined;
|
|
269
|
+
if (!opening) return;
|
|
270
|
+
const owner = ownerFor(opening.tagName);
|
|
271
|
+
if (framedOwner(owner, new Map(attributes(opening)))) cards.push(opening);
|
|
272
|
+
else if (owner?.symbol === 'GridCell' && owner.import === `${index.package}/base/structure` && ts.isJsxElement(child)) child.children.forEach(collect);
|
|
273
|
+
}
|
|
274
|
+
element.parent.children.forEach(collect);
|
|
275
|
+
return new Set(cards);
|
|
276
|
+
};
|
|
277
|
+
const gapEntries = (expression, fallback) => {
|
|
278
|
+
if (!expression) return { base: fallback };
|
|
279
|
+
const node = resolve(expression);
|
|
280
|
+
if (node && ts.isStringLiteralLike(node)) return { base: node.text };
|
|
281
|
+
if (node && ts.isObjectLiteralExpression(node)) return { base: fallback, ...Object.fromEntries(properties(node).filter(([key]) => breakpoints.includes(key)).map(([key, value]) => {
|
|
282
|
+
const gap = literal(value);
|
|
283
|
+
if (gap === undefined && value) unknown(value);
|
|
284
|
+
return [key, gap];
|
|
285
|
+
})) };
|
|
286
|
+
if (node && node.kind === ts.SyntaxKind.NullKeyword || ts.isIdentifier(expression) && expression.text === 'undefined' && !symbolFor(expression)) return { base: fallback };
|
|
287
|
+
unknown(expression);
|
|
288
|
+
return { base: undefined };
|
|
289
|
+
};
|
|
290
|
+
const smallGap = (props, symbol, unknownProps) => {
|
|
291
|
+
// Unknown spread props may replace a missing gap. Report them as unresolved
|
|
292
|
+
// instead of treating the live default as a proven effective value.
|
|
293
|
+
if (!props.has('gap') && unknownProps) return false;
|
|
294
|
+
const gaps = gapEntries(props.get('gap'), structureDefaults[symbol]);
|
|
295
|
+
const rows = symbol === 'Stack' ? {} : gapEntries(props.get('rowGap'));
|
|
296
|
+
const columns = symbol === 'Stack' ? {} : gapEntries(props.get('columnGap'));
|
|
297
|
+
let gap; let row; let column;
|
|
298
|
+
return breakpoints.some(breakpoint => {
|
|
299
|
+
if (Object.hasOwn(gaps, breakpoint)) gap = gaps[breakpoint];
|
|
300
|
+
if (Object.hasOwn(rows, breakpoint)) row = rows[breakpoint];
|
|
301
|
+
if (Object.hasOwn(columns, breakpoint)) column = columns[breakpoint];
|
|
302
|
+
return smallPeerGaps.has(row ?? gap) || smallPeerGaps.has(column ?? gap);
|
|
303
|
+
});
|
|
304
|
+
};
|
|
111
305
|
function visit(node, parents = []) {
|
|
112
306
|
if (ts.isJsxElement(node)) {
|
|
113
307
|
const element = node.openingElement;
|
|
@@ -121,34 +315,93 @@ export function analyzeComposition(text, { filename, index, ts }) {
|
|
|
121
315
|
ts.forEachChild(node, child => visit(child, parents));
|
|
122
316
|
}
|
|
123
317
|
function inspect(node, parents) {
|
|
124
|
-
const owner = ownerFor(node.tagName
|
|
125
|
-
if (
|
|
318
|
+
const owner = ownerFor(node.tagName);
|
|
319
|
+
if (classTokens(node).some(token => rawPaletteClass.test(token))) add(node, 'raw-palette-color', 'warning', 'Raw palette colors bypass the theme and dark mode. Use semantic classes such as bg-primary, bg-muted, text-muted-foreground, border-border or a status tone (text-destructive, bg-success/10).');
|
|
320
|
+
if (!owner) {
|
|
321
|
+
const tag = node.tagName.getText(source);
|
|
322
|
+
if (tag === 'button') add(node, 'raw-button', 'warning', 'Use Button or TextButton from themelia-react-ui/base/buttons: they own size, focus ring, loading and disabled states.');
|
|
323
|
+
if (/^h[1-6]$/.test(tag)) add(node, 'raw-heading', 'warning', 'Use Heading from themelia-react-ui/typography (tag for the level, size for emphasis): it resets native margins and keeps the shared heading scale.');
|
|
324
|
+
const nativeProps = new Map(attributes(node));
|
|
325
|
+
const modalRole = /^(?:dialog|alertdialog)$/.test(literal(nativeProps.get('role')) ?? '') || nativeProps.has('aria-modal');
|
|
326
|
+
const scrim = classTokens(node).includes('fixed') && classTokens(node).includes('inset-0');
|
|
327
|
+
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}`);
|
|
328
|
+
if (/^(?:input|textarea)$/.test(node.tagName.getText(source))) {
|
|
329
|
+
const props = new Map(attributes(node));
|
|
330
|
+
if (props.has('className') && classStrings(props.get('className')).flatMap(value=>value.split(/\s+/)).some(value=>replacesSharedRadius(value,'inner'))) add(node,'shared-radius','error','Native editable controls follow bounded rounded-inner; change the global theme root, not per-field corners.');
|
|
331
|
+
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.');
|
|
332
|
+
}
|
|
333
|
+
if (nativeDisplayNames.test(node.tagName.getText(source))) {
|
|
334
|
+
const props = new Map(attributes(node));
|
|
335
|
+
if (props.has('className') && classStrings(props.get('className')).flatMap(value=>value.split(/\s+/)).some(addsCopyMargin)) add(node,'copy-margin','error','Display copy is margin-free; use the surrounding structure for copy gaps.');
|
|
336
|
+
if (props.has('style') && properties(props.get('style')).some(([name,value])=>marginProperty.test(name)&&!/^['"]?0(?:px|rem|em|%)?['"]?$/.test(resolve(value)?.getText(source)??''))) add(node,'copy-margin','error','Display copy is margin-free; use the surrounding structure for copy gaps.');
|
|
337
|
+
}
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
126
340
|
if (!families.get(owner.import)?.symbols.includes(owner.symbol)) {
|
|
127
341
|
add(node, 'public-symbol', 'error', `${owner.import} does not export ${owner.symbol}.`);
|
|
128
342
|
return;
|
|
129
343
|
}
|
|
344
|
+
const unresolvedBeforeProps = unresolved.length;
|
|
130
345
|
const props = new Map(attributes(node));
|
|
346
|
+
const unknownProps = unresolved.length !== unresolvedBeforeProps;
|
|
131
347
|
const family = owner.import.split('/base/')[1];
|
|
132
348
|
const anatomy = surfaceParts[family]?.test(owner.symbol);
|
|
133
349
|
const forwarder = surfaceForwarders[family] === owner.symbol;
|
|
134
350
|
const control = controlFamilies.has(family) && controlNames.test(owner.symbol);
|
|
135
|
-
|
|
351
|
+
const sharedCorners = sharedCornerParts[family]?.test(owner.symbol);
|
|
352
|
+
const display = owner.import === `${index.package}/typography` && displayNames.test(owner.symbol);
|
|
353
|
+
const fixedText = owner.import === `${index.package}/typography` && owner.symbol === 'DisplayLabel' || family === 'badge' && owner.symbol === 'Badge';
|
|
354
|
+
const badge = family === 'badge' && owner.symbol === 'Badge';
|
|
355
|
+
for (const name of ['className', 'headerClassName', 'contentClassName', 'footerClassName', 'subHeadingClassName']) {
|
|
136
356
|
if (!props.has(name)) continue;
|
|
137
357
|
const classes = classStrings(props.get(name)).flatMap(value => value.split(/\s+/)).map(utility);
|
|
138
358
|
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.`);
|
|
139
359
|
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.`);
|
|
360
|
+
if ((name === 'className' && (control || sharedCorners) || name === 'contentClassName' && forwarder) && classes.some(value=>replacesSharedRadius(value, control && name === 'className' ? 'inner' : cornerRole(family,owner.symbol)))) add(node,'shared-radius','error',`${owner.symbol}: outer shells use rounded-radius; controls and collection rows use bounded rounded-inner. Preserve the owning role and joined-edge resets.`);
|
|
361
|
+
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.`);
|
|
362
|
+
if ((control || family === 'buttons' && owner.symbol === 'TextButton') && name === 'className' && classes.some(attenuatedFocusRing)) add(node, 'focus-ring-contrast', 'error', `${owner.symbol}: keep the shared ring-ring/sidebar-ring focus color at full strength; numeric opacity weakens its tested contrast.`);
|
|
363
|
+
if (fixedText && name === 'className' && classes.some(value => fixedTextClass.test(value) || badge && /^rounded(?:-|$)/.test(value))) add(node, 'fixed-text-role', 'error', `${owner.symbol}: preserve its shared typography${badge ? ' and radius' : ''}; use its public variants and className for placement.`);
|
|
140
364
|
}
|
|
141
|
-
if (props.has('style')) for (const [name] of properties(props.get('style'))) {
|
|
365
|
+
if (props.has('style')) for (const [name,value] of properties(props.get('style'))) {
|
|
142
366
|
if (anatomy && paddingProperty.test(name)) add(node, 'surface-padding', 'error', `${owner.symbol}: ${name} replaces the region inset; use its public surface props.`);
|
|
143
367
|
if (control && (paddingProperty.test(name) || controlStyle.test(name))) add(node, 'control-anatomy', 'error', `${owner.symbol}: ${name} belongs to shared control geometry/typography.`);
|
|
368
|
+
if ((control || sharedCorners) && radiusProperty.test(name)) add(node,'shared-radius','error',`${owner.symbol}: corners follow the global radius theme root, not inline styles.`);
|
|
369
|
+
if (display && marginProperty.test(name) && !/^['"]?0(?:px|rem|em|%)?['"]?$/.test(resolve(value)?.getText(source)??'')) add(node,'copy-margin','error',`${owner.symbol}: typography is margin-free; the surrounding structure owns its copy gap.`);
|
|
370
|
+
if (fixedText && (textStyle.test(name) || badge && /^border.*Radius$/.test(name))) add(node, 'fixed-text-role', 'error', `${owner.symbol}: ${name} replaces its shared text/radius recipe.`);
|
|
144
371
|
}
|
|
145
|
-
if (['SmartCard', 'ContentBlock'].
|
|
146
|
-
const
|
|
372
|
+
if ((isOwner(owner, 'base/cards', ['Card', 'SmartCard']) || isOwner(owner, 'base/display', ['ContentBlock'])) && props.get('icon')) add(node, 'decorative-icon', 'warning', `${owner.symbol}: a title icon is decoration unless it encodes state or a brand; let the title name the subject.`);
|
|
373
|
+
const facts = isOwner(owner, 'blocks/data-display', ['MetadataList']) ? resolve(props.get('items')) : undefined;
|
|
374
|
+
if (facts && ts.isArrayLiteralExpression(facts) && facts.elements.filter(item => properties(item, false).some(([name]) => name === 'icon')).length > 1) add(node, 'decorative-icon', 'warning', 'MetadataList: an icon on every fact repeats what its label says; keep labels and drop the icons.');
|
|
375
|
+
const framed = framedOwner(owner, props);
|
|
376
|
+
if (isOwner(owner, 'base/cards', ['Card', 'SmartCard'])) {
|
|
377
|
+
for (const slot of ['headerAction', 'headerStart']) if (slotBadge(props.get(slot))) add(node, 'card-badge-placement', 'error', `${owner.symbol}: put status/count/version badges in titleSuffix; ${slot} owns ${slot === 'headerAction' ? 'commands' : 'pre-title context'}.`);
|
|
378
|
+
const contents = contentElements(node);
|
|
379
|
+
const role = (item, names) => isOwner(item.owner, 'typography', names);
|
|
380
|
+
const valueHeading = contents.some(item => role(item, ['Heading']) && /[$€£¥]|\d/.test(renderedText(item.element)));
|
|
381
|
+
if (valueHeading && contents.some(item => role(item, ['Text', 'DisplayLabel'])) && contents.some(item => isOwner(item.owner, 'base/badge', ['Badge']) && /[%+−-]|\d/.test(renderedText(item.element)))) add(node, 'reinvented-metric-card', 'warning', 'This label/value/change composition resembles a KPI. Compare Metric/MetricGrid from blocks/analytics or InlineStat from blocks/data-display; retain a custom subject when their metric lifecycle does not fit.');
|
|
382
|
+
const factRows = contents.filter(item => isOwner(item.owner, 'base/structure', ['Stack']) && literal(item.props.get('direction')) === 'horizontal' && literal(item.props.get('justify')) === 'between' && contentElements(item.opening).filter(child => role(child, ['Text', 'DisplayLabel'])).length >= 2);
|
|
383
|
+
if (factRows.length >= 2) add(node, 'reinvented-metadata-list', 'warning', 'Repeated label/value rows resemble record facts. Compare MetadataList or InlineStat from blocks/data-display; preserve custom rows when they own distinct interactions or content.');
|
|
384
|
+
if (contents.some(item => role(item, ['Heading']) && /^(?:no\b|nothing\b|empty\b)/i.test(renderedText(item.element))) && contents.some(item => isOwner(item.owner, 'base/structure', ['Stack']) && literal(item.props.get('align')) === 'center') && contents.some(item => isOwner(item.owner, 'base/buttons', ['Button', 'ButtonLink']))) add(node, 'reinvented-empty-state', 'warning', 'This centered empty region may fit EmptyState from blocks/data-display, with media, title, description and actions. Review the product state before choosing the complete owner.');
|
|
385
|
+
}
|
|
386
|
+
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.');
|
|
387
|
+
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.');
|
|
388
|
+
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.');
|
|
389
|
+
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.');
|
|
390
|
+
const peers = parents.findLast(parent => parent.structure);
|
|
391
|
+
if (framed && (!props.has('surface') || literal(props.get('surface')) === 'framed') && peers?.cards.size > 1 && peers.cards.has(node) && peers.smallGap) add(node, 'card-peer-gap', 'warning', 'Small peer gaps can be consumed by framed Card rings. Use lg/xl for independent surfaces and inspect visible edge clearance; tighter grouping needs an intentional surface policy.');
|
|
147
392
|
if (framed && parents.some(parent => parent.framed)) add(node, 'nested-surface', 'warning', `${owner.symbol} is inside a framed owner. Review subject boundaries; use surface="flat" when the parent owns the frame.`);
|
|
148
393
|
const header = properties(props.get('header'), false);
|
|
394
|
+
const pageOwner = owner.import === `${index.package}/layout/page` || owner.import === `${index.package}/layout`;
|
|
395
|
+
const headerActions = pageOwner && owner.symbol === 'Page' ? new Map(header).get('actions') : pageOwner && owner.symbol === 'PageHeader' ? props.get('actions') : undefined;
|
|
396
|
+
if (pageOwner && owner.symbol === 'Page') {
|
|
397
|
+
const primaries = solidPrimaryCount(ts.isJsxSelfClosingElement(node) ? node : node.parent) + (new Map(header).has('primaryAction') ? 1 : 0);
|
|
398
|
+
if (primaries > 1) add(node, 'multiple-primary-commands', 'warning', `Page has ${primaries} solid primary commands. Keep one primary per page as header.primaryAction; leave other commands untoned (the neutral button) or list them in secondaryActions.`);
|
|
399
|
+
}
|
|
400
|
+
if (headerActions && commandButtons(headerActions)) add(node, 'page-command-roles', 'warning', `${owner.symbol}: buttons in the header actions region choose their own emphasis, size and overflow. Pass commands as primaryAction (one) and ordered secondaryActions; keep actions for non-command controls.`);
|
|
149
401
|
const title = literal(props.get('title')) ?? literal(new Map(header).get('title'));
|
|
150
402
|
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.`);
|
|
151
|
-
|
|
403
|
+
const structure = owner.import === `${index.package}/base/structure` && Object.hasOwn(structureDefaults, owner.symbol);
|
|
404
|
+
return { framed, title, structure, cards: structure ? peerCards(node) : new Set(), smallGap: structure ? smallGap(props, owner.symbol, unknownProps) : false };
|
|
152
405
|
}
|
|
153
406
|
visit(source);
|
|
154
407
|
return { findings, unresolved };
|
|
@@ -3,6 +3,10 @@ const requireFormat = (condition, message) => { if (!condition) throw new Error(
|
|
|
3
3
|
const stringList = value => Array.isArray(value) && value.every(item => typeof item === 'string');
|
|
4
4
|
const text = value => typeof value === 'string' && value.trim().length > 0;
|
|
5
5
|
const normalized = value => value.toLowerCase();
|
|
6
|
+
const words = value => value.replace(/([a-z0-9])([A-Z])/g, '$1 $2').replace(/([A-Z])([A-Z][a-z])/g, '$1 $2').toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
|
|
7
|
+
const symbolKey = value => words(value).join('');
|
|
8
|
+
const searchable = value => words(value).join(' ');
|
|
9
|
+
const containsTerms = (value, terms) => { const tokens = new Set(words(value)); return terms.every(term => tokens.has(term)); };
|
|
6
10
|
const compare = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
7
11
|
|
|
8
12
|
export function validateIndex(index) {
|
|
@@ -17,7 +21,18 @@ export function validateIndex(index) {
|
|
|
17
21
|
requireFormat(text(family.declaration) && /^dist\/[\w./-]+\.d\.ts$/.test(family.declaration) && !family.declaration.split('/').includes('..'), `invalid declaration ${family.declaration}`);
|
|
18
22
|
requireFormat(layers.includes(family.layer) && text(family.responsibility), `missing layer/responsibility for ${family.import}`);
|
|
19
23
|
for (const field of ['symbols', 'jobs', 'tags', 'aliases', 'sections', 'optionalPeers']) requireFormat(stringList(family[field]), `invalid ${field} for ${family.import}`);
|
|
24
|
+
if (family.deprecated !== undefined) requireFormat(family.deprecated !== null && typeof family.deprecated === 'object' && !Array.isArray(family.deprecated) && Object.entries(family.deprecated).every(([symbol, note]) => family.symbols.includes(symbol) && text(note)), `invalid deprecated symbols for ${family.import}`);
|
|
25
|
+
if (family.references !== undefined) requireFormat(stringList(family.references) && family.references.every(file => /^references\/[\w/-]+\.md(?:#[\w-]+)?$/.test(file) && !file.split('/').includes('..')), `invalid focused references for ${family.import}`);
|
|
20
26
|
requireFormat(family.optionalPeers.every(peer => index.peers?.some(owner => owner.name === peer && owner.optional)), `unknown optional peer for ${family.import}`);
|
|
27
|
+
if (family.recipes !== undefined) {
|
|
28
|
+
requireFormat(Array.isArray(family.recipes), `invalid recipes for ${family.import}`);
|
|
29
|
+
for (const recipe of family.recipes) {
|
|
30
|
+
requireFormat(text(recipe?.file) && /^examples\/[\w/-]+\.tsx$/.test(recipe.file) && !recipe.file.split('/').includes('..'), 'invalid recipe file');
|
|
31
|
+
requireFormat(stringList(recipe.exports) && recipe.exports.length > 0 && stringList(recipe.symbols) && recipe.symbols.every(symbol => family.symbols.includes(symbol)), `unknown recipe symbol for ${family.import}`);
|
|
32
|
+
requireFormat(stringList(recipe.references) && recipe.references.every(file => /^references\/[\w/-]+\.md(?:#[\w-]+)?$/.test(file) && !file.split('/').includes('..')), 'invalid recipe references');
|
|
33
|
+
if (recipe.optionalPeers !== undefined) requireFormat(stringList(recipe.optionalPeers) && recipe.optionalPeers.every(peer => index.peers?.some(owner => owner.name === peer && owner.optional)), 'invalid recipe optional peers');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
21
36
|
requireFormat(Array.isArray(family.guidance), `invalid guidance for ${family.import}`);
|
|
22
37
|
for (const guide of family.guidance) {
|
|
23
38
|
requireFormat(text(guide?.title) && typeof guide.description === 'string' && typeof guide.section === 'string', 'invalid guidance title/description/section');
|
|
@@ -56,28 +71,73 @@ export function parseArguments(args) {
|
|
|
56
71
|
return { query, ...options };
|
|
57
72
|
}
|
|
58
73
|
|
|
59
|
-
const guideText = guide =>
|
|
74
|
+
const guideText = guide => searchable([guide.title, guide.description, ...Object.values(guide.browse), ...guide.jobs, ...guide.tags, ...guide.aliases, ...guide.chooseWhen, ...guide.appOwned, ...guide.sections.flatMap(section => [section.title, ...section.symbols])].join(' '));
|
|
75
|
+
const guideSymbols = (guide, family) => [...new Set(guide.sections.flatMap(section => section.symbols))].filter(symbol => family.symbols.includes(symbol));
|
|
76
|
+
|
|
77
|
+
function relevantGuidance(family, needle, terms, matchedSymbols, exactImport) {
|
|
78
|
+
const ranked = family.guidance.map(guide => {
|
|
79
|
+
const haystack = guideText(guide);
|
|
80
|
+
const count = terms.filter(term => containsTerms(haystack, [term])).length;
|
|
81
|
+
const symbol = matchedSymbols.some(value => guideSymbols(guide, family).includes(value));
|
|
82
|
+
const alias = guide.aliases.some(value => normalized(value) === needle);
|
|
83
|
+
const complete = count === terms.length;
|
|
84
|
+
const score = Number(alias) * 10000 + Number(symbol) * 1000 + Number(complete) * 100 + count;
|
|
85
|
+
return { guide, score, symbol, alias, complete };
|
|
86
|
+
}).sort((a, b) => b.score - a.score || Number(guideSymbols(b.guide, family).length > 0) - Number(guideSymbols(a.guide, family).length > 0) || compare(a.guide.title, b.guide.title));
|
|
87
|
+
// Exact owner and curated task matches supply their own narrow reading path.
|
|
88
|
+
// Generic intent can still combine terms across fields/family guidance.
|
|
89
|
+
const selected = ranked.filter(item => ranked.some(item => item.alias) ? item.alias : matchedSymbols.length ? item.symbol : ranked.some(item => item.complete) ? item.complete : item.score > 0);
|
|
90
|
+
return (selected.length || !exactImport ? selected : ranked).slice(0, 3).map(item => item.guide);
|
|
91
|
+
}
|
|
60
92
|
|
|
61
93
|
export function findComponents(index, { query, layer = null, limit = 5 }) {
|
|
62
94
|
validateIndex(index);
|
|
63
95
|
const needle = normalized(query);
|
|
64
|
-
const terms = [...new Set(
|
|
96
|
+
const terms = [...new Set(words(query))];
|
|
97
|
+
const key = symbolKey(query);
|
|
98
|
+
// Cased API names and explicit imports request a precise contract. Ordinary
|
|
99
|
+
// nouns such as table/empty remain task searches with richer alternatives.
|
|
100
|
+
const exactOwners = index.families.filter(family => (!layer || layer === family.layer) && (normalized(family.import) === needle || family.symbols.includes(query.trim())));
|
|
65
101
|
const ranked = [];
|
|
66
102
|
for (const family of index.families) {
|
|
67
103
|
if (layer && layer !== family.layer) continue;
|
|
104
|
+
if (exactOwners.length && !exactOwners.includes(family)) continue;
|
|
68
105
|
const exactImport = normalized(family.import) === needle;
|
|
69
|
-
const matchedSymbols = family.symbols.filter(symbol =>
|
|
70
|
-
const
|
|
106
|
+
const matchedSymbols = family.symbols.filter(symbol => symbolKey(symbol) === key);
|
|
107
|
+
const documented = new Set(family.guidance.flatMap(guide => guideSymbols(guide, family)));
|
|
108
|
+
const focusedSymbols = [...documented].filter(symbol => containsTerms(symbol, terms));
|
|
109
|
+
const haystack = searchable([family.import, family.responsibility, ...documented, ...family.jobs, ...family.tags, ...family.aliases, ...family.sections, ...family.guidance.map(guideText)].join(' '));
|
|
71
110
|
// AND matching prevents a familiar term from inventing results for an
|
|
72
111
|
// otherwise unknown query. Avoidance/alternatives do not recommend owners.
|
|
73
|
-
if (!exactImport && !matchedSymbols.length && !
|
|
112
|
+
if (!exactImport && !matchedSymbols.length && !containsTerms(haystack, terms)) continue;
|
|
74
113
|
// A dedicated, documented owner precedes an aggregate re-export of the
|
|
75
114
|
// same symbol. Explicit import queries still select the requested family.
|
|
76
|
-
const documentedSymbol =
|
|
115
|
+
const documentedSymbol = matchedSymbols.some(symbol => documented.has(symbol));
|
|
116
|
+
const exactFamily = searchable(family.import.split('/').at(-1)) === terms.join(' ');
|
|
77
117
|
const exactIntent = family.aliases.some(alias => normalized(alias) === needle);
|
|
78
|
-
const score = exactImport ? 10000 : matchedSymbols.length ? 9000 + Number(documentedSymbol) : exactIntent ? 8000 : terms.reduce((sum, term) => sum + (family.guidance.some(guide =>
|
|
79
|
-
const
|
|
80
|
-
|
|
118
|
+
const score = exactImport ? 10000 : matchedSymbols.length ? 9000 + Number(documentedSymbol) : exactFamily ? 8500 : exactIntent ? 8000 : focusedSymbols.length ? 7100 + 1 / Math.min(...focusedSymbols.map(symbol => words(symbol).length)) : terms.reduce((sum, term) => sum + (family.guidance.some(guide => containsTerms(guide.title, [term])) ? 20 : family.tags.some(tag => containsTerms(tag, [term])) ? 10 : 1), 0);
|
|
119
|
+
const selectedSymbols = matchedSymbols.length ? matchedSymbols : focusedSymbols;
|
|
120
|
+
const guidance = relevantGuidance(family, needle, terms, exactOwners.length ? selectedSymbols : [], exactImport);
|
|
121
|
+
const candidateSymbols = exactImport ? family.symbols : exactOwners.length || matchedSymbols.length ? selectedSymbols : [...new Set([...selectedSymbols, ...guidance.flatMap(guide => guideSymbols(guide, family))])];
|
|
122
|
+
// Intent searches recommend current names only. Exact queries keep the
|
|
123
|
+
// requested symbol; either way the result reports deprecated candidates.
|
|
124
|
+
const lifecycle = family.deprecated ?? {};
|
|
125
|
+
const relevantSymbols = exactImport || matchedSymbols.length ? candidateSymbols : candidateSymbols.filter(symbol => !Object.hasOwn(lifecycle, symbol));
|
|
126
|
+
const deprecated = Object.fromEntries(candidateSymbols.filter(symbol => Object.hasOwn(lifecycle, symbol)).map(symbol => [symbol, lifecycle[symbol]]));
|
|
127
|
+
const candidates = (family.recipes ?? []).filter(recipe => recipe.symbols.some(symbol => relevantSymbols.includes(symbol)));
|
|
128
|
+
const recipeText = recipe => normalized([recipe.file, ...recipe.exports, ...recipe.references].join(' ')).replace(/[-/#]/g, ' ');
|
|
129
|
+
const taskRecipes = matchedSymbols.length || exactImport ? [] : candidates.filter(recipe => terms.every(term => recipeText(recipe).includes(term)));
|
|
130
|
+
// Retain related task synonyms without routing through an unrelated alias
|
|
131
|
+
// on the same broad family (for example, website versus main/aside layout).
|
|
132
|
+
const aliases = guidance.flatMap(guide => guide.aliases.filter(alias => terms.some(term => containsTerms(alias, [term]))).map(alias => normalized(alias).split(/\s+/)));
|
|
133
|
+
const aliasRecipes = matchedSymbols.length || exactImport ? [] : candidates.filter(recipe => aliases.some(intent => intent.every(term => recipeText(recipe).includes(term))));
|
|
134
|
+
const recipes = taskRecipes.length ? taskRecipes : aliasRecipes.length ? aliasRecipes : candidates;
|
|
135
|
+
const nextReads = {
|
|
136
|
+
declaration: family.declaration,
|
|
137
|
+
references: [...new Set([...(family.references ?? []), ...recipes.flatMap(recipe => recipe.references)])].sort(compare),
|
|
138
|
+
examples: recipes.map(({ file, exports, symbols, optionalPeers = [] }) => ({ file, exports, symbols: symbols.filter(symbol => relevantSymbols.includes(symbol)), optionalPeers })),
|
|
139
|
+
};
|
|
140
|
+
ranked.push({ score, import: family.import, declaration: family.declaration, layer: family.layer, responsibility: family.responsibility, optionalPeers: family.optionalPeers, match: exactImport ? 'import' : matchedSymbols.length ? 'symbol' : 'intent', matchedSymbols, relevantSymbols, ...(Object.keys(deprecated).length ? { deprecated } : {}), nextReads, guidance });
|
|
81
141
|
}
|
|
82
142
|
ranked.sort((a, b) => b.score - a.score || compare(a.import, b.import));
|
|
83
143
|
return { package: index.package, version: index.version, query, layer, limit, total: ranked.length, results: ranked.slice(0, limit) };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
5
|
+
import { sourceBindings } from './composition-policy.mjs';
|
|
6
|
+
|
|
7
|
+
// An untoned Button now renders as the secondary outline, and a Button with a
|
|
8
|
+
// tone but no buttonStyle keeps that tone's solid treatment. Writing the old
|
|
9
|
+
// default tone onto untoned Buttons therefore keeps an existing screen's look.
|
|
10
|
+
// It edits only with --write; otherwise it reports what it would change.
|
|
11
|
+
// --solid-only pins only Buttons that also set no buttonStyle, so untoned
|
|
12
|
+
// outline and ghost Buttons (often brand-coloured by accident) turn neutral.
|
|
13
|
+
const usage = 'Usage: pin-button-defaults.mjs [--write] [--solid-only] [--tone=primary] [--source-root=<dir>] <files or directories>';
|
|
14
|
+
const owners = new Set(['Button', 'BaseButton', 'BaseButtonLink']);
|
|
15
|
+
const packageImports = new Set(['themelia-react-ui', 'themelia-react-ui/base/buttons']);
|
|
16
|
+
|
|
17
|
+
export function pinButtonDefaults(text, { filename, ts, tone = 'primary', solidOnly = false, sourceRoot }) {
|
|
18
|
+
const kind = /\.jsx?$/.test(filename) ? ts.ScriptKind.JSX : filename.endsWith('.tsx') ? ts.ScriptKind.TSX : ts.ScriptKind.TS;
|
|
19
|
+
const source = ts.createSourceFile(filename, text, ts.ScriptTarget.Latest, true, kind);
|
|
20
|
+
const symbolFor = sourceBindings(source, ts);
|
|
21
|
+
// Library maintainers pass --source-root so relative imports of the buttons family count too.
|
|
22
|
+
const buttonsDir = sourceRoot && path.resolve(sourceRoot, 'components/base/buttons');
|
|
23
|
+
const isButtonsImport = specifier => packageImports.has(specifier)
|
|
24
|
+
|| (!!buttonsDir && specifier.startsWith('.') && `${path.resolve(path.dirname(filename), specifier)}/`.startsWith(`${buttonsDir}/`));
|
|
25
|
+
const named = new Set();
|
|
26
|
+
const namespaces = new Set();
|
|
27
|
+
for (const statement of source.statements) {
|
|
28
|
+
if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier) || !isButtonsImport(statement.moduleSpecifier.text)) continue;
|
|
29
|
+
const bindings = statement.importClause?.namedBindings;
|
|
30
|
+
if (bindings && ts.isNamespaceImport(bindings)) namespaces.add(symbolFor(bindings.name));
|
|
31
|
+
if (bindings && ts.isNamedImports(bindings)) for (const item of bindings.elements) {
|
|
32
|
+
if (!item.isTypeOnly && owners.has((item.propertyName ?? item.name).text)) named.add(symbolFor(item.name));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const isOwner = tag => (ts.isIdentifier(tag) && named.has(symbolFor(tag)))
|
|
36
|
+
|| (ts.isPropertyAccessExpression(tag) && ts.isIdentifier(tag.expression) && namespaces.has(symbolFor(tag.expression)) && owners.has(tag.name.text));
|
|
37
|
+
const edits = [];
|
|
38
|
+
const pinned = [];
|
|
39
|
+
const unresolved = [];
|
|
40
|
+
const line = node => source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1;
|
|
41
|
+
const visit = node => {
|
|
42
|
+
if ((ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) && isOwner(node.tagName)) {
|
|
43
|
+
const props = node.attributes.properties;
|
|
44
|
+
const has = name => props.some(attribute => ts.isJsxAttribute(attribute) && attribute.name.getText(source) === name);
|
|
45
|
+
const toned = has('tone') || (solidOnly && has('buttonStyle'));
|
|
46
|
+
if (!toned && props.some(ts.isJsxSpreadAttribute)) {
|
|
47
|
+
// A spread may already carry tone; leave it to review.
|
|
48
|
+
unresolved.push({ file: filename, line: line(node) });
|
|
49
|
+
} else if (!toned) {
|
|
50
|
+
edits.push({ at: (node.typeArguments ?? node.tagName).end, text: ` tone="${tone}"` });
|
|
51
|
+
pinned.push({ file: filename, line: line(node), tone });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
ts.forEachChild(node, visit);
|
|
55
|
+
};
|
|
56
|
+
visit(source);
|
|
57
|
+
let output = text;
|
|
58
|
+
for (const edit of edits.sort((a, b) => b.at - a.at)) output = output.slice(0, edit.at) + edit.text + output.slice(edit.at);
|
|
59
|
+
return { output, pinned, unresolved };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function collect(target, files) {
|
|
63
|
+
const info = fs.lstatSync(target);
|
|
64
|
+
if (info.isSymbolicLink()) throw new Error(`Use a real source path instead of a symlink: ${target}`);
|
|
65
|
+
if (info.isDirectory()) {
|
|
66
|
+
for (const entry of fs.readdirSync(target).sort()) if (!['node_modules', '.git', 'dist', 'build'].includes(entry)) collect(path.join(target, entry), files);
|
|
67
|
+
} else if (/\.[jt]sx?$/.test(target) && !/\.d\.ts$/.test(target)) files.add(path.resolve(target));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) {
|
|
71
|
+
try {
|
|
72
|
+
const args = process.argv.slice(2);
|
|
73
|
+
const option = name => args.find(arg => arg.startsWith(`--${name}=`))?.slice(name.length + 3);
|
|
74
|
+
const targets = args.filter(arg => !arg.startsWith('--'));
|
|
75
|
+
const unknown = args.filter(arg => arg.startsWith('--') && !/^--(?:write|solid-only|tone=.+|source-root=.+)$/.test(arg));
|
|
76
|
+
if (!targets.length || unknown.length) throw new Error(unknown.length ? `Unknown option: ${unknown.join(', ')}\n${usage}` : usage);
|
|
77
|
+
const ts = createRequire(path.join(process.cwd(), 'package.json'))('typescript');
|
|
78
|
+
const files = new Set();
|
|
79
|
+
targets.forEach(target => collect(target, files));
|
|
80
|
+
if (!files.size) throw new Error('No supported source files found in the supplied paths.');
|
|
81
|
+
const write = args.includes('--write');
|
|
82
|
+
const sourceRoot = option('source-root') && path.resolve(option('source-root'));
|
|
83
|
+
const results = [...files].sort().map(filename => {
|
|
84
|
+
const text = fs.readFileSync(filename, 'utf8');
|
|
85
|
+
const result = pinButtonDefaults(text, { filename, ts, tone: option('tone'), solidOnly: args.includes('--solid-only'), sourceRoot });
|
|
86
|
+
if (write && result.output !== text) fs.writeFileSync(filename, result.output);
|
|
87
|
+
return result;
|
|
88
|
+
});
|
|
89
|
+
const pinned = results.flatMap(result => result.pinned);
|
|
90
|
+
process.stdout.write(JSON.stringify({ mode: write ? 'write' : 'dry-run', files: files.size, pinned: pinned.length, changedFiles: new Set(pinned.map(item => item.file)).size, unresolved: results.flatMap(result => result.unresolved), details: pinned }, null, 2) + '\n');
|
|
91
|
+
} catch (error) {
|
|
92
|
+
process.stderr.write(`pin-button-defaults: ${error.message}\nRun from the consumer app with its TypeScript development dependency installed.\n`);
|
|
93
|
+
process.exitCode = 1;
|
|
94
|
+
}
|
|
95
|
+
}
|