@entifix/react-controls 0.18.2 → 0.20.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/README.md +13 -5
- package/dist/i18n/catalog/en.d.ts.map +1 -1
- package/dist/i18n/catalog/en.js +68 -10
- package/dist/i18n/catalog/en.js.map +1 -1
- package/dist/i18n/catalog/es.d.ts +76 -5
- package/dist/i18n/catalog/es.d.ts.map +1 -1
- package/dist/i18n/catalog/es.js +74 -10
- package/dist/i18n/catalog/es.js.map +1 -1
- package/dist/i18n/catalog/index.d.ts +126 -10
- package/dist/i18n/catalog/index.d.ts.map +1 -1
- package/dist/i18n/entity-labels.d.ts +14 -0
- package/dist/i18n/entity-labels.d.ts.map +1 -1
- package/dist/i18n/entity-labels.js +15 -0
- package/dist/i18n/entity-labels.js.map +1 -1
- package/dist/i18n/i18n-context.js +2 -2
- package/dist/i18n/i18n-context.js.map +1 -1
- package/dist/index.d.ts +13 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -2
- package/dist/index.js.map +1 -1
- package/dist/primitives.d.ts +7 -1
- package/dist/primitives.d.ts.map +1 -1
- package/dist/primitives.js +7 -1
- package/dist/primitives.js.map +1 -1
- package/dist/theme/index.d.ts +1 -0
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/index.js +1 -0
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/theme-context.d.ts +35 -7
- package/dist/theme/theme-context.d.ts.map +1 -1
- package/dist/theme/theme-context.js +109 -21
- package/dist/theme/theme-context.js.map +1 -1
- package/dist/theme/theme-cookie.d.ts +9 -0
- package/dist/theme/theme-cookie.d.ts.map +1 -0
- package/dist/theme/theme-cookie.js +8 -0
- package/dist/theme/theme-cookie.js.map +1 -0
- package/dist/theme/theme-script.d.ts +8 -5
- package/dist/theme/theme-script.d.ts.map +1 -1
- package/dist/theme/theme-script.js +24 -10
- package/dist/theme/theme-script.js.map +1 -1
- package/dist/theme/types.d.ts +19 -3
- package/dist/theme/types.d.ts.map +1 -1
- package/dist/theme/types.js.map +1 -1
- package/dist/ui/actions/action-surfaces.d.ts +1 -1
- package/dist/ui/actions/action-surfaces.js +1 -1
- package/dist/ui/actions/action-surfaces.js.map +1 -1
- package/dist/ui/actions/error-actions.d.ts +32 -0
- package/dist/ui/actions/error-actions.d.ts.map +1 -0
- package/dist/ui/actions/error-actions.js +40 -0
- package/dist/ui/actions/error-actions.js.map +1 -0
- package/dist/ui/actions/index.d.ts +2 -0
- package/dist/ui/actions/index.d.ts.map +1 -1
- package/dist/ui/actions/index.js +2 -0
- package/dist/ui/actions/index.js.map +1 -1
- package/dist/ui/actions/shortcut.d.ts +13 -0
- package/dist/ui/actions/shortcut.d.ts.map +1 -0
- package/dist/ui/actions/shortcut.js +18 -0
- package/dist/ui/actions/shortcut.js.map +1 -0
- package/dist/ui/actions/use-verb-invoker.d.ts +50 -0
- package/dist/ui/actions/use-verb-invoker.d.ts.map +1 -0
- package/dist/ui/actions/use-verb-invoker.js +93 -0
- package/dist/ui/actions/use-verb-invoker.js.map +1 -0
- package/dist/ui/atoms/alert/alert-dismiss.d.ts +9 -0
- package/dist/ui/atoms/alert/alert-dismiss.d.ts.map +1 -0
- package/dist/ui/atoms/alert/alert-dismiss.js +28 -0
- package/dist/ui/atoms/alert/alert-dismiss.js.map +1 -0
- package/dist/ui/atoms/alert/alert.d.ts +24 -0
- package/dist/ui/atoms/alert/alert.d.ts.map +1 -0
- package/dist/ui/atoms/alert/alert.js +50 -0
- package/dist/ui/atoms/alert/alert.js.map +1 -0
- package/dist/ui/atoms/alert/index.d.ts +3 -0
- package/dist/ui/atoms/alert/index.d.ts.map +1 -0
- package/dist/ui/atoms/alert/index.js +4 -0
- package/dist/ui/atoms/alert/index.js.map +1 -0
- package/dist/ui/atoms/alert/tone.d.ts +8 -0
- package/dist/ui/atoms/alert/tone.d.ts.map +1 -0
- package/dist/ui/atoms/alert/tone.js +11 -0
- package/dist/ui/atoms/alert/tone.js.map +1 -0
- package/dist/ui/atoms/button/button-recipe.js +1 -1
- package/dist/ui/atoms/button/button-recipe.js.map +1 -1
- package/dist/ui/atoms/button/index.d.ts +1 -0
- package/dist/ui/atoms/button/index.d.ts.map +1 -1
- package/dist/ui/atoms/button/index.js +1 -0
- package/dist/ui/atoms/button/index.js.map +1 -1
- package/dist/ui/atoms/button/submit-button.d.ts +13 -0
- package/dist/ui/atoms/button/submit-button.d.ts.map +1 -0
- package/dist/ui/atoms/button/submit-button.js +29 -0
- package/dist/ui/atoms/button/submit-button.js.map +1 -0
- package/dist/ui/atoms/cell-value/cell-value.d.ts +6 -1
- package/dist/ui/atoms/cell-value/cell-value.d.ts.map +1 -1
- package/dist/ui/atoms/cell-value/cell-value.js +48 -16
- package/dist/ui/atoms/cell-value/cell-value.js.map +1 -1
- package/dist/ui/atoms/field/checkbox.d.ts.map +1 -1
- package/dist/ui/atoms/field/checkbox.js +21 -1
- package/dist/ui/atoms/field/checkbox.js.map +1 -1
- package/dist/ui/atoms/field/field.js +1 -1
- package/dist/ui/atoms/field/field.js.map +1 -1
- package/dist/ui/atoms/field-control/field-control.d.ts +7 -1
- package/dist/ui/atoms/field-control/field-control.d.ts.map +1 -1
- package/dist/ui/atoms/field-control/field-control.js +25 -3
- package/dist/ui/atoms/field-control/field-control.js.map +1 -1
- package/dist/ui/atoms/money/index.d.ts +4 -0
- package/dist/ui/atoms/money/index.d.ts.map +1 -0
- package/dist/ui/atoms/money/index.js +5 -0
- package/dist/ui/atoms/money/index.js.map +1 -0
- package/dist/ui/atoms/money/money-input.d.ts +19 -0
- package/dist/ui/atoms/money/money-input.d.ts.map +1 -0
- package/dist/ui/atoms/money/money-input.js +75 -0
- package/dist/ui/atoms/money/money-input.js.map +1 -0
- package/dist/ui/atoms/money/money-math.d.ts +21 -0
- package/dist/ui/atoms/money/money-math.d.ts.map +1 -0
- package/dist/ui/atoms/money/money-math.js +36 -0
- package/dist/ui/atoms/money/money-math.js.map +1 -0
- package/dist/ui/atoms/money/money.d.ts +19 -0
- package/dist/ui/atoms/money/money.d.ts.map +1 -0
- package/dist/ui/atoms/money/money.js +26 -0
- package/dist/ui/atoms/money/money.js.map +1 -0
- package/dist/ui/atoms/number-input/index.d.ts +3 -0
- package/dist/ui/atoms/number-input/index.d.ts.map +1 -0
- package/dist/ui/atoms/number-input/index.js +4 -0
- package/dist/ui/atoms/number-input/index.js.map +1 -0
- package/dist/ui/atoms/number-input/number-input.d.ts +10 -0
- package/dist/ui/atoms/number-input/number-input.d.ts.map +1 -0
- package/dist/ui/atoms/number-input/number-input.js +23 -0
- package/dist/ui/atoms/number-input/number-input.js.map +1 -0
- package/dist/ui/atoms/number-input/quantity-input.d.ts +29 -0
- package/dist/ui/atoms/number-input/quantity-input.d.ts.map +1 -0
- package/dist/ui/atoms/number-input/quantity-input.js +100 -0
- package/dist/ui/atoms/number-input/quantity-input.js.map +1 -0
- package/dist/ui/atoms/skeleton/skeleton.js +1 -1
- package/dist/ui/atoms/skeleton/skeleton.js.map +1 -1
- package/dist/ui/atoms/status-tag/index.d.ts +2 -0
- package/dist/ui/atoms/status-tag/index.d.ts.map +1 -0
- package/dist/ui/atoms/status-tag/index.js +3 -0
- package/dist/ui/atoms/status-tag/index.js.map +1 -0
- package/dist/ui/atoms/status-tag/status-tag.d.ts +17 -0
- package/dist/ui/atoms/status-tag/status-tag.d.ts.map +1 -0
- package/dist/ui/atoms/status-tag/status-tag.js +37 -0
- package/dist/ui/atoms/status-tag/status-tag.js.map +1 -0
- package/dist/ui/atoms/switch/switch.d.ts.map +1 -1
- package/dist/ui/atoms/switch/switch.js +6 -3
- package/dist/ui/atoms/switch/switch.js.map +1 -1
- package/dist/ui/atoms/table/table.d.ts +12 -3
- package/dist/ui/atoms/table/table.d.ts.map +1 -1
- package/dist/ui/atoms/table/table.js +59 -10
- package/dist/ui/atoms/table/table.js.map +1 -1
- package/dist/ui/atoms/text/text.d.ts +2 -2
- package/dist/ui/atoms/text/text.d.ts.map +1 -1
- package/dist/ui/atoms/text/text.js.map +1 -1
- package/dist/ui/molecules/action-bar/action-bar.d.ts +76 -0
- package/dist/ui/molecules/action-bar/action-bar.d.ts.map +1 -0
- package/dist/ui/molecules/action-bar/action-bar.js +218 -0
- package/dist/ui/molecules/action-bar/action-bar.js.map +1 -0
- package/dist/ui/molecules/action-bar/index.d.ts +2 -0
- package/dist/ui/molecules/action-bar/index.d.ts.map +1 -0
- package/dist/ui/molecules/action-bar/index.js +3 -0
- package/dist/ui/molecules/action-bar/index.js.map +1 -0
- package/dist/ui/molecules/breadcrumbs/breadcrumbs.d.ts +11 -2
- package/dist/ui/molecules/breadcrumbs/breadcrumbs.d.ts.map +1 -1
- package/dist/ui/molecules/breadcrumbs/breadcrumbs.js +9 -7
- package/dist/ui/molecules/breadcrumbs/breadcrumbs.js.map +1 -1
- package/dist/ui/molecules/bulk-action-bar/bulk-action-bar.d.ts.map +1 -1
- package/dist/ui/molecules/bulk-action-bar/bulk-action-bar.js +8 -10
- package/dist/ui/molecules/bulk-action-bar/bulk-action-bar.js.map +1 -1
- package/dist/ui/molecules/bulk-result/bulk-result.d.ts +3 -3
- package/dist/ui/molecules/bulk-result/bulk-result.js +4 -4
- package/dist/ui/molecules/bulk-result/bulk-result.js.map +1 -1
- package/dist/ui/molecules/column-settings/column-settings.js +1 -1
- package/dist/ui/molecules/column-settings/column-settings.js.map +1 -1
- package/dist/ui/molecules/confirm-dialog/confirm-dialog.d.ts +24 -9
- package/dist/ui/molecules/confirm-dialog/confirm-dialog.d.ts.map +1 -1
- package/dist/ui/molecules/confirm-dialog/confirm-dialog.js +38 -13
- package/dist/ui/molecules/confirm-dialog/confirm-dialog.js.map +1 -1
- package/dist/ui/molecules/description-list/description-list.d.ts +17 -0
- package/dist/ui/molecules/description-list/description-list.d.ts.map +1 -0
- package/dist/ui/molecules/description-list/description-list.js +36 -0
- package/dist/ui/molecules/description-list/description-list.js.map +1 -0
- package/dist/ui/molecules/description-list/index.d.ts +2 -0
- package/dist/ui/molecules/description-list/index.d.ts.map +1 -0
- package/dist/ui/molecules/description-list/index.js +3 -0
- package/dist/ui/molecules/description-list/index.js.map +1 -0
- package/dist/ui/molecules/drawer/drawer.js +3 -3
- package/dist/ui/molecules/drawer/drawer.js.map +1 -1
- package/dist/ui/molecules/empty-state/empty-state.d.ts +15 -0
- package/dist/ui/molecules/empty-state/empty-state.d.ts.map +1 -0
- package/dist/ui/molecules/empty-state/empty-state.js +29 -0
- package/dist/ui/molecules/empty-state/empty-state.js.map +1 -0
- package/dist/ui/molecules/empty-state/index.d.ts +2 -0
- package/dist/ui/molecules/empty-state/index.d.ts.map +1 -0
- package/dist/ui/molecules/empty-state/index.js +3 -0
- package/dist/ui/molecules/empty-state/index.js.map +1 -0
- package/dist/ui/molecules/entity-link-input/entity-link-input.d.ts.map +1 -1
- package/dist/ui/molecules/entity-link-input/entity-link-input.js +7 -4
- package/dist/ui/molecules/entity-link-input/entity-link-input.js.map +1 -1
- package/dist/ui/molecules/entity-link-picker/entity-link-picker.js +2 -2
- package/dist/ui/molecules/entity-link-picker/entity-link-picker.js.map +1 -1
- package/dist/ui/molecules/entity-record-card/entity-record-card.d.ts +33 -20
- package/dist/ui/molecules/entity-record-card/entity-record-card.d.ts.map +1 -1
- package/dist/ui/molecules/entity-record-card/entity-record-card.js +53 -20
- package/dist/ui/molecules/entity-record-card/entity-record-card.js.map +1 -1
- package/dist/ui/molecules/filter-builder/filter-builder.d.ts +8 -2
- package/dist/ui/molecules/filter-builder/filter-builder.d.ts.map +1 -1
- package/dist/ui/molecules/filter-builder/filter-builder.js +14 -5
- package/dist/ui/molecules/filter-builder/filter-builder.js.map +1 -1
- package/dist/ui/molecules/filter-chips/filter-chips.d.ts +16 -0
- package/dist/ui/molecules/filter-chips/filter-chips.d.ts.map +1 -0
- package/dist/ui/molecules/filter-chips/filter-chips.js +73 -0
- package/dist/ui/molecules/filter-chips/filter-chips.js.map +1 -0
- package/dist/ui/molecules/filter-chips/index.d.ts +2 -0
- package/dist/ui/molecules/filter-chips/index.d.ts.map +1 -0
- package/dist/ui/molecules/filter-chips/index.js +3 -0
- package/dist/ui/molecules/filter-chips/index.js.map +1 -0
- package/dist/ui/molecules/media-gallery/index.d.ts +2 -0
- package/dist/ui/molecules/media-gallery/index.d.ts.map +1 -0
- package/dist/ui/molecules/media-gallery/index.js +3 -0
- package/dist/ui/molecules/media-gallery/index.js.map +1 -0
- package/dist/ui/molecules/media-gallery/media-gallery.d.ts +31 -0
- package/dist/ui/molecules/media-gallery/media-gallery.d.ts.map +1 -0
- package/dist/ui/molecules/media-gallery/media-gallery.js +64 -0
- package/dist/ui/molecules/media-gallery/media-gallery.js.map +1 -0
- package/dist/ui/molecules/menu/menu.d.ts +11 -1
- package/dist/ui/molecules/menu/menu.d.ts.map +1 -1
- package/dist/ui/molecules/menu/menu.js +68 -6
- package/dist/ui/molecules/menu/menu.js.map +1 -1
- package/dist/ui/molecules/nav-menu/nav-menu.js +4 -4
- package/dist/ui/molecules/nav-menu/nav-menu.js.map +1 -1
- package/dist/ui/molecules/pagination/pagination.d.ts +5 -0
- package/dist/ui/molecules/pagination/pagination.d.ts.map +1 -1
- package/dist/ui/molecules/pagination/pagination.js +35 -18
- package/dist/ui/molecules/pagination/pagination.js.map +1 -1
- package/dist/ui/molecules/region-state/index.d.ts +2 -0
- package/dist/ui/molecules/region-state/index.d.ts.map +1 -0
- package/dist/ui/molecules/region-state/index.js +3 -0
- package/dist/ui/molecules/region-state/index.js.map +1 -0
- package/dist/ui/molecules/region-state/region-state.d.ts +42 -0
- package/dist/ui/molecules/region-state/region-state.d.ts.map +1 -0
- package/dist/ui/molecules/region-state/region-state.js +83 -0
- package/dist/ui/molecules/region-state/region-state.js.map +1 -0
- package/dist/ui/molecules/segmented-nav/segment-classes.d.ts +3 -3
- package/dist/ui/molecules/segmented-nav/segment-classes.d.ts.map +1 -1
- package/dist/ui/molecules/segmented-nav/segment-classes.js +2 -2
- package/dist/ui/molecules/segmented-nav/segment-classes.js.map +1 -1
- package/dist/ui/molecules/segmented-nav/segmented-nav.d.ts +14 -4
- package/dist/ui/molecules/segmented-nav/segmented-nav.d.ts.map +1 -1
- package/dist/ui/molecules/segmented-nav/segmented-nav.js +6 -5
- package/dist/ui/molecules/segmented-nav/segmented-nav.js.map +1 -1
- package/dist/ui/molecules/split-view/split-view.js +3 -3
- package/dist/ui/molecules/split-view/split-view.js.map +1 -1
- package/dist/ui/molecules/tab-strip/tab-strip.d.ts +51 -12
- package/dist/ui/molecules/tab-strip/tab-strip.d.ts.map +1 -1
- package/dist/ui/molecules/tab-strip/tab-strip.js +125 -34
- package/dist/ui/molecules/tab-strip/tab-strip.js.map +1 -1
- package/dist/ui/molecules/table-toolbar/table-toolbar.d.ts +3 -1
- package/dist/ui/molecules/table-toolbar/table-toolbar.d.ts.map +1 -1
- package/dist/ui/molecules/table-toolbar/table-toolbar.js +3 -2
- package/dist/ui/molecules/table-toolbar/table-toolbar.js.map +1 -1
- package/dist/ui/molecules/toast-region/toast-region.d.ts +63 -19
- package/dist/ui/molecules/toast-region/toast-region.d.ts.map +1 -1
- package/dist/ui/molecules/toast-region/toast-region.js +129 -42
- package/dist/ui/molecules/toast-region/toast-region.js.map +1 -1
- package/dist/ui/organisms/command-palette/command-palette.d.ts.map +1 -1
- package/dist/ui/organisms/command-palette/command-palette.js +25 -18
- package/dist/ui/organisms/command-palette/command-palette.js.map +1 -1
- package/dist/ui/organisms/entity-detail-grid/entity-detail-grid.d.ts.map +1 -1
- package/dist/ui/organisms/entity-detail-grid/entity-detail-grid.js +8 -3
- package/dist/ui/organisms/entity-detail-grid/entity-detail-grid.js.map +1 -1
- package/dist/ui/organisms/entity-form/entity-form.d.ts +1 -1
- package/dist/ui/organisms/entity-form/entity-form.d.ts.map +1 -1
- package/dist/ui/organisms/entity-form/entity-form.js +325 -215
- package/dist/ui/organisms/entity-form/entity-form.js.map +1 -1
- package/dist/ui/organisms/entity-form/entity-form.types.d.ts +8 -0
- package/dist/ui/organisms/entity-form/entity-form.types.d.ts.map +1 -1
- package/dist/ui/organisms/entity-form/entity-form.types.js.map +1 -1
- package/dist/ui/organisms/entity-table/entity-table.d.ts +1 -1
- package/dist/ui/organisms/entity-table/entity-table.d.ts.map +1 -1
- package/dist/ui/organisms/entity-table/entity-table.js +334 -198
- package/dist/ui/organisms/entity-table/entity-table.js.map +1 -1
- package/dist/ui/organisms/entity-table/entity-table.types.d.ts +19 -4
- package/dist/ui/organisms/entity-table/entity-table.types.d.ts.map +1 -1
- package/dist/ui/organisms/entity-table/entity-table.types.js.map +1 -1
- package/dist/ui/organisms/entity-table/header-sort.d.ts +18 -0
- package/dist/ui/organisms/entity-table/header-sort.d.ts.map +1 -0
- package/dist/ui/organisms/entity-table/header-sort.js +54 -0
- package/dist/ui/organisms/entity-table/header-sort.js.map +1 -0
- package/dist/ui/organisms/entity-table/pivot.d.ts +25 -0
- package/dist/ui/organisms/entity-table/pivot.d.ts.map +1 -0
- package/dist/ui/organisms/entity-table/pivot.js +106 -0
- package/dist/ui/organisms/entity-table/pivot.js.map +1 -0
- package/dist/ui/organisms/entity-table/use-result-announcement.d.ts +13 -0
- package/dist/ui/organisms/entity-table/use-result-announcement.d.ts.map +1 -0
- package/dist/ui/organisms/entity-table/use-result-announcement.js +31 -0
- package/dist/ui/organisms/entity-table/use-result-announcement.js.map +1 -0
- package/dist/ui/organisms/top-bar/live-indicator.d.ts +11 -0
- package/dist/ui/organisms/top-bar/live-indicator.d.ts.map +1 -0
- package/dist/ui/organisms/top-bar/live-indicator.js +46 -0
- package/dist/ui/organisms/top-bar/live-indicator.js.map +1 -0
- package/dist/ui/organisms/top-bar/top-bar.d.ts +11 -2
- package/dist/ui/organisms/top-bar/top-bar.d.ts.map +1 -1
- package/dist/ui/organisms/top-bar/top-bar.js +14 -5
- package/dist/ui/organisms/top-bar/top-bar.js.map +1 -1
- package/dist/ui/organisms/wizard/wizard.d.ts +1 -1
- package/dist/ui/organisms/wizard/wizard.d.ts.map +1 -1
- package/dist/ui/organisms/wizard/wizard.js +7 -1
- package/dist/ui/organisms/wizard/wizard.js.map +1 -1
- package/dist/ui/organisms/wizard/wizard.types.d.ts +8 -0
- package/dist/ui/organisms/wizard/wizard.types.d.ts.map +1 -1
- package/dist/ui/organisms/wizard/wizard.types.js.map +1 -1
- package/dist/ui/utils/live-updates.d.ts +24 -0
- package/dist/ui/utils/live-updates.d.ts.map +1 -0
- package/dist/ui/utils/live-updates.js +20 -0
- package/dist/ui/utils/live-updates.js.map +1 -0
- package/dist/ui/utils/use-focus-on-arrival.d.ts +14 -0
- package/dist/ui/utils/use-focus-on-arrival.d.ts.map +1 -0
- package/dist/ui/utils/use-focus-on-arrival.js +42 -0
- package/dist/ui/utils/use-focus-on-arrival.js.map +1 -0
- package/dist/ui/utils/use-hotkey.d.ts +44 -25
- package/dist/ui/utils/use-hotkey.d.ts.map +1 -1
- package/dist/ui/utils/use-hotkey.js +80 -48
- package/dist/ui/utils/use-hotkey.js.map +1 -1
- package/package.json +5 -5
- package/dist/ui/molecules/split-button/index.d.ts +0 -2
- package/dist/ui/molecules/split-button/index.d.ts.map +0 -1
- package/dist/ui/molecules/split-button/index.js +0 -3
- package/dist/ui/molecules/split-button/index.js.map +0 -1
- package/dist/ui/molecules/split-button/split-button.d.ts +0 -21
- package/dist/ui/molecules/split-button/split-button.d.ts.map +0 -1
- package/dist/ui/molecules/split-button/split-button.js +0 -35
- package/dist/ui/molecules/split-button/split-button.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/ui/molecules/filter-builder/filter-builder.tsx"],"sourcesContent":["'use client';\n\nimport {\n type Entity,\n type EntityFieldDescriptor,\n type EntityFilter,\n type FilterGroup,\n findQueriedDescriptor,\n type Locale,\n type LogicOperator,\n type MetaAccessorType,\n queryPropertyFor,\n} from '@entifix/core';\nimport { useCallback, useId, useState } from 'react';\n\nimport { useEnumLabel, useLocale, useT } from '../../../i18n/index.js';\nimport { Button } from '../../atoms/button/index.js';\nimport { Select, TextInput } from '../../atoms/field/index.js';\nimport { Text } from '../../atoms/text/index.js';\nimport {\n type EntityFilterOperator,\n operatorArity,\n operatorLabelKey,\n operatorsForType,\n} from './filter-operators.js';\n\n/**\n * One in-progress filter row. Values are held as strings because that is what\n * the inputs produce; they are coerced to the member's type only when the row\n * is converted into an {@link EntityFilter}.\n */\ninterface FilterDraft {\n key: string;\n property: string;\n operator: EntityFilterOperator;\n value: string;\n end: string;\n}\n\nfunction coerce(raw: string, type: MetaAccessorType): unknown {\n if (raw === '') return undefined;\n switch (type) {\n case 'number':\n return Number(raw);\n case 'boolean':\n return raw === 'true';\n case 'date':\n return new Date(raw);\n default:\n return raw;\n }\n}\n\n/**\n * Converts a draft row into the wire filter, or `undefined` while the row is\n * still incomplete — a half-typed row must not reach the load request.\n */\nfunction toFilter<TEntity extends Entity>(\n draft: FilterDraft,\n descriptor: EntityFieldDescriptor,\n locale: Locale,\n): EntityFilter<TEntity> | undefined {\n // A localized member is filtered in the reader's own locale.\n const property = queryPropertyFor(descriptor, locale) as keyof TEntity;\n\n switch (operatorArity(draft.operator)) {\n case 'none':\n return { property, operator: draft.operator } as EntityFilter<TEntity>;\n case 'list': {\n const values = draft.value\n .split(',')\n .map(entry => entry.trim())\n .filter(entry => entry !== '')\n .map(entry => coerce(entry, descriptor.type));\n return values.length === 0\n ? undefined\n : ({\n property,\n operator: draft.operator,\n values,\n } as EntityFilter<TEntity>);\n }\n case 'range': {\n const start = coerce(draft.value, descriptor.type);\n const end = coerce(draft.end, descriptor.type);\n return start === undefined || end === undefined\n ? undefined\n : ({\n property,\n operator: draft.operator,\n start,\n end,\n } as EntityFilter<TEntity>);\n }\n default: {\n const value = coerce(draft.value, descriptor.type);\n return value === undefined\n ? undefined\n : ({\n property,\n operator: draft.operator,\n value,\n } as EntityFilter<TEntity>);\n }\n }\n}\n\n/**\n * Rebuilds the editable rows from an already-applied filter, so reopening the\n * panel shows what is actually in effect rather than an empty form. Only the\n * flat rows this builder can express are restored — a group nested by some\n * other means is left to the caller to re-enter.\n */\nfunction toDrafts<TEntity extends Entity>(\n filtering: FilterGroup<TEntity> | undefined,\n idPrefix: string,\n descriptors: readonly EntityFieldDescriptor[],\n): FilterDraft[] {\n if (!filtering) return [];\n\n return filtering.values.flatMap((node, index) => {\n if (!('property' in node)) return [];\n const filter = node as {\n property: unknown;\n operator: EntityFilterOperator;\n value?: unknown;\n values?: unknown[];\n start?: unknown;\n end?: unknown;\n };\n const text = (value: unknown) =>\n value instanceof Date\n ? value.toISOString().slice(0, 10)\n : value === undefined\n ? ''\n : String(value);\n\n return [\n {\n key: `${idPrefix}-applied-${index}`,\n // Rows address members by accessor name; an applied filter carries the\n // wire key, or a locale path into a localized member.\n property:\n findQueriedDescriptor(descriptors, String(filter.property))?.name ??\n String(filter.property),\n operator: filter.operator,\n value: filter.values\n ? filter.values.map(text).join(', ')\n : text(filter.start ?? filter.value),\n end: text(filter.end),\n },\n ];\n });\n}\n\nexport interface FilterBuilderProps<TEntity extends Entity> {\n /** Filterable members, already resolved from the entity metadata. */\n descriptors: readonly EntityFieldDescriptor[];\n /** The filtering currently applied, used to seed the rows. */\n value?: FilterGroup<TEntity>;\n onChange: (filtering: FilterGroup<TEntity>) => void;\n}\n\n/**\n * Builds an `EntityFiltering` value from entity metadata: the member list, the\n * operators offered and the shape of the value control all come from each\n * member's descriptor, so no per-entity filter UI has to be written.\n *\n * Editing is a draft. `onChange` fires only on **Apply**, because the value\n * feeds a load request: emitting per keystroke would put one HTTP request on\n * the wire per character typed.\n */\nexport function FilterBuilder<TEntity extends Entity>({\n descriptors,\n value: applied,\n onChange,\n}: FilterBuilderProps<TEntity>) {\n const t = useT();\n const idPrefix = useId();\n const [logic, setLogic] = useState<LogicOperator>(applied?.operator ?? 'and');\n const [drafts, setDrafts] = useState<FilterDraft[]>(() =>\n toDrafts(applied, idPrefix, descriptors),\n );\n const locale = useLocale();\n\n const descriptorFor = useCallback(\n (name: string) => descriptors.find(entry => entry.name === name),\n [descriptors],\n );\n\n const apply = useCallback(\n (nextDrafts: FilterDraft[], nextLogic: LogicOperator) => {\n const values = nextDrafts\n .map(draft => {\n const descriptor = descriptorFor(draft.property);\n return descriptor\n ? toFilter<TEntity>(draft, descriptor, locale)\n : undefined;\n })\n .filter(\n (filter): filter is EntityFilter<TEntity> => filter !== undefined,\n );\n\n onChange({ operator: nextLogic, values });\n },\n [descriptorFor, locale, onChange],\n );\n\n /** Edits the draft only — nothing reaches the caller until Apply. */\n const update = (nextDrafts: FilterDraft[]) => setDrafts(nextDrafts);\n\n const addRow = () => {\n const first = descriptors[0];\n // Defence in depth: the component returns early for an empty descriptor\n // list, so this button does not exist without a first member.\n /* v8 ignore next */\n if (!first) return;\n update([\n ...drafts,\n {\n key: `${idPrefix}-${drafts.length}-${Date.now()}`,\n property: first.name,\n operator: operatorsForType(first.type)[0],\n value: '',\n end: '',\n },\n ]);\n };\n\n const patch = (key: string, changes: Partial<FilterDraft>) =>\n update(\n drafts.map(draft =>\n draft.key === key ? { ...draft, ...changes } : draft,\n ),\n );\n\n if (descriptors.length === 0) {\n return (\n <Text step={-1} tone=\"muted\">\n {t('filter.none')}\n </Text>\n );\n }\n\n return (\n <div className=\"flex flex-col gap-2xs\">\n <div className=\"flex items-center gap-2xs\">\n <Text step={-2} tone=\"muted\">\n {t('filter.match')}\n </Text>\n <Select\n aria-label={t('filter.matchAria')}\n value={logic}\n onChange={event => {\n setLogic(event.target.value as LogicOperator);\n }}\n >\n <option value=\"and\">{t('filter.all')}</option>\n <option value=\"or\">{t('filter.any')}</option>\n </Select>\n </div>\n\n {drafts.map(draft => {\n const descriptor = descriptorFor(draft.property);\n if (!descriptor) return null;\n const arity = operatorArity(draft.operator);\n\n return (\n <div key={draft.key} className=\"flex flex-wrap items-center gap-2xs\">\n <Select\n aria-label={t('filter.member')}\n value={draft.property}\n onChange={event => {\n const next = descriptorFor(event.target.value);\n // Defence in depth: the options are built from `descriptors`,\n // so a chosen value always resolves.\n /* v8 ignore next */\n if (!next) return;\n patch(draft.key, {\n property: next.name,\n // The previous operator may not exist for the new type.\n operator: operatorsForType(next.type)[0],\n value: '',\n end: '',\n });\n }}\n >\n {descriptors.map(entry => (\n <option key={entry.name} value={entry.name}>\n {entry.label}\n </option>\n ))}\n </Select>\n\n <Select\n aria-label={t('filter.operator')}\n value={draft.operator}\n onChange={event =>\n patch(draft.key, {\n operator: event.target.value as EntityFilterOperator,\n })\n }\n >\n {operatorsForType(descriptor.type).map(operator => (\n <option key={operator} value={operator}>\n {t(operatorLabelKey(operator))}\n </option>\n ))}\n </Select>\n\n {arity !== 'none' && (\n <FilterValueInput\n descriptor={descriptor}\n arity={arity}\n value={draft.value}\n end={draft.end}\n onValueChange={value => patch(draft.key, { value })}\n onEndChange={end => patch(draft.key, { end })}\n />\n )}\n\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-label={t('filter.remove')}\n onClick={() =>\n update(drafts.filter(entry => entry.key !== draft.key))\n }\n >\n ✕\n </Button>\n </div>\n );\n })}\n\n <div className=\"flex flex-wrap items-center gap-2xs\">\n <Button type=\"button\" variant=\"secondary\" size=\"sm\" onClick={addRow}>\n {t('filter.add')}\n </Button>\n <Button\n type=\"button\"\n variant=\"primary\"\n size=\"sm\"\n aria-label={t('filter.applyAria')}\n onClick={() => apply(drafts, logic)}\n >\n {t('filter.apply')}\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-label={t('filter.clearAria')}\n onClick={() => {\n setDrafts([]);\n setLogic('and');\n // Applying the emptied form is what actually clears the listing;\n // dropping the rows alone would leave the old filter in effect.\n apply([], 'and');\n }}\n >\n {t('filter.clear')}\n </Button>\n </div>\n </div>\n );\n}\n\ninterface FilterValueInputProps {\n descriptor: EntityFieldDescriptor;\n arity: ReturnType<typeof operatorArity>;\n value: string;\n end: string;\n onValueChange: (value: string) => void;\n onEndChange: (end: string) => void;\n}\n\n/** The value control the member's type calls for. */\nfunction FilterValueInput({\n descriptor,\n arity,\n value,\n end,\n onValueChange,\n onEndChange,\n}: FilterValueInputProps) {\n const t = useT();\n const enumLabel = useEnumLabel();\n\n if (descriptor.type === 'boolean') {\n return (\n <Select\n aria-label={t('filter.value')}\n value={value}\n onChange={event => onValueChange(event.target.value)}\n >\n <option value=\"\">—</option>\n <option value=\"true\">{t('value.yes')}</option>\n <option value=\"false\">{t('value.no')}</option>\n </Select>\n );\n }\n\n if (descriptor.type === 'enum' && descriptor.enumValues && arity !== 'list') {\n return (\n <Select\n aria-label={t('filter.value')}\n value={value}\n onChange={event => onValueChange(event.target.value)}\n >\n <option value=\"\">—</option>\n {descriptor.enumValues.map(option => (\n <option key={option} value={option}>\n {enumLabel(descriptor, option)}\n </option>\n ))}\n </Select>\n );\n }\n\n const inputType =\n descriptor.type === 'number'\n ? 'number'\n : descriptor.type === 'date'\n ? 'date'\n : 'text';\n\n return (\n <>\n <TextInput\n aria-label={t('filter.value')}\n type={arity === 'list' ? 'text' : inputType}\n placeholder={arity === 'list' ? t('filter.listPlaceholder') : undefined}\n value={value}\n onChange={event => onValueChange(event.target.value)}\n />\n {arity === 'range' && (\n <TextInput\n aria-label={t('filter.rangeEnd')}\n type={inputType}\n value={end}\n onChange={event => onEndChange(event.target.value)}\n />\n )}\n </>\n );\n}\n"],"names":["findQueriedDescriptor","queryPropertyFor","useCallback","useId","useState","useEnumLabel","useLocale","useT","Button","Select","TextInput","Text","operatorArity","operatorLabelKey","operatorsForType","coerce","raw","type","undefined","Number","Date","toFilter","draft","descriptor","locale","property","operator","values","value","split","map","entry","trim","filter","length","start","end","toDrafts","filtering","idPrefix","descriptors","flatMap","node","index","text","toISOString","slice","String","key","name","join","FilterBuilder","applied","onChange","t","logic","setLogic","drafts","setDrafts","descriptorFor","find","apply","nextDrafts","nextLogic","update","addRow","first","now","patch","changes","step","tone","div","className","aria-label","event","target","option","arity","next","label","FilterValueInput","onValueChange","onEndChange","variant","size","onClick","enumLabel","enumValues","inputType","placeholder"],"mappings":"AAAA;;;AAEA,SAKEA,qBAAqB,EAIrBC,gBAAgB,QACX,gBAAgB;AACvB,SAASC,WAAW,EAAEC,KAAK,EAAEC,QAAQ,QAAQ,QAAQ;AAErD,SAASC,YAAY,EAAEC,SAAS,EAAEC,IAAI,QAAQ,yBAAyB;AACvE,SAASC,MAAM,QAAQ,8BAA8B;AACrD,SAASC,MAAM,EAAEC,SAAS,QAAQ,6BAA6B;AAC/D,SAASC,IAAI,QAAQ,4BAA4B;AACjD,SAEEC,aAAa,EACbC,gBAAgB,EAChBC,gBAAgB,QACX,wBAAwB;AAe/B,SAASC,OAAOC,GAAW,EAAEC,IAAsB;IACjD,IAAID,QAAQ,IAAI,OAAOE;IACvB,OAAQD;QACN,KAAK;YACH,OAAOE,OAAOH;QAChB,KAAK;YACH,OAAOA,QAAQ;QACjB,KAAK;YACH,OAAO,IAAII,KAAKJ;QAClB;YACE,OAAOA;IACX;AACF;AAEA;;;CAGC,GACD,SAASK,SACPC,KAAkB,EAClBC,UAAiC,EACjCC,MAAc;IAEd,6DAA6D;IAC7D,MAAMC,WAAWxB,iBAAiBsB,YAAYC;IAE9C,OAAQZ,cAAcU,MAAMI,QAAQ;QAClC,KAAK;YACH,OAAO;gBAAED;gBAAUC,UAAUJ,MAAMI,QAAQ;YAAC;QAC9C,KAAK;YAAQ;gBACX,MAAMC,SAASL,MAAMM,KAAK,CACvBC,KAAK,CAAC,KACNC,GAAG,CAACC,CAAAA,QAASA,MAAMC,IAAI,IACvBC,MAAM,CAACF,CAAAA,QAASA,UAAU,IAC1BD,GAAG,CAACC,CAAAA,QAAShB,OAAOgB,OAAOR,WAAWN,IAAI;gBAC7C,OAAOU,OAAOO,MAAM,KAAK,IACrBhB,YACC;oBACCO;oBACAC,UAAUJ,MAAMI,QAAQ;oBACxBC;gBACF;YACN;QACA,KAAK;YAAS;gBACZ,MAAMQ,QAAQpB,OAAOO,MAAMM,KAAK,EAAEL,WAAWN,IAAI;gBACjD,MAAMmB,MAAMrB,OAAOO,MAAMc,GAAG,EAAEb,WAAWN,IAAI;gBAC7C,OAAOkB,UAAUjB,aAAakB,QAAQlB,YAClCA,YACC;oBACCO;oBACAC,UAAUJ,MAAMI,QAAQ;oBACxBS;oBACAC;gBACF;YACN;QACA;YAAS;gBACP,MAAMR,QAAQb,OAAOO,MAAMM,KAAK,EAAEL,WAAWN,IAAI;gBACjD,OAAOW,UAAUV,YACbA,YACC;oBACCO;oBACAC,UAAUJ,MAAMI,QAAQ;oBACxBE;gBACF;YACN;IACF;AACF;AAEA;;;;;CAKC,GACD,SAASS,SACPC,SAA2C,EAC3CC,QAAgB,EAChBC,WAA6C;IAE7C,IAAI,CAACF,WAAW,OAAO,EAAE;IAEzB,OAAOA,UAAUX,MAAM,CAACc,OAAO,CAAC,CAACC,MAAMC;kBA4BxBV;YALPjC;QAtBN,IAAI,CAAE,CAAA,cAAc0C,IAAG,GAAI,OAAO,EAAE;QACpC,MAAMT,SAASS;QAQf,MAAME,OAAO,CAAChB,QACZA,iBAAiBR,OACbQ,MAAMiB,WAAW,GAAGC,KAAK,CAAC,GAAG,MAC7BlB,UAAUV,YACR,KACA6B,OAAOnB;QAEf,OAAO;YACL;gBACEoB,KAAK,GAAGT,SAAS,SAAS,EAAEI,OAAO;gBACnC,uEAAuE;gBACvE,sDAAsD;gBACtDlB,QAAQ,WACNzB,yBAAAA,sBAAsBwC,aAAaO,OAAOd,OAAOR,QAAQ,uBAAzDzB,uBAA6DiD,IAAI,mBACjEF,OAAOd,OAAOR,QAAQ;gBACxBC,UAAUO,OAAOP,QAAQ;gBACzBE,OAAOK,OAAON,MAAM,GAChBM,OAAON,MAAM,CAACG,GAAG,CAACc,MAAMM,IAAI,CAAC,QAC7BN,MAAKX,gBAAAA,OAAOE,KAAK,YAAZF,gBAAgBA,OAAOL,KAAK;gBACrCQ,KAAKQ,KAAKX,OAAOG,GAAG;YACtB;SACD;IACH;AACF;AAUA;;;;;;;;CAQC,GACD,OAAO,SAASe,cAAsC,EACpDX,WAAW,EACXZ,OAAOwB,OAAO,EACdC,QAAQ,EACoB;;IAC5B,MAAMC,IAAI/C;IACV,MAAMgC,WAAWpC;IACjB,MAAM,CAACoD,OAAOC,SAAS,GAAGpD,iBAAwBgD,2BAAAA,QAAS1B,QAAQ,mBAAI;IACvE,MAAM,CAAC+B,QAAQC,UAAU,GAAGtD,SAAwB,IAClDiC,SAASe,SAASb,UAAUC;IAE9B,MAAMhB,SAASlB;IAEf,MAAMqD,gBAAgBzD,YACpB,CAAC+C,OAAiBT,YAAYoB,IAAI,CAAC7B,CAAAA,QAASA,MAAMkB,IAAI,KAAKA,OAC3D;QAACT;KAAY;IAGf,MAAMqB,QAAQ3D,YACZ,CAAC4D,YAA2BC;QAC1B,MAAMpC,SAASmC,WACZhC,GAAG,CAACR,CAAAA;YACH,MAAMC,aAAaoC,cAAcrC,MAAMG,QAAQ;YAC/C,OAAOF,aACHF,SAAkBC,OAAOC,YAAYC,UACrCN;QACN,GACCe,MAAM,CACL,CAACA,SAA4CA,WAAWf;QAG5DmC,SAAS;YAAE3B,UAAUqC;YAAWpC;QAAO;IACzC,GACA;QAACgC;QAAenC;QAAQ6B;KAAS;IAGnC,mEAAmE,GACnE,MAAMW,SAAS,CAACF,aAA8BJ,UAAUI;IAExD,MAAMG,SAAS;QACb,MAAMC,QAAQ1B,WAAW,CAAC,EAAE;QAC5B,wEAAwE;QACxE,8DAA8D;QAC9D,kBAAkB,GAClB,IAAI,CAAC0B,OAAO;QACZF,OAAO;eACFP;YACH;gBACET,KAAK,GAAGT,SAAS,CAAC,EAAEkB,OAAOvB,MAAM,CAAC,CAAC,EAAEd,KAAK+C,GAAG,IAAI;gBACjD1C,UAAUyC,MAAMjB,IAAI;gBACpBvB,UAAUZ,iBAAiBoD,MAAMjD,IAAI,CAAC,CAAC,EAAE;gBACzCW,OAAO;gBACPQ,KAAK;YACP;SACD;IACH;IAEA,MAAMgC,QAAQ,CAACpB,KAAaqB,UAC1BL,OACEP,OAAO3B,GAAG,CAACR,CAAAA,QACTA,MAAM0B,GAAG,KAAKA,MAAM,aAAK1B,OAAU+C,WAAY/C;IAIrD,IAAIkB,YAAYN,MAAM,KAAK,GAAG;QAC5B,qBACE,KAACvB;YAAK2D,MAAM,CAAC;YAAGC,MAAK;sBAClBjB,EAAE;;IAGT;IAEA,qBACE,MAACkB;QAAIC,WAAU;;0BACb,MAACD;gBAAIC,WAAU;;kCACb,KAAC9D;wBAAK2D,MAAM,CAAC;wBAAGC,MAAK;kCAClBjB,EAAE;;kCAEL,MAAC7C;wBACCiE,cAAYpB,EAAE;wBACd1B,OAAO2B;wBACPF,UAAUsB,CAAAA;4BACRnB,SAASmB,MAAMC,MAAM,CAAChD,KAAK;wBAC7B;;0CAEA,KAACiD;gCAAOjD,OAAM;0CAAO0B,EAAE;;0CACvB,KAACuB;gCAAOjD,OAAM;0CAAM0B,EAAE;;;;;;YAIzBG,OAAO3B,GAAG,CAACR,CAAAA;gBACV,MAAMC,aAAaoC,cAAcrC,MAAMG,QAAQ;gBAC/C,IAAI,CAACF,YAAY,OAAO;gBACxB,MAAMuD,QAAQlE,cAAcU,MAAMI,QAAQ;gBAE1C,qBACE,MAAC8C;oBAAoBC,WAAU;;sCAC7B,KAAChE;4BACCiE,cAAYpB,EAAE;4BACd1B,OAAON,MAAMG,QAAQ;4BACrB4B,UAAUsB,CAAAA;gCACR,MAAMI,OAAOpB,cAAcgB,MAAMC,MAAM,CAAChD,KAAK;gCAC7C,8DAA8D;gCAC9D,qCAAqC;gCACrC,kBAAkB,GAClB,IAAI,CAACmD,MAAM;gCACXX,MAAM9C,MAAM0B,GAAG,EAAE;oCACfvB,UAAUsD,KAAK9B,IAAI;oCACnB,wDAAwD;oCACxDvB,UAAUZ,iBAAiBiE,KAAK9D,IAAI,CAAC,CAAC,EAAE;oCACxCW,OAAO;oCACPQ,KAAK;gCACP;4BACF;sCAECI,YAAYV,GAAG,CAACC,CAAAA,sBACf,KAAC8C;oCAAwBjD,OAAOG,MAAMkB,IAAI;8CACvClB,MAAMiD,KAAK;mCADDjD,MAAMkB,IAAI;;sCAM3B,KAACxC;4BACCiE,cAAYpB,EAAE;4BACd1B,OAAON,MAAMI,QAAQ;4BACrB2B,UAAUsB,CAAAA,QACRP,MAAM9C,MAAM0B,GAAG,EAAE;oCACftB,UAAUiD,MAAMC,MAAM,CAAChD,KAAK;gCAC9B;sCAGDd,iBAAiBS,WAAWN,IAAI,EAAEa,GAAG,CAACJ,CAAAA,yBACrC,KAACmD;oCAAsBjD,OAAOF;8CAC3B4B,EAAEzC,iBAAiBa;mCADTA;;wBAMhBoD,UAAU,wBACT,KAACG;4BACC1D,YAAYA;4BACZuD,OAAOA;4BACPlD,OAAON,MAAMM,KAAK;4BAClBQ,KAAKd,MAAMc,GAAG;4BACd8C,eAAetD,CAAAA,QAASwC,MAAM9C,MAAM0B,GAAG,EAAE;oCAAEpB;gCAAM;4BACjDuD,aAAa/C,CAAAA,MAAOgC,MAAM9C,MAAM0B,GAAG,EAAE;oCAAEZ;gCAAI;;sCAI/C,KAAC5B;4BACCS,MAAK;4BACLmE,SAAQ;4BACRC,MAAK;4BACLX,cAAYpB,EAAE;4BACdgC,SAAS,IACPtB,OAAOP,OAAOxB,MAAM,CAACF,CAAAA,QAASA,MAAMiB,GAAG,KAAK1B,MAAM0B,GAAG;sCAExD;;;mBA7DO1B,MAAM0B,GAAG;YAkEvB;0BAEA,MAACwB;gBAAIC,WAAU;;kCACb,KAACjE;wBAAOS,MAAK;wBAASmE,SAAQ;wBAAYC,MAAK;wBAAKC,SAASrB;kCAC1DX,EAAE;;kCAEL,KAAC9C;wBACCS,MAAK;wBACLmE,SAAQ;wBACRC,MAAK;wBACLX,cAAYpB,EAAE;wBACdgC,SAAS,IAAMzB,MAAMJ,QAAQF;kCAE5BD,EAAE;;kCAEL,KAAC9C;wBACCS,MAAK;wBACLmE,SAAQ;wBACRC,MAAK;wBACLX,cAAYpB,EAAE;wBACdgC,SAAS;4BACP5B,UAAU,EAAE;4BACZF,SAAS;4BACT,iEAAiE;4BACjE,gEAAgE;4BAChEK,MAAM,EAAE,EAAE;wBACZ;kCAECP,EAAE;;;;;;AAKb;AAWA,mDAAmD,GACnD,SAAS2B,iBAAiB,EACxB1D,UAAU,EACVuD,KAAK,EACLlD,KAAK,EACLQ,GAAG,EACH8C,aAAa,EACbC,WAAW,EACW;IACtB,MAAM7B,IAAI/C;IACV,MAAMgF,YAAYlF;IAElB,IAAIkB,WAAWN,IAAI,KAAK,WAAW;QACjC,qBACE,MAACR;YACCiE,cAAYpB,EAAE;YACd1B,OAAOA;YACPyB,UAAUsB,CAAAA,QAASO,cAAcP,MAAMC,MAAM,CAAChD,KAAK;;8BAEnD,KAACiD;oBAAOjD,OAAM;8BAAG;;8BACjB,KAACiD;oBAAOjD,OAAM;8BAAQ0B,EAAE;;8BACxB,KAACuB;oBAAOjD,OAAM;8BAAS0B,EAAE;;;;IAG/B;IAEA,IAAI/B,WAAWN,IAAI,KAAK,UAAUM,WAAWiE,UAAU,IAAIV,UAAU,QAAQ;QAC3E,qBACE,MAACrE;YACCiE,cAAYpB,EAAE;YACd1B,OAAOA;YACPyB,UAAUsB,CAAAA,QAASO,cAAcP,MAAMC,MAAM,CAAChD,KAAK;;8BAEnD,KAACiD;oBAAOjD,OAAM;8BAAG;;gBAChBL,WAAWiE,UAAU,CAAC1D,GAAG,CAAC+C,CAAAA,uBACzB,KAACA;wBAAoBjD,OAAOiD;kCACzBU,UAAUhE,YAAYsD;uBADZA;;;IAMrB;IAEA,MAAMY,YACJlE,WAAWN,IAAI,KAAK,WAChB,WACAM,WAAWN,IAAI,KAAK,SAClB,SACA;IAER,qBACE;;0BACE,KAACP;gBACCgE,cAAYpB,EAAE;gBACdrC,MAAM6D,UAAU,SAAS,SAASW;gBAClCC,aAAaZ,UAAU,SAASxB,EAAE,4BAA4BpC;gBAC9DU,OAAOA;gBACPyB,UAAUsB,CAAAA,QAASO,cAAcP,MAAMC,MAAM,CAAChD,KAAK;;YAEpDkD,UAAU,yBACT,KAACpE;gBACCgE,cAAYpB,EAAE;gBACdrC,MAAMwE;gBACN7D,OAAOQ;gBACPiB,UAAUsB,CAAAA,QAASQ,YAAYR,MAAMC,MAAM,CAAChD,KAAK;;;;AAK3D"}
|
|
1
|
+
{"version":3,"sources":["../../../../src/ui/molecules/filter-builder/filter-builder.tsx"],"sourcesContent":["'use client';\n\nimport {\n type Entity,\n type EntityFacetCounts,\n type EntityFacetValue,\n type EntityFieldDescriptor,\n type EntityFilter,\n type FilterGroup,\n findQueriedDescriptor,\n type Locale,\n type LogicOperator,\n type MetaAccessorType,\n queryPropertyFor,\n} from '@entifix/core';\nimport { useCallback, useId, useState } from 'react';\n\nimport { useEnumLabel, useLocale, useT } from '../../../i18n/index.js';\nimport { Button } from '../../atoms/button/index.js';\nimport { Select, TextInput } from '../../atoms/field/index.js';\nimport { Text } from '../../atoms/text/index.js';\nimport {\n type EntityFilterOperator,\n operatorArity,\n operatorLabelKey,\n operatorsForType,\n} from './filter-operators.js';\n\n/**\n * One in-progress filter row. Values are held as strings because that is what\n * the inputs produce; they are coerced to the member's type only when the row\n * is converted into an {@link EntityFilter}.\n */\ninterface FilterDraft {\n key: string;\n property: string;\n operator: EntityFilterOperator;\n value: string;\n end: string;\n}\n\nfunction coerce(raw: string, type: MetaAccessorType): unknown {\n if (raw === '') return undefined;\n switch (type) {\n case 'number':\n return Number(raw);\n case 'boolean':\n return raw === 'true';\n case 'date':\n return new Date(raw);\n default:\n return raw;\n }\n}\n\n/**\n * Converts a draft row into the wire filter, or `undefined` while the row is\n * still incomplete — a half-typed row must not reach the load request.\n */\nfunction toFilter<TEntity extends Entity>(\n draft: FilterDraft,\n descriptor: EntityFieldDescriptor,\n locale: Locale,\n): EntityFilter<TEntity> | undefined {\n // A localized member is filtered in the reader's own locale.\n const property = queryPropertyFor(descriptor, locale) as keyof TEntity;\n\n switch (operatorArity(draft.operator)) {\n case 'none':\n return { property, operator: draft.operator } as EntityFilter<TEntity>;\n case 'list': {\n const values = draft.value\n .split(',')\n .map(entry => entry.trim())\n .filter(entry => entry !== '')\n .map(entry => coerce(entry, descriptor.type));\n return values.length === 0\n ? undefined\n : ({\n property,\n operator: draft.operator,\n values,\n } as EntityFilter<TEntity>);\n }\n case 'range': {\n const start = coerce(draft.value, descriptor.type);\n const end = coerce(draft.end, descriptor.type);\n return start === undefined || end === undefined\n ? undefined\n : ({\n property,\n operator: draft.operator,\n start,\n end,\n } as EntityFilter<TEntity>);\n }\n default: {\n const value = coerce(draft.value, descriptor.type);\n return value === undefined\n ? undefined\n : ({\n property,\n operator: draft.operator,\n value,\n } as EntityFilter<TEntity>);\n }\n }\n}\n\n/**\n * Rebuilds the editable rows from an already-applied filter, so reopening the\n * panel shows what is actually in effect rather than an empty form. Only the\n * flat rows this builder can express are restored — a group nested by some\n * other means is left to the caller to re-enter.\n */\nfunction toDrafts<TEntity extends Entity>(\n filtering: FilterGroup<TEntity> | undefined,\n idPrefix: string,\n descriptors: readonly EntityFieldDescriptor[],\n): FilterDraft[] {\n if (!filtering) return [];\n\n return filtering.values.flatMap((node, index) => {\n if (!('property' in node)) return [];\n const filter = node as {\n property: unknown;\n operator: EntityFilterOperator;\n value?: unknown;\n values?: unknown[];\n start?: unknown;\n end?: unknown;\n };\n const text = (value: unknown) =>\n value instanceof Date\n ? value.toISOString().slice(0, 10)\n : value === undefined\n ? ''\n : String(value);\n\n return [\n {\n key: `${idPrefix}-applied-${index}`,\n // Rows address members by accessor name; an applied filter carries the\n // wire key, or a locale path into a localized member.\n property:\n findQueriedDescriptor(descriptors, String(filter.property))?.name ??\n String(filter.property),\n operator: filter.operator,\n value: filter.values\n ? filter.values.map(text).join(', ')\n : text(filter.start ?? filter.value),\n end: text(filter.end),\n },\n ];\n });\n}\n\nexport interface FilterBuilderProps<TEntity extends Entity> {\n /** Filterable members, already resolved from the entity metadata. */\n descriptors: readonly EntityFieldDescriptor[];\n /** The filtering currently applied, used to seed the rows. */\n value?: FilterGroup<TEntity>;\n onChange: (filtering: FilterGroup<TEntity>) => void;\n /**\n * How many records hold each value, by member — shown beside an enum's or a\n * boolean's choices, so a reader sees what a choice would keep before\n * applying it ([ADR 0012](../../../../../../../docs/adr/0012-facet-counts-are-a-port-of-their-own.md)).\n */\n facets?: EntityFacetCounts;\n}\n\n/**\n * Builds an `EntityFiltering` value from entity metadata: the member list, the\n * operators offered and the shape of the value control all come from each\n * member's descriptor, so no per-entity filter UI has to be written.\n *\n * Editing is a draft. `onChange` fires only on **Apply**, because the value\n * feeds a load request: emitting per keystroke would put one HTTP request on\n * the wire per character typed.\n */\nexport function FilterBuilder<TEntity extends Entity>({\n descriptors,\n value: applied,\n onChange,\n facets,\n}: FilterBuilderProps<TEntity>) {\n const t = useT();\n const idPrefix = useId();\n const [logic, setLogic] = useState<LogicOperator>(applied?.operator ?? 'and');\n const [drafts, setDrafts] = useState<FilterDraft[]>(() =>\n toDrafts(applied, idPrefix, descriptors),\n );\n const locale = useLocale();\n\n const descriptorFor = useCallback(\n (name: string) => descriptors.find(entry => entry.name === name),\n [descriptors],\n );\n\n const apply = useCallback(\n (nextDrafts: FilterDraft[], nextLogic: LogicOperator) => {\n const values = nextDrafts\n .map(draft => {\n const descriptor = descriptorFor(draft.property);\n return descriptor\n ? toFilter<TEntity>(draft, descriptor, locale)\n : undefined;\n })\n .filter(\n (filter): filter is EntityFilter<TEntity> => filter !== undefined,\n );\n\n onChange({ operator: nextLogic, values });\n },\n [descriptorFor, locale, onChange],\n );\n\n /** Edits the draft only — nothing reaches the caller until Apply. */\n const update = (nextDrafts: FilterDraft[]) => setDrafts(nextDrafts);\n\n const addRow = () => {\n const first = descriptors[0];\n // Defence in depth: the component returns early for an empty descriptor\n // list, so this button does not exist without a first member.\n /* v8 ignore next */\n if (!first) return;\n update([\n ...drafts,\n {\n key: `${idPrefix}-${drafts.length}-${Date.now()}`,\n property: first.name,\n operator: operatorsForType(first.type)[0],\n value: '',\n end: '',\n },\n ]);\n };\n\n const patch = (key: string, changes: Partial<FilterDraft>) =>\n update(\n drafts.map(draft =>\n draft.key === key ? { ...draft, ...changes } : draft,\n ),\n );\n\n if (descriptors.length === 0) {\n return (\n <Text step={-1} tone=\"muted\">\n {t('filter.none')}\n </Text>\n );\n }\n\n return (\n <div className=\"flex flex-col gap-2xs\">\n <div className=\"flex items-center gap-2xs\">\n <Text step={-2} tone=\"muted\">\n {t('filter.match')}\n </Text>\n <Select\n aria-label={t('filter.matchAria')}\n value={logic}\n onChange={event => {\n setLogic(event.target.value as LogicOperator);\n }}\n >\n <option value=\"and\">{t('filter.all')}</option>\n <option value=\"or\">{t('filter.any')}</option>\n </Select>\n </div>\n\n {drafts.map(draft => {\n const descriptor = descriptorFor(draft.property);\n if (!descriptor) return null;\n const arity = operatorArity(draft.operator);\n\n return (\n <div key={draft.key} className=\"flex flex-wrap items-center gap-2xs\">\n <Select\n aria-label={t('filter.member')}\n value={draft.property}\n onChange={event => {\n const next = descriptorFor(event.target.value);\n // Defence in depth: the options are built from `descriptors`,\n // so a chosen value always resolves.\n /* v8 ignore next */\n if (!next) return;\n patch(draft.key, {\n property: next.name,\n // The previous operator may not exist for the new type.\n operator: operatorsForType(next.type)[0],\n value: '',\n end: '',\n });\n }}\n >\n {descriptors.map(entry => (\n <option key={entry.name} value={entry.name}>\n {entry.label}\n </option>\n ))}\n </Select>\n\n <Select\n aria-label={t('filter.operator')}\n value={draft.operator}\n onChange={event =>\n patch(draft.key, {\n operator: event.target.value as EntityFilterOperator,\n })\n }\n >\n {operatorsForType(descriptor.type).map(operator => (\n <option key={operator} value={operator}>\n {t(operatorLabelKey(operator))}\n </option>\n ))}\n </Select>\n\n {arity !== 'none' && (\n <FilterValueInput\n descriptor={descriptor}\n counts={facets?.[descriptor.name]}\n arity={arity}\n value={draft.value}\n end={draft.end}\n onValueChange={value => patch(draft.key, { value })}\n onEndChange={end => patch(draft.key, { end })}\n />\n )}\n\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-label={t('filter.remove')}\n onClick={() =>\n update(drafts.filter(entry => entry.key !== draft.key))\n }\n >\n ✕\n </Button>\n </div>\n );\n })}\n\n <div className=\"flex flex-wrap items-center gap-2xs\">\n <Button type=\"button\" variant=\"secondary\" size=\"sm\" onClick={addRow}>\n {t('filter.add')}\n </Button>\n <Button\n type=\"button\"\n variant=\"primary\"\n size=\"sm\"\n aria-label={t('filter.applyAria')}\n onClick={() => apply(drafts, logic)}\n >\n {t('filter.apply')}\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-label={t('filter.clearAria')}\n onClick={() => {\n setDrafts([]);\n setLogic('and');\n // Applying the emptied form is what actually clears the listing;\n // dropping the rows alone would leave the old filter in effect.\n apply([], 'and');\n }}\n >\n {t('filter.clear')}\n </Button>\n </div>\n </div>\n );\n}\n\ninterface FilterValueInputProps {\n descriptor: EntityFieldDescriptor;\n counts?: readonly EntityFacetValue[];\n arity: ReturnType<typeof operatorArity>;\n value: string;\n end: string;\n onValueChange: (value: string) => void;\n onEndChange: (end: string) => void;\n}\n\n/** The value control the member's type calls for. */\nfunction FilterValueInput({\n descriptor,\n counts,\n arity,\n value,\n end,\n onValueChange,\n onEndChange,\n}: FilterValueInputProps) {\n const t = useT();\n const enumLabel = useEnumLabel();\n /** A choice's label, with how many records hold it when that is known. */\n const counted = (label: string, option: string) => {\n const count = counts?.find(entry => String(entry.value) === option)?.count;\n return count === undefined\n ? label\n : t('filter.withCount', { label, count });\n };\n\n if (descriptor.type === 'boolean') {\n return (\n <Select\n aria-label={t('filter.value')}\n value={value}\n onChange={event => onValueChange(event.target.value)}\n >\n <option value=\"\">—</option>\n <option value=\"true\">{counted(t('value.yes'), 'true')}</option>\n <option value=\"false\">{counted(t('value.no'), 'false')}</option>\n </Select>\n );\n }\n\n if (descriptor.type === 'enum' && descriptor.enumValues && arity !== 'list') {\n return (\n <Select\n aria-label={t('filter.value')}\n value={value}\n onChange={event => onValueChange(event.target.value)}\n >\n <option value=\"\">—</option>\n {descriptor.enumValues.map(option => (\n <option key={option} value={option}>\n {counted(enumLabel(descriptor, option), option)}\n </option>\n ))}\n </Select>\n );\n }\n\n const inputType =\n descriptor.type === 'number'\n ? 'number'\n : descriptor.type === 'date'\n ? 'date'\n : 'text';\n\n return (\n <>\n <TextInput\n aria-label={t('filter.value')}\n type={arity === 'list' ? 'text' : inputType}\n placeholder={arity === 'list' ? t('filter.listPlaceholder') : undefined}\n value={value}\n onChange={event => onValueChange(event.target.value)}\n />\n {arity === 'range' && (\n <TextInput\n aria-label={t('filter.rangeEnd')}\n type={inputType}\n value={end}\n onChange={event => onEndChange(event.target.value)}\n />\n )}\n </>\n );\n}\n"],"names":["findQueriedDescriptor","queryPropertyFor","useCallback","useId","useState","useEnumLabel","useLocale","useT","Button","Select","TextInput","Text","operatorArity","operatorLabelKey","operatorsForType","coerce","raw","type","undefined","Number","Date","toFilter","draft","descriptor","locale","property","operator","values","value","split","map","entry","trim","filter","length","start","end","toDrafts","filtering","idPrefix","descriptors","flatMap","node","index","text","toISOString","slice","String","key","name","join","FilterBuilder","applied","onChange","facets","t","logic","setLogic","drafts","setDrafts","descriptorFor","find","apply","nextDrafts","nextLogic","update","addRow","first","now","patch","changes","step","tone","div","className","aria-label","event","target","option","arity","next","label","FilterValueInput","counts","onValueChange","onEndChange","variant","size","onClick","enumLabel","counted","count","enumValues","inputType","placeholder"],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type Entity, type EntityFieldDescriptor, type FilterGroup } from '@entifix/core';
|
|
2
|
+
export interface FilterChipsProps<TEntity extends Entity> {
|
|
3
|
+
/** Filterable members, to name each filter by its column. */
|
|
4
|
+
descriptors: readonly EntityFieldDescriptor[];
|
|
5
|
+
/** The filtering in effect. */
|
|
6
|
+
value?: FilterGroup<TEntity>;
|
|
7
|
+
onChange: (filtering: FilterGroup<TEntity>) => void;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The filters in effect, one removable chip each, and Clear all — so what
|
|
11
|
+
* narrowed the list is visible without opening the builder, and each can be
|
|
12
|
+
* dropped in one step. A nested group, which the builder cannot express, has
|
|
13
|
+
* no chip; Clear all still removes it.
|
|
14
|
+
*/
|
|
15
|
+
export declare function FilterChips<TEntity extends Entity>({ descriptors, value, onChange, }: FilterChipsProps<TEntity>): import("react").JSX.Element | null;
|
|
16
|
+
//# sourceMappingURL=filter-chips.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter-chips.d.ts","sourceRoot":"","sources":["../../../../src/ui/molecules/filter-chips/filter-chips.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,MAAM,EACX,KAAK,qBAAqB,EAC1B,KAAK,WAAW,EAEjB,MAAM,eAAe,CAAC;AASvB,MAAM,WAAW,gBAAgB,CAAC,OAAO,SAAS,MAAM;IACtD,6DAA6D;IAC7D,WAAW,EAAE,SAAS,qBAAqB,EAAE,CAAC;IAC9C,+BAA+B;IAC/B,KAAK,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC;IAC7B,QAAQ,EAAE,CAAC,SAAS,EAAE,WAAW,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC;CACrD;AAWD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,OAAO,SAAS,MAAM,EAAE,EAClD,WAAW,EACX,KAAK,EACL,QAAQ,GACT,EAAE,gBAAgB,CAAC,OAAO,CAAC,sCA6E3B"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { findQueriedDescriptor } from '@entifix/core';
|
|
4
|
+
import { useEnumLabel, useT } from '../../../i18n/index.js';
|
|
5
|
+
import { Button } from '../../atoms/button/index.js';
|
|
6
|
+
import { operatorLabelKey } from '../filter-builder/filter-operators.js';
|
|
7
|
+
/**
|
|
8
|
+
* The filters in effect, one removable chip each, and Clear all — so what
|
|
9
|
+
* narrowed the list is visible without opening the builder, and each can be
|
|
10
|
+
* dropped in one step. A nested group, which the builder cannot express, has
|
|
11
|
+
* no chip; Clear all still removes it.
|
|
12
|
+
*/ export function FilterChips({ descriptors, value, onChange }) {
|
|
13
|
+
const t = useT();
|
|
14
|
+
const enumLabel = useEnumLabel();
|
|
15
|
+
if (value === undefined || value.values.length === 0) return null;
|
|
16
|
+
const text = (descriptor, raw)=>raw instanceof Date ? raw.toISOString().slice(0, 10) : typeof raw === 'boolean' ? t(raw ? 'value.yes' : 'value.no') : (descriptor == null ? void 0 : descriptor.type) === 'enum' ? enumLabel(descriptor, String(raw)) : String(raw);
|
|
17
|
+
const chips = value.values.flatMap((node, index)=>{
|
|
18
|
+
var _ref;
|
|
19
|
+
if (!('property' in node)) return [];
|
|
20
|
+
const filter = node;
|
|
21
|
+
const descriptor = findQueriedDescriptor(descriptors, String(filter.property));
|
|
22
|
+
const operand = filter.values !== undefined ? filter.values.map((entry)=>text(descriptor, entry)).join(', ') : filter.start !== undefined ? `${text(descriptor, filter.start)} – ${text(descriptor, filter.end)}` : filter.value === undefined ? '' : text(descriptor, filter.value);
|
|
23
|
+
const label = [
|
|
24
|
+
(_ref = descriptor == null ? void 0 : descriptor.label) != null ? _ref : String(filter.property),
|
|
25
|
+
t(operatorLabelKey(filter.operator)),
|
|
26
|
+
operand
|
|
27
|
+
].filter((part)=>part !== '').join(' ');
|
|
28
|
+
return [
|
|
29
|
+
{
|
|
30
|
+
index,
|
|
31
|
+
label
|
|
32
|
+
}
|
|
33
|
+
];
|
|
34
|
+
});
|
|
35
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
36
|
+
role: "group",
|
|
37
|
+
"aria-label": t('filter.applied'),
|
|
38
|
+
className: "flex flex-wrap items-center gap-3xs",
|
|
39
|
+
children: [
|
|
40
|
+
chips.map((chip)=>/*#__PURE__*/ _jsxs("span", {
|
|
41
|
+
className: "inline-flex items-center gap-3xs rounded-full border border-border bg-surface ps-xs text-step-sm text-content",
|
|
42
|
+
children: [
|
|
43
|
+
chip.label,
|
|
44
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
45
|
+
type: "button",
|
|
46
|
+
variant: "ghost",
|
|
47
|
+
size: "sm",
|
|
48
|
+
"aria-label": t('filter.removeChip', {
|
|
49
|
+
filter: chip.label
|
|
50
|
+
}),
|
|
51
|
+
onClick: ()=>onChange({
|
|
52
|
+
operator: value.operator,
|
|
53
|
+
values: value.values.filter((_, at)=>at !== chip.index)
|
|
54
|
+
}),
|
|
55
|
+
children: "✕"
|
|
56
|
+
})
|
|
57
|
+
]
|
|
58
|
+
}, chip.index)),
|
|
59
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
60
|
+
type: "button",
|
|
61
|
+
variant: "ghost",
|
|
62
|
+
size: "sm",
|
|
63
|
+
onClick: ()=>onChange({
|
|
64
|
+
operator: value.operator,
|
|
65
|
+
values: []
|
|
66
|
+
}),
|
|
67
|
+
children: t('filter.clearAll')
|
|
68
|
+
})
|
|
69
|
+
]
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
//# sourceMappingURL=filter-chips.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/ui/molecules/filter-chips/filter-chips.tsx"],"sourcesContent":["'use client';\n\nimport {\n type Entity,\n type EntityFieldDescriptor,\n type FilterGroup,\n findQueriedDescriptor,\n} from '@entifix/core';\n\nimport { useEnumLabel, useT } from '../../../i18n/index.js';\nimport { Button } from '../../atoms/button/index.js';\nimport {\n type EntityFilterOperator,\n operatorLabelKey,\n} from '../filter-builder/filter-operators.js';\n\nexport interface FilterChipsProps<TEntity extends Entity> {\n /** Filterable members, to name each filter by its column. */\n descriptors: readonly EntityFieldDescriptor[];\n /** The filtering in effect. */\n value?: FilterGroup<TEntity>;\n onChange: (filtering: FilterGroup<TEntity>) => void;\n}\n\ninterface AppliedFilter {\n readonly property: unknown;\n readonly operator: EntityFilterOperator;\n readonly value?: unknown;\n readonly values?: readonly unknown[];\n readonly start?: unknown;\n readonly end?: unknown;\n}\n\n/**\n * The filters in effect, one removable chip each, and Clear all — so what\n * narrowed the list is visible without opening the builder, and each can be\n * dropped in one step. A nested group, which the builder cannot express, has\n * no chip; Clear all still removes it.\n */\nexport function FilterChips<TEntity extends Entity>({\n descriptors,\n value,\n onChange,\n}: FilterChipsProps<TEntity>) {\n const t = useT();\n const enumLabel = useEnumLabel();\n if (value === undefined || value.values.length === 0) return null;\n\n const text = (descriptor: EntityFieldDescriptor | undefined, raw: unknown) =>\n raw instanceof Date\n ? raw.toISOString().slice(0, 10)\n : typeof raw === 'boolean'\n ? t(raw ? 'value.yes' : 'value.no')\n : descriptor?.type === 'enum'\n ? enumLabel(descriptor, String(raw))\n : String(raw);\n\n const chips = value.values.flatMap((node, index) => {\n if (!('property' in node)) return [];\n const filter = node as AppliedFilter;\n const descriptor = findQueriedDescriptor(\n descriptors,\n String(filter.property),\n );\n const operand =\n filter.values !== undefined\n ? filter.values.map(entry => text(descriptor, entry)).join(', ')\n : filter.start !== undefined\n ? `${text(descriptor, filter.start)} – ${text(descriptor, filter.end)}`\n : filter.value === undefined\n ? ''\n : text(descriptor, filter.value);\n const label = [\n descriptor?.label ?? String(filter.property),\n t(operatorLabelKey(filter.operator)),\n operand,\n ]\n .filter(part => part !== '')\n .join(' ');\n return [{ index, label }];\n });\n\n return (\n <div\n role=\"group\"\n aria-label={t('filter.applied')}\n className=\"flex flex-wrap items-center gap-3xs\"\n >\n {chips.map(chip => (\n <span\n key={chip.index}\n className=\"inline-flex items-center gap-3xs rounded-full border border-border bg-surface ps-xs text-step-sm text-content\"\n >\n {chip.label}\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-label={t('filter.removeChip', { filter: chip.label })}\n onClick={() =>\n onChange({\n operator: value.operator,\n values: value.values.filter((_, at) => at !== chip.index),\n })\n }\n >\n ✕\n </Button>\n </span>\n ))}\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => onChange({ operator: value.operator, values: [] })}\n >\n {t('filter.clearAll')}\n </Button>\n </div>\n );\n}\n"],"names":["findQueriedDescriptor","useEnumLabel","useT","Button","operatorLabelKey","FilterChips","descriptors","value","onChange","t","enumLabel","undefined","values","length","text","descriptor","raw","Date","toISOString","slice","type","String","chips","flatMap","node","index","filter","property","operand","map","entry","join","start","end","label","operator","part","div","role","aria-label","className","chip","span","variant","size","onClick","_","at"],"mappings":"AAAA;;AAEA,SAIEA,qBAAqB,QAChB,gBAAgB;AAEvB,SAASC,YAAY,EAAEC,IAAI,QAAQ,yBAAyB;AAC5D,SAASC,MAAM,QAAQ,8BAA8B;AACrD,SAEEC,gBAAgB,QACX,wCAAwC;AAmB/C;;;;;CAKC,GACD,OAAO,SAASC,YAAoC,EAClDC,WAAW,EACXC,KAAK,EACLC,QAAQ,EACkB;IAC1B,MAAMC,IAAIP;IACV,MAAMQ,YAAYT;IAClB,IAAIM,UAAUI,aAAaJ,MAAMK,MAAM,CAACC,MAAM,KAAK,GAAG,OAAO;IAE7D,MAAMC,OAAO,CAACC,YAA+CC,MAC3DA,eAAeC,OACXD,IAAIE,WAAW,GAAGC,KAAK,CAAC,GAAG,MAC3B,OAAOH,QAAQ,YACbP,EAAEO,MAAM,cAAc,cACtBD,CAAAA,8BAAAA,WAAYK,IAAI,MAAK,SACnBV,UAAUK,YAAYM,OAAOL,QAC7BK,OAAOL;IAEjB,MAAMM,QAAQf,MAAMK,MAAM,CAACW,OAAO,CAAC,CAACC,MAAMC;;QACxC,IAAI,CAAE,CAAA,cAAcD,IAAG,GAAI,OAAO,EAAE;QACpC,MAAME,SAASF;QACf,MAAMT,aAAaf,sBACjBM,aACAe,OAAOK,OAAOC,QAAQ;QAExB,MAAMC,UACJF,OAAOd,MAAM,KAAKD,YACde,OAAOd,MAAM,CAACiB,GAAG,CAACC,CAAAA,QAAShB,KAAKC,YAAYe,QAAQC,IAAI,CAAC,QACzDL,OAAOM,KAAK,KAAKrB,YACf,GAAGG,KAAKC,YAAYW,OAAOM,KAAK,EAAE,GAAG,EAAElB,KAAKC,YAAYW,OAAOO,GAAG,GAAG,GACrEP,OAAOnB,KAAK,KAAKI,YACf,KACAG,KAAKC,YAAYW,OAAOnB,KAAK;QACvC,MAAM2B,QAAQ;oBACZnB,8BAAAA,WAAYmB,KAAK,mBAAIb,OAAOK,OAAOC,QAAQ;YAC3ClB,EAAEL,iBAAiBsB,OAAOS,QAAQ;YAClCP;SACD,CACEF,MAAM,CAACU,CAAAA,OAAQA,SAAS,IACxBL,IAAI,CAAC;QACR,OAAO;YAAC;gBAAEN;gBAAOS;YAAM;SAAE;IAC3B;IAEA,qBACE,MAACG;QACCC,MAAK;QACLC,cAAY9B,EAAE;QACd+B,WAAU;;YAETlB,MAAMO,GAAG,CAACY,CAAAA,qBACT,MAACC;oBAECF,WAAU;;wBAETC,KAAKP,KAAK;sCACX,KAAC/B;4BACCiB,MAAK;4BACLuB,SAAQ;4BACRC,MAAK;4BACLL,cAAY9B,EAAE,qBAAqB;gCAAEiB,QAAQe,KAAKP,KAAK;4BAAC;4BACxDW,SAAS,IACPrC,SAAS;oCACP2B,UAAU5B,MAAM4B,QAAQ;oCACxBvB,QAAQL,MAAMK,MAAM,CAACc,MAAM,CAAC,CAACoB,GAAGC,KAAOA,OAAON,KAAKhB,KAAK;gCAC1D;sCAEH;;;mBAfIgB,KAAKhB,KAAK;0BAoBnB,KAACtB;gBACCiB,MAAK;gBACLuB,SAAQ;gBACRC,MAAK;gBACLC,SAAS,IAAMrC,SAAS;wBAAE2B,UAAU5B,MAAM4B,QAAQ;wBAAEvB,QAAQ,EAAE;oBAAC;0BAE9DH,EAAE;;;;AAIX"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/ui/molecules/filter-chips/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/ui/molecules/filter-chips/index.ts"],"sourcesContent":["export * from './filter-chips.js';\n"],"names":[],"mappings":"AAAA,cAAc,oBAAoB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/ui/molecules/media-gallery/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/ui/molecules/media-gallery/index.ts"],"sourcesContent":["export * from './media-gallery.js';\n"],"names":[],"mappings":"AAAA,cAAc,qBAAqB"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ComponentPropsWithoutRef, ReactNode } from 'react';
|
|
2
|
+
export interface MediaGalleryItem {
|
|
3
|
+
/** Unique within the gallery; the slide's element id is derived from it. */
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly src: string;
|
|
6
|
+
/** What the image shows. Empty only for an image that adds nothing to the text beside it. */
|
|
7
|
+
readonly alt: string;
|
|
8
|
+
}
|
|
9
|
+
export interface MediaGalleryLabels {
|
|
10
|
+
/** The gallery's name — usually the product's. */
|
|
11
|
+
readonly region: string;
|
|
12
|
+
readonly previous: string;
|
|
13
|
+
readonly next: string;
|
|
14
|
+
/** A slide's position, e.g. `2 of 5`. */
|
|
15
|
+
readonly position: (index: number, total: number) => string;
|
|
16
|
+
}
|
|
17
|
+
export interface MediaGalleryProps extends Omit<ComponentPropsWithoutRef<'div'>, 'children'> {
|
|
18
|
+
items: readonly MediaGalleryItem[];
|
|
19
|
+
labels: MediaGalleryLabels;
|
|
20
|
+
/** Renders one image; a plain `<img>` when omitted (a Next host passes `next/image`). */
|
|
21
|
+
renderMedia?: (item: MediaGalleryItem) => ReactNode;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Product media as a horizontal scroller that works with scripting off and
|
|
25
|
+
* with a keyboard. The scroller is a named region and a Tab stop, so arrow
|
|
26
|
+
* keys scroll it (WCAG 2.1.1, axe `scrollable-region-focusable`); each slide is
|
|
27
|
+
* a figure with its position, and its previous and next links jump to the
|
|
28
|
+
* neighbouring slide's id, which the browser scrolls into view. Server-safe.
|
|
29
|
+
*/
|
|
30
|
+
export declare function MediaGallery({ items, labels, renderMedia, className, ...props }: MediaGalleryProps): import("react").JSX.Element;
|
|
31
|
+
//# sourceMappingURL=media-gallery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-gallery.d.ts","sourceRoot":"","sources":["../../../../src/ui/molecules/media-gallery/media-gallery.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAIjE,MAAM,WAAW,gBAAgB;IAC/B,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,6FAA6F;IAC7F,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,kDAAkD;IAClD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,yCAAyC;IACzC,QAAQ,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;CAC7D;AAED,MAAM,WAAW,iBAAkB,SAAQ,IAAI,CAC7C,wBAAwB,CAAC,KAAK,CAAC,EAC/B,UAAU,CACX;IACC,KAAK,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACnC,MAAM,EAAE,kBAAkB,CAAC;IAC3B,yFAAyF;IACzF,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,gBAAgB,KAAK,SAAS,CAAC;CACrD;AAOD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,MAAM,EACN,WAMC,EACD,SAAS,EACT,GAAG,KAAK,EACT,EAAE,iBAAiB,+BA8CnB"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
|
|
3
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { cn } from '../../utils/cn.js';
|
|
5
|
+
const slideId = (item)=>`media-${item.id}`;
|
|
6
|
+
const STEP = 'flex min-h-(--target-min) items-center rounded-md px-2xs text-step-sm text-content focus-ring hover:underline';
|
|
7
|
+
/**
|
|
8
|
+
* Product media as a horizontal scroller that works with scripting off and
|
|
9
|
+
* with a keyboard. The scroller is a named region and a Tab stop, so arrow
|
|
10
|
+
* keys scroll it (WCAG 2.1.1, axe `scrollable-region-focusable`); each slide is
|
|
11
|
+
* a figure with its position, and its previous and next links jump to the
|
|
12
|
+
* neighbouring slide's id, which the browser scrolls into view. Server-safe.
|
|
13
|
+
*/ export function MediaGallery(_0) {
|
|
14
|
+
let { items, labels, renderMedia = (item)=>/*#__PURE__*/ _jsx("img", {
|
|
15
|
+
src: item.src,
|
|
16
|
+
alt: item.alt,
|
|
17
|
+
className: "h-auto w-full object-contain"
|
|
18
|
+
}), className } = _0, props = _object_without_properties_loose(_0, [
|
|
19
|
+
"items",
|
|
20
|
+
"labels",
|
|
21
|
+
"renderMedia",
|
|
22
|
+
"className"
|
|
23
|
+
]);
|
|
24
|
+
return /*#__PURE__*/ _jsx("div", _extends({
|
|
25
|
+
role: "region",
|
|
26
|
+
"aria-label": labels.region,
|
|
27
|
+
// A scroller must take focus to be scrolled from the keyboard.
|
|
28
|
+
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
|
|
29
|
+
tabIndex: 0,
|
|
30
|
+
className: cn('flex snap-x snap-mandatory gap-s overflow-x-auto scroll-smooth rounded-lg focus-ring motion-reduce:scroll-auto', className)
|
|
31
|
+
}, props, {
|
|
32
|
+
children: items.map((item, index)=>{
|
|
33
|
+
const previous = items[index - 1];
|
|
34
|
+
const next = items[index + 1];
|
|
35
|
+
return /*#__PURE__*/ _jsxs("figure", {
|
|
36
|
+
id: slideId(item),
|
|
37
|
+
className: "m-0 flex w-full shrink-0 snap-center flex-col gap-2xs",
|
|
38
|
+
children: [
|
|
39
|
+
renderMedia(item),
|
|
40
|
+
/*#__PURE__*/ _jsxs("figcaption", {
|
|
41
|
+
className: "flex items-center justify-between gap-s text-step-xs text-content-muted",
|
|
42
|
+
children: [
|
|
43
|
+
previous ? /*#__PURE__*/ _jsx("a", {
|
|
44
|
+
href: `#${slideId(previous)}`,
|
|
45
|
+
className: STEP,
|
|
46
|
+
children: labels.previous
|
|
47
|
+
}) : /*#__PURE__*/ _jsx("span", {}),
|
|
48
|
+
/*#__PURE__*/ _jsx("span", {
|
|
49
|
+
children: labels.position(index + 1, items.length)
|
|
50
|
+
}),
|
|
51
|
+
next ? /*#__PURE__*/ _jsx("a", {
|
|
52
|
+
href: `#${slideId(next)}`,
|
|
53
|
+
className: STEP,
|
|
54
|
+
children: labels.next
|
|
55
|
+
}) : /*#__PURE__*/ _jsx("span", {})
|
|
56
|
+
]
|
|
57
|
+
})
|
|
58
|
+
]
|
|
59
|
+
}, item.id);
|
|
60
|
+
})
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
//# sourceMappingURL=media-gallery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/ui/molecules/media-gallery/media-gallery.tsx"],"sourcesContent":["import type { ComponentPropsWithoutRef, ReactNode } from 'react';\n\nimport { cn } from '../../utils/cn.js';\n\nexport interface MediaGalleryItem {\n /** Unique within the gallery; the slide's element id is derived from it. */\n readonly id: string;\n readonly src: string;\n /** What the image shows. Empty only for an image that adds nothing to the text beside it. */\n readonly alt: string;\n}\n\nexport interface MediaGalleryLabels {\n /** The gallery's name — usually the product's. */\n readonly region: string;\n readonly previous: string;\n readonly next: string;\n /** A slide's position, e.g. `2 of 5`. */\n readonly position: (index: number, total: number) => string;\n}\n\nexport interface MediaGalleryProps extends Omit<\n ComponentPropsWithoutRef<'div'>,\n 'children'\n> {\n items: readonly MediaGalleryItem[];\n labels: MediaGalleryLabels;\n /** Renders one image; a plain `<img>` when omitted (a Next host passes `next/image`). */\n renderMedia?: (item: MediaGalleryItem) => ReactNode;\n}\n\nconst slideId = (item: MediaGalleryItem) => `media-${item.id}`;\n\nconst STEP =\n 'flex min-h-(--target-min) items-center rounded-md px-2xs text-step-sm text-content focus-ring hover:underline';\n\n/**\n * Product media as a horizontal scroller that works with scripting off and\n * with a keyboard. The scroller is a named region and a Tab stop, so arrow\n * keys scroll it (WCAG 2.1.1, axe `scrollable-region-focusable`); each slide is\n * a figure with its position, and its previous and next links jump to the\n * neighbouring slide's id, which the browser scrolls into view. Server-safe.\n */\nexport function MediaGallery({\n items,\n labels,\n renderMedia = item => (\n <img\n src={item.src}\n alt={item.alt}\n className=\"h-auto w-full object-contain\"\n />\n ),\n className,\n ...props\n}: MediaGalleryProps) {\n return (\n <div\n role=\"region\"\n aria-label={labels.region}\n // A scroller must take focus to be scrolled from the keyboard.\n // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex\n tabIndex={0}\n className={cn(\n 'flex snap-x snap-mandatory gap-s overflow-x-auto scroll-smooth rounded-lg focus-ring motion-reduce:scroll-auto',\n className,\n )}\n {...props}\n >\n {items.map((item, index) => {\n const previous = items[index - 1];\n const next = items[index + 1];\n return (\n <figure\n key={item.id}\n id={slideId(item)}\n className=\"m-0 flex w-full shrink-0 snap-center flex-col gap-2xs\"\n >\n {renderMedia(item)}\n <figcaption className=\"flex items-center justify-between gap-s text-step-xs text-content-muted\">\n {previous ? (\n <a href={`#${slideId(previous)}`} className={STEP}>\n {labels.previous}\n </a>\n ) : (\n <span />\n )}\n <span>{labels.position(index + 1, items.length)}</span>\n {next ? (\n <a href={`#${slideId(next)}`} className={STEP}>\n {labels.next}\n </a>\n ) : (\n <span />\n )}\n </figcaption>\n </figure>\n );\n })}\n </div>\n );\n}\n"],"names":["cn","slideId","item","id","STEP","MediaGallery","items","labels","renderMedia","img","src","alt","className","props","div","role","aria-label","region","tabIndex","map","index","previous","next","figure","figcaption","a","href","span","position","length"],"mappings":";;;AAEA,SAASA,EAAE,QAAQ,oBAAoB;AA6BvC,MAAMC,UAAU,CAACC,OAA2B,CAAC,MAAM,EAAEA,KAAKC,EAAE,EAAE;AAE9D,MAAMC,OACJ;AAEF;;;;;;CAMC,GACD,OAAO,SAASC,aAAa,EAYT;QAZS,EAC3BC,KAAK,EACLC,MAAM,EACNC,cAAcN,CAAAA,qBACZ,KAACO;YACCC,KAAKR,KAAKQ,GAAG;YACbC,KAAKT,KAAKS,GAAG;YACbC,WAAU;UAEb,EACDA,SAAS,EAES,OADfC;;;;;;IAEH,qBACE,KAACC;QACCC,MAAK;QACLC,cAAYT,OAAOU,MAAM;QACzB,+DAA+D;QAC/D,+DAA+D;QAC/DC,UAAU;QACVN,WAAWZ,GACT,kHACAY;OAEEC;kBAEHP,MAAMa,GAAG,CAAC,CAACjB,MAAMkB;YAChB,MAAMC,WAAWf,KAAK,CAACc,QAAQ,EAAE;YACjC,MAAME,OAAOhB,KAAK,CAACc,QAAQ,EAAE;YAC7B,qBACE,MAACG;gBAECpB,IAAIF,QAAQC;gBACZU,WAAU;;oBAETJ,YAAYN;kCACb,MAACsB;wBAAWZ,WAAU;;4BACnBS,yBACC,KAACI;gCAAEC,MAAM,CAAC,CAAC,EAAEzB,QAAQoB,WAAW;gCAAET,WAAWR;0CAC1CG,OAAOc,QAAQ;+CAGlB,KAACM;0CAEH,KAACA;0CAAMpB,OAAOqB,QAAQ,CAACR,QAAQ,GAAGd,MAAMuB,MAAM;;4BAC7CP,qBACC,KAACG;gCAAEC,MAAM,CAAC,CAAC,EAAEzB,QAAQqB,OAAO;gCAAEV,WAAWR;0CACtCG,OAAOe,IAAI;+CAGd,KAACK;;;;eAnBAzB,KAAKC,EAAE;QAwBlB;;AAGN"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ComponentPropsWithoutRef, type ReactNode } from 'react';
|
|
2
2
|
export interface MenuProps {
|
|
3
3
|
children: ReactNode;
|
|
4
4
|
className?: string;
|
|
@@ -17,6 +17,12 @@ export declare namespace Menu {
|
|
|
17
17
|
}) => import("react").JSX.Element;
|
|
18
18
|
var Item: ({ className, tone, ...props }: MenuItemProps) => import("react").JSX.Element;
|
|
19
19
|
var Link: ({ className, ...props }: ComponentPropsWithoutRef<"a">) => import("react").JSX.Element;
|
|
20
|
+
var Separator: () => import("react").JSX.Element;
|
|
21
|
+
var Group: ({ label, children }: {
|
|
22
|
+
label: string;
|
|
23
|
+
children: ReactNode;
|
|
24
|
+
}) => import("react").JSX.Element;
|
|
25
|
+
var Radio: ({ checked, className, children, ...props }: MenuRadioProps) => import("react").JSX.Element;
|
|
20
26
|
}
|
|
21
27
|
/** How an entry reads: an ordinary action, or one that destroys something. */
|
|
22
28
|
export type MenuItemTone = 'neutral' | 'destructive';
|
|
@@ -34,4 +40,8 @@ export interface MenuItemProps extends ComponentPropsWithoutRef<'button'> {
|
|
|
34
40
|
*/
|
|
35
41
|
tone?: MenuItemTone;
|
|
36
42
|
}
|
|
43
|
+
export interface MenuRadioProps extends Omit<ComponentPropsWithoutRef<'button'>, 'role'> {
|
|
44
|
+
/** Whether this is the option in effect. */
|
|
45
|
+
checked: boolean;
|
|
46
|
+
}
|
|
37
47
|
//# sourceMappingURL=menu.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu.d.ts","sourceRoot":"","sources":["../../../../src/ui/molecules/menu/menu.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"menu.d.ts","sourceRoot":"","sources":["../../../../src/ui/molecules/menu/menu.tsx"],"names":[],"mappings":"AASA,OAAO,EAAE,KAAK,wBAAwB,EAAE,KAAK,SAAS,EAAS,MAAM,OAAO,CAAC;AAI7E,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,SAAS,+BAStD;yBATe,IAAI;2CAWsB,wBAAwB,CAAC,QAAQ,CAAC;0CAiBzE;QACD,QAAQ,EAAE,SAAS,CAAC;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB;8CAwCwD,aAAa;wCA2B/B,wBAAwB,CAAC,GAAG,CAAC;;qCA0BhC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,SAAS,CAAA;KAAE;4DA2Bf,cAAc;;AAvGzE,8EAA8E;AAC9E,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,aAAa,CAAC;AAErD,MAAM,WAAW,aAAc,SAAQ,wBAAwB,CAAC,QAAQ,CAAC;IACvE;;;;;;;;;;OAUG;IACH,IAAI,CAAC,EAAE,YAAY,CAAC;CACrB;AA2ED,MAAM,WAAW,cAAe,SAAQ,IAAI,CAC1C,wBAAwB,CAAC,QAAQ,CAAC,EAClC,MAAM,CACP;IACC,4CAA4C;IAC5C,OAAO,EAAE,OAAO,CAAC;CAClB"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
3
3
|
import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
|
|
4
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
5
|
-
import { Menu as HuiMenu, MenuButton, MenuItem as HuiMenuItem, MenuItems } from '@headlessui/react';
|
|
4
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { Menu as HuiMenu, MenuButton, MenuItem as HuiMenuItem, MenuItems, MenuSeparator } from '@headlessui/react';
|
|
6
|
+
import { useId } from 'react';
|
|
6
7
|
import { cn } from '../../utils/cn.js';
|
|
7
8
|
/**
|
|
8
9
|
* A dropdown menu — the user context menu in the top bar, and the tab-overflow
|
|
@@ -20,12 +21,12 @@ function Trigger(_0) {
|
|
|
20
21
|
"className"
|
|
21
22
|
]);
|
|
22
23
|
return /*#__PURE__*/ _jsx(MenuButton, _extends({
|
|
23
|
-
className: cn('inline-flex items-center gap-2xs rounded-lg px-2xs py-3xs text-step-sm text-content', 'transition duration-200 ease-smooth hover:bg-surface', 'focus-ring', className)
|
|
24
|
+
className: cn('inline-flex min-h-(--target-min) min-w-(--target-min) items-center gap-2xs rounded-lg px-2xs py-3xs text-step-sm text-content', 'transition duration-200 ease-smooth hover:bg-surface', 'focus-ring', className)
|
|
24
25
|
}, props));
|
|
25
26
|
}
|
|
26
27
|
function Items({ children, className }) {
|
|
27
28
|
return /*#__PURE__*/ _jsx(MenuItems, {
|
|
28
|
-
className: cn('right-0 absolute z-
|
|
29
|
+
className: cn('right-0 min-w-48 absolute z-(--z-dropdown) mt-2xs rounded-lg border border-border bg-surface-elevated p-3xs shadow-overlay', // The panel itself is a focus-managed CONTAINER, not a target: the ring
|
|
29
30
|
// belongs on the item inside it. Stripping the outline here is
|
|
30
31
|
// correct, and is not a missing `focus-ring`.
|
|
31
32
|
'focus:outline-none', className),
|
|
@@ -43,7 +44,9 @@ function Item(_0) {
|
|
|
43
44
|
]);
|
|
44
45
|
return /*#__PURE__*/ _jsx(HuiMenuItem, _extends({
|
|
45
46
|
as: "button",
|
|
46
|
-
className: cn('flex w-full items-center gap-2xs rounded-md px-2xs py-3xs text-step-sm',
|
|
47
|
+
className: cn('flex min-h-(--target-min) w-full items-center gap-2xs rounded-md px-2xs py-3xs text-step-sm', // The item under the keyboard wears the focus ring, inset so the
|
|
48
|
+
// panel's edge never clips it; a background swap alone is ~1.05:1.
|
|
49
|
+
'focus-ring data-focus:bg-surface data-focus:-outline-offset-2', ITEM_TONE_CLASS[tone], 'disabled:cursor-not-allowed disabled:opacity-50', className)
|
|
47
50
|
}, props));
|
|
48
51
|
}
|
|
49
52
|
/**
|
|
@@ -60,12 +63,71 @@ function Item(_0) {
|
|
|
60
63
|
]);
|
|
61
64
|
return /*#__PURE__*/ _jsx(HuiMenuItem, _extends({
|
|
62
65
|
as: "a",
|
|
63
|
-
className: cn('flex w-full items-center gap-2xs rounded-md px-2xs py-3xs text-step-sm text-content no-underline', 'data-focus:bg-surface data-focus:text-content', className)
|
|
66
|
+
className: cn('flex min-h-(--target-min) w-full items-center gap-2xs rounded-md px-2xs py-3xs text-step-sm text-content no-underline', 'focus-ring data-focus:bg-surface data-focus:text-content data-focus:-outline-offset-2', className)
|
|
64
67
|
}, props));
|
|
65
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* A rule between groups of entries — before the destructive ones, so "do" and
|
|
71
|
+
* "destroy" are not one undifferentiated list.
|
|
72
|
+
*/ function Separator() {
|
|
73
|
+
return /*#__PURE__*/ _jsx(MenuSeparator, {
|
|
74
|
+
className: "my-3xs h-px bg-border"
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* A named set of entries — "Tema", "Densidad" — so a choice among them is
|
|
79
|
+
* announced with what it chooses. The heading is shown, and names the group.
|
|
80
|
+
*/ function Group({ label, children }) {
|
|
81
|
+
const id = useId();
|
|
82
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
83
|
+
role: "group",
|
|
84
|
+
"aria-labelledby": id,
|
|
85
|
+
className: "py-3xs",
|
|
86
|
+
children: [
|
|
87
|
+
/*#__PURE__*/ _jsx("p", {
|
|
88
|
+
id: id,
|
|
89
|
+
className: "m-0 px-2xs py-3xs text-step-xs font-semibold text-content-muted",
|
|
90
|
+
children: label
|
|
91
|
+
}),
|
|
92
|
+
children
|
|
93
|
+
]
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* One option of a {@link Group} where exactly one holds: a `menuitemradio`,
|
|
98
|
+
* its state in `aria-checked` and in a visible mark rather than colour alone.
|
|
99
|
+
*/ function Radio(_0) {
|
|
100
|
+
let { checked, className, children } = _0, props = _object_without_properties_loose(_0, [
|
|
101
|
+
"checked",
|
|
102
|
+
"className",
|
|
103
|
+
"children"
|
|
104
|
+
]);
|
|
105
|
+
return /*#__PURE__*/ _jsxs(HuiMenuItem, _extends({
|
|
106
|
+
as: "button",
|
|
107
|
+
// Headless UI names every item `menuitem` and lets no prop change it;
|
|
108
|
+
// the role is set on the element once it mounts, which keeps the
|
|
109
|
+
// menu's own keyboard handling, and React never writes it back because
|
|
110
|
+
// the prop it renders never changes.
|
|
111
|
+
ref: (node)=>node == null ? void 0 : node.setAttribute('role', 'menuitemradio'),
|
|
112
|
+
"aria-checked": checked,
|
|
113
|
+
className: cn('flex min-h-(--target-min) w-full items-center gap-2xs rounded-md px-2xs py-3xs text-step-sm text-content', 'focus-ring data-focus:bg-surface data-focus:-outline-offset-2', className)
|
|
114
|
+
}, props, {
|
|
115
|
+
children: [
|
|
116
|
+
/*#__PURE__*/ _jsx("span", {
|
|
117
|
+
"aria-hidden": "true",
|
|
118
|
+
className: "shrink-0",
|
|
119
|
+
children: checked ? '●' : '○'
|
|
120
|
+
}),
|
|
121
|
+
children
|
|
122
|
+
]
|
|
123
|
+
}));
|
|
124
|
+
}
|
|
66
125
|
Menu.Trigger = Trigger;
|
|
67
126
|
Menu.Items = Items;
|
|
68
127
|
Menu.Item = Item;
|
|
69
128
|
Menu.Link = Link;
|
|
129
|
+
Menu.Separator = Separator;
|
|
130
|
+
Menu.Group = Group;
|
|
131
|
+
Menu.Radio = Radio;
|
|
70
132
|
|
|
71
133
|
//# sourceMappingURL=menu.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/ui/molecules/menu/menu.tsx"],"sourcesContent":["'use client';\n\nimport {\n Menu as HuiMenu,\n MenuButton,\n MenuItem as HuiMenuItem,\n MenuItems,\n} from '@headlessui/react';\nimport type
|
|
1
|
+
{"version":3,"sources":["../../../../src/ui/molecules/menu/menu.tsx"],"sourcesContent":["'use client';\n\nimport {\n Menu as HuiMenu,\n MenuButton,\n MenuItem as HuiMenuItem,\n MenuItems,\n MenuSeparator,\n} from '@headlessui/react';\nimport { type ComponentPropsWithoutRef, type ReactNode, useId } from 'react';\n\nimport { cn } from '../../utils/cn.js';\n\nexport interface MenuProps {\n children: ReactNode;\n className?: string;\n}\n\n/**\n * A dropdown menu — the user context menu in the top bar, and the tab-overflow\n * list. A thin styled wrapper over Headless UI so focus management, keyboard\n * nav, and outside-click come for free; the tokens keep it on-brand.\n */\nexport function Menu({ children, className }: MenuProps) {\n return (\n <HuiMenu\n as=\"div\"\n className={cn('relative inline-block text-left', className)}\n >\n {children}\n </HuiMenu>\n );\n}\n\nfunction Trigger({ className, ...props }: ComponentPropsWithoutRef<'button'>) {\n return (\n <MenuButton\n className={cn(\n 'inline-flex min-h-(--target-min) min-w-(--target-min) items-center gap-2xs rounded-lg px-2xs py-3xs text-step-sm text-content',\n 'transition duration-200 ease-smooth hover:bg-surface',\n 'focus-ring',\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction Items({\n children,\n className,\n}: {\n children: ReactNode;\n className?: string;\n}) {\n return (\n <MenuItems\n className={cn(\n 'right-0 min-w-48 absolute z-(--z-dropdown) mt-2xs rounded-lg border border-border bg-surface-elevated p-3xs shadow-overlay',\n // The panel itself is a focus-managed CONTAINER, not a target: the ring\n // belongs on the item inside it. Stripping the outline here is\n // correct, and is not a missing `focus-ring`.\n 'focus:outline-none',\n className,\n )}\n >\n {children}\n </MenuItems>\n );\n}\n\n/** How an entry reads: an ordinary action, or one that destroys something. */\nexport type MenuItemTone = 'neutral' | 'destructive';\n\nexport interface MenuItemProps extends ComponentPropsWithoutRef<'button'> {\n /**\n * `destructive` colours the entry as a deletion.\n *\n * It exists because an overflow menu is where a destructive verb ends up once\n * the visible actions are full, and a `Button` in that position would carry\n * `variant=\"destructive\"` — a menu that cannot say the same thing makes the\n * appearance of a verb depend on how many siblings it happens to have.\n *\n * Colour is not the affordance, and must not be the only warning: a verb\n * whose descriptor carries `confirm` still asks before it fires.\n */\n tone?: MenuItemTone;\n}\n\nconst ITEM_TONE_CLASS: Record<MenuItemTone, string> = {\n neutral: 'text-content data-focus:text-content',\n destructive: 'text-danger data-focus:text-danger',\n};\n\nfunction Item({ className, tone = 'neutral', ...props }: MenuItemProps) {\n return (\n <HuiMenuItem\n as=\"button\"\n className={cn(\n 'flex min-h-(--target-min) w-full items-center gap-2xs rounded-md px-2xs py-3xs text-step-sm',\n // The item under the keyboard wears the focus ring, inset so the\n // panel's edge never clips it; a background swap alone is ~1.05:1.\n 'focus-ring data-focus:bg-surface data-focus:-outline-offset-2',\n ITEM_TONE_CLASS[tone],\n 'disabled:cursor-not-allowed disabled:opacity-50',\n className,\n )}\n {...props}\n />\n );\n}\n\n/**\n * A navigating menu entry.\n *\n * A plain anchor rather than a router link: the entries that need this are the\n * account menu's, and those point at a different app on a different origin,\n * where a client-side transition is not an option anyway. Callers wanting an\n * in-app transition should pass their router's link through `as`-style\n * composition at the call site instead.\n */\nfunction Link({ className, ...props }: ComponentPropsWithoutRef<'a'>) {\n return (\n <HuiMenuItem\n as=\"a\"\n className={cn(\n 'flex min-h-(--target-min) w-full items-center gap-2xs rounded-md px-2xs py-3xs text-step-sm text-content no-underline',\n 'focus-ring data-focus:bg-surface data-focus:text-content data-focus:-outline-offset-2',\n className,\n )}\n {...props}\n />\n );\n}\n\n/**\n * A rule between groups of entries — before the destructive ones, so \"do\" and\n * \"destroy\" are not one undifferentiated list.\n */\nfunction Separator() {\n return <MenuSeparator className=\"my-3xs h-px bg-border\" />;\n}\n\n/**\n * A named set of entries — \"Tema\", \"Densidad\" — so a choice among them is\n * announced with what it chooses. The heading is shown, and names the group.\n */\nfunction Group({ label, children }: { label: string; children: ReactNode }) {\n const id = useId();\n return (\n <div role=\"group\" aria-labelledby={id} className=\"py-3xs\">\n <p\n id={id}\n className=\"m-0 px-2xs py-3xs text-step-xs font-semibold text-content-muted\"\n >\n {label}\n </p>\n {children}\n </div>\n );\n}\n\nexport interface MenuRadioProps extends Omit<\n ComponentPropsWithoutRef<'button'>,\n 'role'\n> {\n /** Whether this is the option in effect. */\n checked: boolean;\n}\n\n/**\n * One option of a {@link Group} where exactly one holds: a `menuitemradio`,\n * its state in `aria-checked` and in a visible mark rather than colour alone.\n */\nfunction Radio({ checked, className, children, ...props }: MenuRadioProps) {\n return (\n <HuiMenuItem\n as=\"button\"\n // Headless UI names every item `menuitem` and lets no prop change it;\n // the role is set on the element once it mounts, which keeps the\n // menu's own keyboard handling, and React never writes it back because\n // the prop it renders never changes.\n ref={(node: HTMLButtonElement | null) =>\n node?.setAttribute('role', 'menuitemradio')\n }\n aria-checked={checked}\n className={cn(\n 'flex min-h-(--target-min) w-full items-center gap-2xs rounded-md px-2xs py-3xs text-step-sm text-content',\n 'focus-ring data-focus:bg-surface data-focus:-outline-offset-2',\n className,\n )}\n {...props}\n >\n <span aria-hidden=\"true\" className=\"shrink-0\">\n {checked ? '●' : '○'}\n </span>\n {children}\n </HuiMenuItem>\n );\n}\n\nMenu.Trigger = Trigger;\nMenu.Items = Items;\nMenu.Item = Item;\nMenu.Link = Link;\nMenu.Separator = Separator;\nMenu.Group = Group;\nMenu.Radio = Radio;\n"],"names":["Menu","HuiMenu","MenuButton","MenuItem","HuiMenuItem","MenuItems","MenuSeparator","useId","cn","children","className","as","Trigger","props","Items","ITEM_TONE_CLASS","neutral","destructive","Item","tone","Link","Separator","Group","label","id","div","role","aria-labelledby","p","Radio","checked","ref","node","setAttribute","aria-checked","span","aria-hidden"],"mappings":"AAAA;;;;AAEA,SACEA,QAAQC,OAAO,EACfC,UAAU,EACVC,YAAYC,WAAW,EACvBC,SAAS,EACTC,aAAa,QACR,oBAAoB;AAC3B,SAAwDC,KAAK,QAAQ,QAAQ;AAE7E,SAASC,EAAE,QAAQ,oBAAoB;AAOvC;;;;CAIC,GACD,OAAO,SAASR,KAAK,EAAES,QAAQ,EAAEC,SAAS,EAAa;IACrD,qBACE,KAACT;QACCU,IAAG;QACHD,WAAWF,GAAG,mCAAmCE;kBAEhDD;;AAGP;AAEA,SAASG,QAAQ,EAA2D;QAA3D,EAAEF,SAAS,EAAgD,OAA3CG;;;IAC/B,qBACE,KAACX;QACCQ,WAAWF,GACT,iIACA,wDACA,cACAE;OAEEG;AAGV;AAEA,SAASC,MAAM,EACbL,QAAQ,EACRC,SAAS,EAIV;IACC,qBACE,KAACL;QACCK,WAAWF,GACT,8HACA,wEAAwE;QACxE,+DAA+D;QAC/D,8CAA8C;QAC9C,sBACAE;kBAGDD;;AAGP;AAoBA,MAAMM,kBAAgD;IACpDC,SAAS;IACTC,aAAa;AACf;AAEA,SAASC,KAAK,EAAwD;QAAxD,EAAER,SAAS,EAAES,OAAO,SAAS,EAA2B,OAAtBN;;;;IAC9C,qBACE,KAACT;QACCO,IAAG;QACHD,WAAWF,GACT,+FACA,iEAAiE;QACjE,mEAAmE;QACnE,iEACAO,eAAe,CAACI,KAAK,EACrB,mDACAT;OAEEG;AAGV;AAEA;;;;;;;;CAQC,GACD,SAASO,KAAK,EAAsD;QAAtD,EAAEV,SAAS,EAA2C,OAAtCG;;;IAC5B,qBACE,KAACT;QACCO,IAAG;QACHD,WAAWF,GACT,yHACA,yFACAE;OAEEG;AAGV;AAEA;;;CAGC,GACD,SAASQ;IACP,qBAAO,KAACf;QAAcI,WAAU;;AAClC;AAEA;;;CAGC,GACD,SAASY,MAAM,EAAEC,KAAK,EAAEd,QAAQ,EAA0C;IACxE,MAAMe,KAAKjB;IACX,qBACE,MAACkB;QAAIC,MAAK;QAAQC,mBAAiBH;QAAId,WAAU;;0BAC/C,KAACkB;gBACCJ,IAAIA;gBACJd,WAAU;0BAETa;;YAEFd;;;AAGP;AAUA;;;CAGC,GACD,SAASoB,MAAM,EAA0D;QAA1D,EAAEC,OAAO,EAAEpB,SAAS,EAAED,QAAQ,EAA4B,OAAvBI;;;;;IAChD,qBACE,MAACT;QACCO,IAAG;QACH,sEAAsE;QACtE,iEAAiE;QACjE,uEAAuE;QACvE,qCAAqC;QACrCoB,KAAK,CAACC,OACJA,wBAAAA,KAAMC,YAAY,CAAC,QAAQ;QAE7BC,gBAAcJ;QACdpB,WAAWF,GACT,4GACA,iEACAE;OAEEG;;0BAEJ,KAACsB;gBAAKC,eAAY;gBAAO1B,WAAU;0BAChCoB,UAAU,MAAM;;YAElBrB;;;AAGP;AAEAT,KAAKY,OAAO,GAAGA;AACfZ,KAAKc,KAAK,GAAGA;AACbd,KAAKkB,IAAI,GAAGA;AACZlB,KAAKoB,IAAI,GAAGA;AACZpB,KAAKqB,SAAS,GAAGA;AACjBrB,KAAKsB,KAAK,GAAGA;AACbtB,KAAK6B,KAAK,GAAGA"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cn } from '../../utils/cn.js';
|
|
3
|
-
/** A bar's trigger: quiet until hovered or open. */ const TRIGGER = 'focus-ring flex h-
|
|
3
|
+
/** A bar's trigger: quiet until hovered or open. */ const TRIGGER = 'focus-ring flex h-8 cursor-pointer list-none items-center gap-3xs rounded-md border border-transparent px-2xs text-step-sm font-medium text-content select-none transition-[background-color,border-color] duration-(--duration-fast,150ms) hover:border-border hover:bg-[color-mix(in_srgb,var(--color-content)_6%,transparent)] [&::-webkit-details-marker]:hidden [[open]>&]:border-border [[open]>&]:bg-[color-mix(in_srgb,var(--color-content)_6%,transparent)]';
|
|
4
4
|
/**
|
|
5
5
|
* A dropdown in a navigation bar: a native `<details>` disclosure, so it opens
|
|
6
6
|
* and its links work with scripting off. What a dropdown is expected to do on
|
|
@@ -31,7 +31,7 @@ import { cn } from '../../utils/cn.js';
|
|
|
31
31
|
"aria-hidden": "true",
|
|
32
32
|
viewBox: "0 0 24 24",
|
|
33
33
|
"data-slot": "nav-menu-chevron",
|
|
34
|
-
className: "size-
|
|
34
|
+
className: "size-3.5 flex-none fill-none stroke-current stroke-2 text-content-muted transition-[rotate] duration-(--duration-fast,150ms) [stroke-linecap:round] [stroke-linejoin:round] [[open]>summary>&]:rotate-180",
|
|
35
35
|
children: /*#__PURE__*/ _jsx("path", {
|
|
36
36
|
d: "m7 10 5 5 5-5"
|
|
37
37
|
})
|
|
@@ -40,7 +40,7 @@ import { cn } from '../../utils/cn.js';
|
|
|
40
40
|
}),
|
|
41
41
|
/*#__PURE__*/ _jsx("div", {
|
|
42
42
|
"data-slot": "nav-menu-panel",
|
|
43
|
-
className: "right-0 ease-out starting:-translate-y-1 [&_ul]:m-0 [&_ul]:p-0 absolute top-[calc(100%+var(--spacing-3xs))] z-
|
|
43
|
+
className: "right-0 ease-out starting:-translate-y-1 [&_ul]:m-0 [&_ul]:p-0 min-w-40 absolute top-[calc(100%+var(--spacing-3xs))] z-(--z-dropdown) rounded-lg border border-border bg-surface-elevated p-3xs shadow-overlay transition-[opacity,translate] duration-(--duration-fast) motion-reduce:transition-none starting:opacity-0 [&_ul]:list-none",
|
|
44
44
|
children: children
|
|
45
45
|
})
|
|
46
46
|
]
|
|
@@ -51,7 +51,7 @@ import { cn } from '../../utils/cn.js';
|
|
|
51
51
|
"aria-hidden": "true",
|
|
52
52
|
viewBox: "0 0 24 24",
|
|
53
53
|
"data-slot": "nav-menu-check",
|
|
54
|
-
className: "size-
|
|
54
|
+
className: "size-4 flex-none fill-none stroke-current stroke-2 [stroke-linecap:round] [stroke-linejoin:round]",
|
|
55
55
|
children: checked && /*#__PURE__*/ _jsx("path", {
|
|
56
56
|
d: "m5 12.5 4.5 4.5L19 7.5"
|
|
57
57
|
})
|