@svgrid/enterprise 3.1.8 → 3.1.10
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 +1 -0
- package/dist/SvAlertRuleEditor.svelte +16 -5
- package/dist/SvGridAlerts.svelte +8 -0
- package/dist/SvGridAlerts.svelte.d.ts +4 -0
- package/dist/SvGridBoard.svelte +69 -10
- package/dist/SvGridBulkEditDrawer.svelte +13 -5
- package/dist/SvGridGallery.svelte +286 -0
- package/dist/SvGridGallery.svelte.d.ts +30 -0
- package/dist/SvGridScheduler.svelte +33 -11
- package/dist/SvGridSelectionBar.svelte +47 -0
- package/dist/advanced-filter/expr-columns-from-grid.d.ts +1 -0
- package/dist/advanced-filter/expr-columns-from-grid.js +11 -7
- package/dist/advanced-filter-enable.d.ts +6 -0
- package/dist/advanced-filter-enable.js +8 -0
- package/dist/alerts/alert-engine.d.ts +7 -0
- package/dist/alerts/alert-engine.js +5 -2
- package/dist/alerts/condition-editor.d.ts +26 -0
- package/dist/alerts/condition-editor.js +17 -0
- package/dist/base/index.d.ts +20 -0
- package/dist/base/index.js +20 -0
- package/dist/bulk-edit.d.ts +11 -0
- package/dist/bulk-edit.js +18 -1
- package/dist/cdn/svgrid-enterprise.svelte-external.js +11197 -10639
- package/dist/designer/assets/GridFooter-Cu8_yBlQ.js +2 -0
- package/dist/designer/assets/GridMenus-BUjkkAU2.js +6 -0
- package/dist/designer/assets/PinSplitter-BgTpgh1d.css +1 -0
- package/dist/designer/assets/PinSplitter-C5AwA1xV.js +1 -0
- package/dist/designer/assets/SvGrid.helpers-CIrmIo9U.js +1 -0
- package/dist/designer/assets/SvGridCellEditor-CJxThKL_.js +4 -0
- package/dist/designer/assets/SvListBox-CS7oUAiX.js +2 -0
- package/dist/designer/assets/conditional-formatting-MDWbF8_Y.js +1 -0
- package/dist/designer/assets/grid-extension.svelte-BZ7AqyJI.js +1 -0
- package/dist/designer/assets/index-BmvG99kR.css +1 -0
- package/dist/designer/assets/{index-Obk8sI77.js → index-CJodU3tU.js} +307 -307
- package/dist/designer/assets/sparkline-C-NcnB2i.js +1 -0
- package/dist/designer/assets/src-C_U1NKOY.js +58 -0
- package/dist/designer/assets/sv-grid-scrollbar-B9N6Wuj0.js +109 -0
- package/dist/designer/assets/validate-H5u2HEQh.js +1 -0
- package/dist/designer/index.html +11 -7
- package/dist/expressions/compile.js +10 -0
- package/dist/expressions/evaluate.js +18 -0
- package/dist/expressions/expression-types.d.ts +10 -0
- package/dist/expressions/kinds.d.ts +28 -0
- package/dist/expressions/kinds.js +19 -0
- package/dist/expressions/parse.js +9 -1
- package/dist/fields/AttachmentCell.svelte +242 -0
- package/dist/fields/AttachmentCell.svelte.d.ts +5 -0
- package/dist/fields/AttachmentDialog.svelte +357 -0
- package/dist/fields/AttachmentDialog.svelte.d.ts +13 -0
- package/dist/fields/AttachmentEditor.svelte +41 -0
- package/dist/fields/AttachmentEditor.svelte.d.ts +11 -0
- package/dist/fields/AttachmentPreview.svelte +72 -0
- package/dist/fields/AttachmentPreview.svelte.d.ts +8 -0
- package/dist/fields/BarcodeCell.svelte +20 -0
- package/dist/fields/BarcodeCell.svelte.d.ts +5 -0
- package/dist/fields/BaseAutomations.svelte +558 -0
- package/dist/fields/BaseAutomations.svelte.d.ts +18 -0
- package/dist/fields/BaseChartView.svelte +47 -0
- package/dist/fields/BaseChartView.svelte.d.ts +8 -0
- package/dist/fields/BaseTableView.svelte +185 -0
- package/dist/fields/BaseTableView.svelte.d.ts +37 -0
- package/dist/fields/BaseTabs.svelte +308 -0
- package/dist/fields/BaseTabs.svelte.d.ts +21 -0
- package/dist/fields/ButtonCell.svelte +47 -0
- package/dist/fields/ButtonCell.svelte.d.ts +20 -0
- package/dist/fields/CheckboxCell.svelte +46 -0
- package/dist/fields/CheckboxCell.svelte.d.ts +14 -0
- package/dist/fields/DeleteFieldConfirm.svelte +80 -0
- package/dist/fields/DeleteFieldConfirm.svelte.d.ts +8 -0
- package/dist/fields/DeleteRecordConfirm.svelte +48 -0
- package/dist/fields/DeleteRecordConfirm.svelte.d.ts +9 -0
- package/dist/fields/FieldCell.svelte +80 -0
- package/dist/fields/FieldCell.svelte.d.ts +9 -0
- package/dist/fields/FieldEditor.svelte +321 -0
- package/dist/fields/FieldEditor.svelte.d.ts +10 -0
- package/dist/fields/FieldInput.svelte +188 -0
- package/dist/fields/FieldInput.svelte.d.ts +29 -0
- package/dist/fields/FieldsHeaderEnd.svelte +146 -0
- package/dist/fields/FieldsHeaderEnd.svelte.d.ts +6 -0
- package/dist/fields/FootRowAdder.svelte +8 -0
- package/dist/fields/FootRowAdder.svelte.d.ts +6 -0
- package/dist/fields/GroupHeader.svelte +127 -0
- package/dist/fields/GroupHeader.svelte.d.ts +24 -0
- package/dist/fields/LinkCell.svelte +46 -0
- package/dist/fields/LinkCell.svelte.d.ts +9 -0
- package/dist/fields/LinkPickerEditor.svelte +172 -0
- package/dist/fields/LinkPickerEditor.svelte.d.ts +9 -0
- package/dist/fields/LinkPreview.svelte +60 -0
- package/dist/fields/LinkPreview.svelte.d.ts +7 -0
- package/dist/fields/LinkedRecordView.svelte +94 -0
- package/dist/fields/LinkedRecordView.svelte.d.ts +9 -0
- package/dist/fields/LinkedRecordsCell.svelte +59 -0
- package/dist/fields/LinkedRecordsCell.svelte.d.ts +11 -0
- package/dist/fields/LongTextEditor.svelte +157 -0
- package/dist/fields/LongTextEditor.svelte.d.ts +11 -0
- package/dist/fields/NumberEditor.svelte +309 -0
- package/dist/fields/NumberEditor.svelte.d.ts +12 -0
- package/dist/fields/OptionsEditor.svelte +117 -0
- package/dist/fields/OptionsEditor.svelte.d.ts +11 -0
- package/dist/fields/PercentCell.svelte +21 -0
- package/dist/fields/PercentCell.svelte.d.ts +8 -0
- package/dist/fields/PropertyInput.svelte +204 -0
- package/dist/fields/PropertyInput.svelte.d.ts +11 -0
- package/dist/fields/RatingCell.svelte +75 -0
- package/dist/fields/RatingCell.svelte.d.ts +16 -0
- package/dist/fields/RatingEditor.svelte +82 -0
- package/dist/fields/RatingEditor.svelte.d.ts +9 -0
- package/dist/fields/RecordView.svelte +310 -0
- package/dist/fields/RecordView.svelte.d.ts +11 -0
- package/dist/fields/RichTextCell.svelte +30 -0
- package/dist/fields/RichTextCell.svelte.d.ts +5 -0
- package/dist/fields/RowAdder.svelte +93 -0
- package/dist/fields/RowAdder.svelte.d.ts +7 -0
- package/dist/fields/RowHeader.svelte +99 -0
- package/dist/fields/RowHeader.svelte.d.ts +11 -0
- package/dist/fields/SelectCell.svelte +35 -0
- package/dist/fields/SelectCell.svelte.d.ts +15 -0
- package/dist/fields/SelectEditor.svelte +350 -0
- package/dist/fields/SelectEditor.svelte.d.ts +17 -0
- package/dist/fields/SummaryCell.svelte +160 -0
- package/dist/fields/SummaryCell.svelte.d.ts +11 -0
- package/dist/fields/SvBase.svelte +545 -0
- package/dist/fields/SvBase.svelte.d.ts +30 -0
- package/dist/fields/SvBaseForm.svelte +295 -0
- package/dist/fields/SvBaseForm.svelte.d.ts +22 -0
- package/dist/fields/SvBaseViewBar.svelte +644 -0
- package/dist/fields/SvBaseViewBar.svelte.d.ts +26 -0
- package/dist/fields/UserCell.svelte +39 -0
- package/dist/fields/UserCell.svelte.d.ts +9 -0
- package/dist/fields/UserFace.svelte +30 -0
- package/dist/fields/UserFace.svelte.d.ts +12 -0
- package/dist/fields/activity.svelte.d.ts +50 -0
- package/dist/fields/activity.svelte.js +110 -0
- package/dist/fields/attachments.d.ts +95 -0
- package/dist/fields/attachments.js +145 -0
- package/dist/fields/automation-outbound.d.ts +46 -0
- package/dist/fields/automation-outbound.js +61 -0
- package/dist/fields/automation-server.d.ts +53 -0
- package/dist/fields/automation-server.js +333 -0
- package/dist/fields/automations.svelte.d.ts +208 -0
- package/dist/fields/automations.svelte.js +437 -0
- package/dist/fields/bar-request.svelte.d.ts +16 -0
- package/dist/fields/bar-request.svelte.js +15 -0
- package/dist/fields/base-alerts.d.ts +24 -0
- package/dist/fields/base-alerts.js +64 -0
- package/dist/fields/base-connect.d.ts +82 -0
- package/dist/fields/base-connect.js +271 -0
- package/dist/fields/base-schema.d.ts +104 -0
- package/dist/fields/base-schema.js +266 -0
- package/dist/fields/base-sql.d.ts +62 -0
- package/dist/fields/base-sql.js +251 -0
- package/dist/fields/base-sync.d.ts +142 -0
- package/dist/fields/base-sync.js +499 -0
- package/dist/fields/base-tables.d.ts +89 -0
- package/dist/fields/base-tables.js +349 -0
- package/dist/fields/builtin-types.d.ts +13 -0
- package/dist/fields/builtin-types.js +773 -0
- package/dist/fields/button.d.ts +2 -0
- package/dist/fields/button.js +17 -0
- package/dist/fields/chart-view.d.ts +35 -0
- package/dist/fields/chart-view.js +154 -0
- package/dist/fields/column-order.d.ts +6 -0
- package/dist/fields/column-order.js +12 -0
- package/dist/fields/convert.d.ts +16 -0
- package/dist/fields/convert.js +176 -0
- package/dist/fields/copy-text.d.ts +13 -0
- package/dist/fields/copy-text.js +45 -0
- package/dist/fields/editor-request.svelte.d.ts +71 -0
- package/dist/fields/editor-request.svelte.js +52 -0
- package/dist/fields/field-ops.d.ts +72 -0
- package/dist/fields/field-ops.js +334 -0
- package/dist/fields/filter-model.d.ts +84 -0
- package/dist/fields/filter-model.js +446 -0
- package/dist/fields/format.d.ts +109 -0
- package/dist/fields/format.js +288 -0
- package/dist/fields/formula.d.ts +50 -0
- package/dist/fields/formula.js +530 -0
- package/dist/fields/grid-access.d.ts +60 -0
- package/dist/fields/grid-access.js +120 -0
- package/dist/fields/index.d.ts +34 -0
- package/dist/fields/index.js +312 -0
- package/dist/fields/linked-fields.d.ts +8 -0
- package/dist/fields/linked-fields.js +40 -0
- package/dist/fields/links.d.ts +10 -0
- package/dist/fields/links.js +46 -0
- package/dist/fields/name-list.d.ts +16 -0
- package/dist/fields/name-list.js +49 -0
- package/dist/fields/palette.d.ts +47 -0
- package/dist/fields/palette.js +69 -0
- package/dist/fields/record-delete.svelte.d.ts +12 -0
- package/dist/fields/record-delete.svelte.js +5 -0
- package/dist/fields/record-menu.d.ts +9 -0
- package/dist/fields/record-menu.js +78 -0
- package/dist/fields/registry.svelte.d.ts +176 -0
- package/dist/fields/registry.svelte.js +51 -0
- package/dist/fields/rich-text.d.ts +23 -0
- package/dist/fields/rich-text.js +48 -0
- package/dist/fields/row-color-choice.svelte.d.ts +31 -0
- package/dist/fields/row-color-choice.svelte.js +25 -0
- package/dist/fields/row-colors.d.ts +36 -0
- package/dist/fields/row-colors.js +90 -0
- package/dist/fields/row-height.d.ts +28 -0
- package/dist/fields/row-height.js +50 -0
- package/dist/fields/sources.svelte.d.ts +80 -0
- package/dist/fields/sources.svelte.js +131 -0
- package/dist/fields/summaries.d.ts +18 -0
- package/dist/fields/summaries.js +138 -0
- package/dist/fields/view-model.d.ts +129 -0
- package/dist/fields/view-model.js +492 -0
- package/dist/fields/viewbar/ChartPanel.svelte +61 -0
- package/dist/fields/viewbar/ChartPanel.svelte.d.ts +9 -0
- package/dist/fields/viewbar/ColorPanel.svelte +112 -0
- package/dist/fields/viewbar/ColorPanel.svelte.d.ts +9 -0
- package/dist/fields/viewbar/ConditionValue.svelte +153 -0
- package/dist/fields/viewbar/ConditionValue.svelte.d.ts +15 -0
- package/dist/fields/viewbar/FieldIcon.svelte +18 -0
- package/dist/fields/viewbar/FieldIcon.svelte.d.ts +9 -0
- package/dist/fields/viewbar/FilterPanel.svelte +192 -0
- package/dist/fields/viewbar/FilterPanel.svelte.d.ts +13 -0
- package/dist/fields/viewbar/GanttPanel.svelte +60 -0
- package/dist/fields/viewbar/GanttPanel.svelte.d.ts +10 -0
- package/dist/fields/viewbar/HideFieldsPanel.svelte +104 -0
- package/dist/fields/viewbar/HideFieldsPanel.svelte.d.ts +7 -0
- package/dist/fields/viewbar/SortPanel.svelte +85 -0
- package/dist/fields/viewbar/SortPanel.svelte.d.ts +8 -0
- package/dist/fields/viewbar/ViewsMenu.svelte +250 -0
- package/dist/fields/viewbar/ViewsMenu.svelte.d.ts +15 -0
- package/dist/fields/viewbar/panel-focus.d.ts +3 -0
- package/dist/fields/viewbar/panel-focus.js +22 -0
- package/dist/fields/viewbar/sort-state.d.ts +20 -0
- package/dist/fields/viewbar/sort-state.js +24 -0
- package/dist/gallery.d.ts +7 -0
- package/dist/gallery.js +36 -0
- package/dist/gantt/SvGridGantt.svelte +88 -16
- package/dist/gantt/gantt-config.d.ts +28 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +6 -0
- package/dist/install.js +3 -0
- package/dist/license-core.d.ts +3 -3
- package/dist/license.js +1 -0
- package/dist/node/studio.js +105 -4
- package/dist/scheduler-config.d.ts +11 -0
- package/dist/sources/introspect-supabase.js +4 -1
- package/dist/sources/rest.js +6 -2
- package/dist/studio/ui-components.generated.js +93 -1
- package/dist/upgrade-prompt.d.ts +1 -1
- package/dist/version.d.ts +2 -2
- package/dist/version.js +2 -2
- package/dist/wc/GridFooter-f5K0aLe_.js +145 -0
- package/dist/wc/GridMenus-FtSN_8t9.js +1369 -0
- package/dist/wc/PinSplitter-D-qtDhSM.js +132 -0
- package/dist/wc/{SvGrid.helpers-zBkDTryR.js → SvGrid.helpers-B2GEKrxS.js} +84 -36
- package/dist/wc/{SvGridCellEditor-CzUKEbkI.js → SvGridCellEditor-B_cLJrsp.js} +20 -9
- package/dist/wc/conditional-formatting-BXis_oP1.js +189 -0
- package/dist/wc/grid-extension.svelte-Bufq4B4C.js +20 -0
- package/dist/wc/sv-sheet-element.js +7556 -7617
- package/dist/wc/svelte-virtualizer.svelte-ehwOupl-.js +360 -0
- package/dist/wc/{validate-43C03DLP.js → validate-BaMO8UPE.js} +1 -1
- package/package.json +14 -3
- package/dist/designer/assets/GridMenus-CvcASlKv.js +0 -8
- package/dist/designer/assets/SvGrid.helpers-DE5mmORW.js +0 -1
- package/dist/designer/assets/SvGridCellEditor-C7NWcnNC.js +0 -4
- package/dist/designer/assets/SvListBox-BlmmzYcU.js +0 -110
- package/dist/designer/assets/index-KmSEY7Lb.css +0 -1
- package/dist/designer/assets/src-DqHCqori.js +0 -58
- package/dist/designer/assets/validate-B71dE1Cs.js +0 -1
- package/dist/wc/GridMenus-fWbBUiRH.js +0 -1088
- package/dist/wc/svelte-virtualizer.svelte-Dntdh8Ms.js +0 -581
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* The select and user fields' editor (registered as 'fieldSelect'): the
|
|
4
|
+
* cell grows into a panel over itself, with the chosen items on top (each
|
|
5
|
+
* with a remove button), a search box, and the items drawn as the cells
|
|
6
|
+
* draw them: options as colored pills, people as avatar and name.
|
|
7
|
+
*
|
|
8
|
+
* Typing filters the options; one that does not exist yet is offered as
|
|
9
|
+
* "Add option", which adds it to the field (onColumnChange) and picks it
|
|
10
|
+
* (not for people: they come from setFieldUsers; not with `addOptions`
|
|
11
|
+
* false, as in a form, where only the field's options can be picked).
|
|
12
|
+
* Up / Down move, Enter picks. A single select commits on the pick; a
|
|
13
|
+
* multiple select keeps the dropdown open, Backspace in the empty search box
|
|
14
|
+
* removes the last pill, and Enter on nothing, Escape, Tab or a click
|
|
15
|
+
* outside keep what is chosen. Escape on a single select leaves the cell as
|
|
16
|
+
* it was.
|
|
17
|
+
*/
|
|
18
|
+
import { portalToBody } from '@svgrid/grid'
|
|
19
|
+
import { onMount } from 'svelte'
|
|
20
|
+
import { fieldPropsOf } from './registry.svelte'
|
|
21
|
+
import { addSelectOptions } from './field-ops'
|
|
22
|
+
import { OPTION_CYCLE, textOn } from './palette'
|
|
23
|
+
import { getFieldUsers, type FieldUser } from './sources.svelte'
|
|
24
|
+
import UserFace from './UserFace.svelte'
|
|
25
|
+
|
|
26
|
+
type Option = { value: string; label?: string; color?: string; user?: FieldUser }
|
|
27
|
+
let {
|
|
28
|
+
value,
|
|
29
|
+
typed = false,
|
|
30
|
+
row,
|
|
31
|
+
columnDef,
|
|
32
|
+
columnId,
|
|
33
|
+
grid,
|
|
34
|
+
onChange,
|
|
35
|
+
onCommit,
|
|
36
|
+
onCancel,
|
|
37
|
+
onCommitAndMove,
|
|
38
|
+
addOptions = true,
|
|
39
|
+
}: {
|
|
40
|
+
value: unknown
|
|
41
|
+
typed?: boolean
|
|
42
|
+
row?: unknown
|
|
43
|
+
columnDef?: Record<string, unknown>
|
|
44
|
+
columnId: string
|
|
45
|
+
grid?: unknown
|
|
46
|
+
onChange?: (v: unknown) => void
|
|
47
|
+
onCommit: (v?: unknown) => void
|
|
48
|
+
onCancel: () => void
|
|
49
|
+
onCommitAndMove?: (v: unknown, direction: 1 | -1) => void
|
|
50
|
+
/** False: no "Add option", only the field's own options (a form). */
|
|
51
|
+
addOptions?: boolean
|
|
52
|
+
} = $props()
|
|
53
|
+
|
|
54
|
+
const def = $derived((columnDef ?? {}) as { fieldType?: unknown; fieldProps?: Record<string, unknown> })
|
|
55
|
+
// A user field picks people: from setFieldUsers, one or (with `multiple`) several.
|
|
56
|
+
const isUser = $derived(def.fieldType === 'user')
|
|
57
|
+
const multiple = $derived(isUser ? !!fieldPropsOf(def).multiple : def.fieldType === 'multiSelect')
|
|
58
|
+
// svelte-ignore state_referenced_locally
|
|
59
|
+
const declared: Option[] =
|
|
60
|
+
def.fieldType === 'user'
|
|
61
|
+
? getFieldUsers().map((u) => ({ value: u.id, label: u.name, user: u }))
|
|
62
|
+
: ((fieldPropsOf(def).options as Array<{ value: unknown; label?: string; color?: string }> | undefined) ?? []).map(
|
|
63
|
+
(o): Option => ({ value: String(o.value), ...(o.label ? { label: o.label } : {}), ...(o.color ? { color: o.color } : {}) }),
|
|
64
|
+
)
|
|
65
|
+
// New options show here at once; they reach the field when the edit ends.
|
|
66
|
+
let added = $state<Option[]>([])
|
|
67
|
+
const options = $derived([...declared, ...added])
|
|
68
|
+
// Typed on the cell: the text searches the options, and the choices
|
|
69
|
+
// already made are the row's.
|
|
70
|
+
// svelte-ignore state_referenced_locally
|
|
71
|
+
const start = typed ? (row as Record<string, unknown> | undefined)?.[String(columnDef?.field ?? '')] : value
|
|
72
|
+
// svelte-ignore state_referenced_locally
|
|
73
|
+
let picked = $state<string[]>((Array.isArray(start) ? start : start == null || start === '' ? [] : [start]).map(String))
|
|
74
|
+
// svelte-ignore state_referenced_locally
|
|
75
|
+
let search = $state(typed ? String(value ?? '') : '')
|
|
76
|
+
let active = $state(0)
|
|
77
|
+
let finished = false
|
|
78
|
+
|
|
79
|
+
const labelOf = (o: Option) => o.label ?? o.value
|
|
80
|
+
const byValue = $derived(new Map(options.map((o) => [o.value, o])))
|
|
81
|
+
const shown = $derived.by(() => {
|
|
82
|
+
const q = search.trim().toLowerCase()
|
|
83
|
+
return options.filter((o) => (!multiple || !picked.includes(o.value)) && (!q || labelOf(o).toLowerCase().includes(q)))
|
|
84
|
+
})
|
|
85
|
+
const newValue = $derived.by(() => {
|
|
86
|
+
const t = search.trim()
|
|
87
|
+
return t && addOptions && !isUser && !options.some((o) => labelOf(o).toLowerCase() === t.toLowerCase() || o.value.toLowerCase() === t.toLowerCase()) ? t : null
|
|
88
|
+
})
|
|
89
|
+
const count = $derived(shown.length + (newValue ? 1 : 0))
|
|
90
|
+
$effect(() => {
|
|
91
|
+
if (active >= count) active = Math.max(0, count - 1)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
// ---- the panel: the cell grown in place, over the cell and down from it;
|
|
95
|
+
// in the body, so the cell cannot clip it
|
|
96
|
+
let anchor = $state<HTMLElement | null>(null)
|
|
97
|
+
let input = $state<HTMLInputElement | null>(null)
|
|
98
|
+
let list = $state<HTMLElement | null>(null)
|
|
99
|
+
let panel = $state<HTMLElement | null>(null)
|
|
100
|
+
let rect = $state<{ top: number; left: number; width: number; maxHeight: number } | null>(null)
|
|
101
|
+
const PANEL_HEIGHT = 340
|
|
102
|
+
function place() {
|
|
103
|
+
const cell = anchor?.closest('td') ?? anchor
|
|
104
|
+
if (!cell) return
|
|
105
|
+
const r = cell.getBoundingClientRect()
|
|
106
|
+
const width = Math.min(Math.max(r.width + 2, 260), window.innerWidth - 16)
|
|
107
|
+
const left = Math.max(8, Math.min(r.left - 1, window.innerWidth - width - 8))
|
|
108
|
+
// Near the bottom of the window the panel moves up rather than shrink.
|
|
109
|
+
const top = Math.max(8, Math.min(r.top - 1, window.innerHeight - PANEL_HEIGHT - 8))
|
|
110
|
+
rect = { top, left, width, maxHeight: window.innerHeight - top - 8 }
|
|
111
|
+
}
|
|
112
|
+
onMount(() => {
|
|
113
|
+
// A single select opens on its current choice.
|
|
114
|
+
if (!multiple && picked.length) active = Math.max(0, shown.findIndex((o) => o.value === picked[0]))
|
|
115
|
+
place()
|
|
116
|
+
// The search box (caret after any typed text); once more on the next
|
|
117
|
+
// frame, as the double-click that opened the editor can take focus back.
|
|
118
|
+
const takeFocus = () => {
|
|
119
|
+
if (!input || document.activeElement === input) return
|
|
120
|
+
input.focus({ preventScroll: true })
|
|
121
|
+
input.setSelectionRange(search.length, search.length)
|
|
122
|
+
}
|
|
123
|
+
queueMicrotask(takeFocus)
|
|
124
|
+
const frame = requestAnimationFrame(takeFocus)
|
|
125
|
+
const onScroll = (e: Event) => {
|
|
126
|
+
if (!panel?.contains(e.target as Node)) place()
|
|
127
|
+
}
|
|
128
|
+
const onDown = (e: PointerEvent) => {
|
|
129
|
+
const t = e.target as Node
|
|
130
|
+
if (panel?.contains(t) || anchor?.contains(t)) return
|
|
131
|
+
keep()
|
|
132
|
+
}
|
|
133
|
+
window.addEventListener('resize', place)
|
|
134
|
+
window.addEventListener('scroll', onScroll, true)
|
|
135
|
+
document.addEventListener('pointerdown', onDown, true)
|
|
136
|
+
return () => {
|
|
137
|
+
cancelAnimationFrame(frame)
|
|
138
|
+
window.removeEventListener('resize', place)
|
|
139
|
+
window.removeEventListener('scroll', onScroll, true)
|
|
140
|
+
document.removeEventListener('pointerdown', onDown, true)
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
// ---- picking
|
|
145
|
+
const current = (): unknown => (multiple ? [...picked] : (picked[0] ?? null))
|
|
146
|
+
function finish(how: 'commit' | 'cancel' | 'next' | 'prev') {
|
|
147
|
+
if (finished) return
|
|
148
|
+
finished = true
|
|
149
|
+
if (how === 'cancel') return onCancel()
|
|
150
|
+
// Read everything first: committing ends the edit and unmounts this
|
|
151
|
+
// editor, and its props are gone after that.
|
|
152
|
+
const v = current()
|
|
153
|
+
const g = grid
|
|
154
|
+
const id = columnId
|
|
155
|
+
const used = added.filter((o) => picked.includes(o.value))
|
|
156
|
+
if (how === 'commit') onCommit(v)
|
|
157
|
+
else if (onCommitAndMove) onCommitAndMove(v, how === 'next' ? 1 : -1)
|
|
158
|
+
else onCommit(v)
|
|
159
|
+
// New options reach the field after the value, so the grid is not
|
|
160
|
+
// refilling the column while the edit is still open.
|
|
161
|
+
if (used.length && g) addSelectOptions(g as never, id, used)
|
|
162
|
+
}
|
|
163
|
+
const keep = () => finish('commit')
|
|
164
|
+
function choose(i: number) {
|
|
165
|
+
let v: string
|
|
166
|
+
if (i < shown.length) v = shown[i]!.value
|
|
167
|
+
else if (newValue) {
|
|
168
|
+
v = newValue
|
|
169
|
+
added = [...added, { value: v, color: OPTION_CYCLE[options.length % OPTION_CYCLE.length] }]
|
|
170
|
+
} else return
|
|
171
|
+
search = ''
|
|
172
|
+
if (!multiple) {
|
|
173
|
+
picked = [v]
|
|
174
|
+
return keep()
|
|
175
|
+
}
|
|
176
|
+
picked = [...picked, v]
|
|
177
|
+
onChange?.(current())
|
|
178
|
+
active = 0
|
|
179
|
+
}
|
|
180
|
+
function remove(v: string) {
|
|
181
|
+
picked = picked.filter((x) => x !== v)
|
|
182
|
+
onChange?.(current())
|
|
183
|
+
input?.focus()
|
|
184
|
+
}
|
|
185
|
+
function move(by: number) {
|
|
186
|
+
if (!count) return
|
|
187
|
+
active = (active + by + count) % count
|
|
188
|
+
queueMicrotask(() => list?.querySelector(`[data-i="${active}"]`)?.scrollIntoView({ block: 'nearest' }))
|
|
189
|
+
}
|
|
190
|
+
function onKey(e: KeyboardEvent) {
|
|
191
|
+
if (e.key === 'ArrowDown') move(1)
|
|
192
|
+
else if (e.key === 'ArrowUp') move(-1)
|
|
193
|
+
else if (e.key === 'Enter') count ? choose(active) : keep()
|
|
194
|
+
else if (e.key === 'Escape') multiple ? keep() : finish('cancel')
|
|
195
|
+
else if (e.key === 'Tab') finish(e.shiftKey ? 'prev' : 'next')
|
|
196
|
+
else if (e.key === 'Backspace' && !search && multiple && picked.length) remove(picked[picked.length - 1]!)
|
|
197
|
+
else {
|
|
198
|
+
e.stopPropagation()
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
e.preventDefault()
|
|
202
|
+
e.stopPropagation()
|
|
203
|
+
}
|
|
204
|
+
const inputValue = (e: Event) => (e.currentTarget as HTMLInputElement).value
|
|
205
|
+
const listId = `sv-fse-${Math.random().toString(36).slice(2, 8)}`
|
|
206
|
+
</script>
|
|
207
|
+
|
|
208
|
+
<!-- One chosen or offered item, drawn as its cell draws it. -->
|
|
209
|
+
{#snippet item(v: string, removable: boolean)}
|
|
210
|
+
{@const o = byValue.get(v)}
|
|
211
|
+
{@const label = o ? labelOf(o) : v}
|
|
212
|
+
{#if isUser}
|
|
213
|
+
<span class="sv-fse-user" class:is-chip={removable}>
|
|
214
|
+
<UserFace user={o?.user ?? { id: v, name: v }} />
|
|
215
|
+
<span class="sv-fse-user-name">{label}</span>
|
|
216
|
+
{#if removable}{@render removeButton(v, label)}{/if}
|
|
217
|
+
</span>
|
|
218
|
+
{:else}
|
|
219
|
+
<span class="sv-fse-pill" class:is-plain={!o?.color} style:background={o?.color} style:color={o?.color ? textOn(o.color) : undefined}>
|
|
220
|
+
{label}
|
|
221
|
+
{#if removable}{@render removeButton(v, label)}{/if}
|
|
222
|
+
</span>
|
|
223
|
+
{/if}
|
|
224
|
+
{/snippet}
|
|
225
|
+
{#snippet removeButton(v: string, label: string)}
|
|
226
|
+
<button type="button" class="sv-fse-x" aria-label={`Remove ${label}`} onmousedown={(e) => e.preventDefault()} onclick={() => remove(v)}>
|
|
227
|
+
<svg viewBox="0 0 24 24" width="10" height="10" aria-hidden="true"><path d="M6 6l12 12M18 6 6 18" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" /></svg>
|
|
228
|
+
</button>
|
|
229
|
+
{/snippet}
|
|
230
|
+
|
|
231
|
+
<span class="sv-fse-cell" bind:this={anchor}>
|
|
232
|
+
{#each picked as v (v)}
|
|
233
|
+
{@render item(v, false)}
|
|
234
|
+
{/each}
|
|
235
|
+
</span>
|
|
236
|
+
|
|
237
|
+
{#if rect}
|
|
238
|
+
<div
|
|
239
|
+
class="sv-fse-panel"
|
|
240
|
+
bind:this={panel}
|
|
241
|
+
use:portalToBody
|
|
242
|
+
style:top={`${rect.top}px`}
|
|
243
|
+
style:left={`${rect.left}px`}
|
|
244
|
+
style:width={`${rect.width}px`}
|
|
245
|
+
style:max-height={`${rect.maxHeight}px`}
|
|
246
|
+
>
|
|
247
|
+
{#if picked.length}
|
|
248
|
+
<div class="sv-fse-chosen">
|
|
249
|
+
{#each picked as v (v)}
|
|
250
|
+
{@render item(v, true)}
|
|
251
|
+
{/each}
|
|
252
|
+
</div>
|
|
253
|
+
{/if}
|
|
254
|
+
<input
|
|
255
|
+
bind:this={input}
|
|
256
|
+
class="sv-fse-search"
|
|
257
|
+
placeholder={isUser ? 'Find a person' : 'Find an option'}
|
|
258
|
+
role="combobox"
|
|
259
|
+
aria-expanded="true"
|
|
260
|
+
aria-controls={listId}
|
|
261
|
+
aria-activedescendant={count ? `${listId}-${active}` : undefined}
|
|
262
|
+
aria-label={isUser ? (multiple ? 'Add a person' : 'Choose a person') : multiple ? 'Add an option' : 'Choose an option'}
|
|
263
|
+
value={search}
|
|
264
|
+
oninput={(e) => {
|
|
265
|
+
search = inputValue(e)
|
|
266
|
+
active = 0
|
|
267
|
+
}}
|
|
268
|
+
onkeydown={onKey}
|
|
269
|
+
/>
|
|
270
|
+
<ul class="sv-fse-list" id={listId} role="listbox" bind:this={list}>
|
|
271
|
+
{#each shown as o, i (o.value)}
|
|
272
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
273
|
+
<!-- svelte-ignore a11y_interactive_supports_focus -->
|
|
274
|
+
<li
|
|
275
|
+
id={`${listId}-${i}`}
|
|
276
|
+
data-i={i}
|
|
277
|
+
role="option"
|
|
278
|
+
aria-selected={picked.includes(o.value)}
|
|
279
|
+
class="sv-fse-option"
|
|
280
|
+
class:is-active={i === active}
|
|
281
|
+
onpointermove={() => (active = i)}
|
|
282
|
+
onmousedown={(e) => e.preventDefault()}
|
|
283
|
+
onclick={() => choose(i)}
|
|
284
|
+
>
|
|
285
|
+
{@render item(o.value, false)}
|
|
286
|
+
{#if !multiple && picked[0] === o.value}
|
|
287
|
+
<svg class="sv-fse-check" viewBox="0 0 24 24" width="14" height="14" aria-hidden="true"><path d="M5 12l5 5 9-10" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" /></svg>
|
|
288
|
+
{/if}
|
|
289
|
+
</li>
|
|
290
|
+
{/each}
|
|
291
|
+
{#if newValue}
|
|
292
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
293
|
+
<!-- svelte-ignore a11y_interactive_supports_focus -->
|
|
294
|
+
<li
|
|
295
|
+
id={`${listId}-${shown.length}`}
|
|
296
|
+
data-i={shown.length}
|
|
297
|
+
role="option"
|
|
298
|
+
aria-selected="false"
|
|
299
|
+
class="sv-fse-option sv-fse-add"
|
|
300
|
+
class:is-active={active === shown.length}
|
|
301
|
+
onpointermove={() => (active = shown.length)}
|
|
302
|
+
onmousedown={(e) => e.preventDefault()}
|
|
303
|
+
onclick={() => choose(shown.length)}
|
|
304
|
+
>
|
|
305
|
+
<span class="sv-fse-add-label">Add option</span>
|
|
306
|
+
<span class="sv-fse-pill" style:background={OPTION_CYCLE[options.length % OPTION_CYCLE.length]} style:color={textOn(OPTION_CYCLE[options.length % OPTION_CYCLE.length])}>{newValue}</span>
|
|
307
|
+
</li>
|
|
308
|
+
{/if}
|
|
309
|
+
{#if !count}
|
|
310
|
+
<li class="sv-fse-empty">
|
|
311
|
+
{#if isUser}{multiple && options.length && !search ? 'Everyone is chosen' : options.length ? 'Nobody matches' : 'No people (setFieldUsers)'}
|
|
312
|
+
{:else}{multiple && options.length && !search ? 'Every option is chosen' : search && options.length ? 'No option matches' : 'No options'}{/if}
|
|
313
|
+
</li>
|
|
314
|
+
{/if}
|
|
315
|
+
</ul>
|
|
316
|
+
</div>
|
|
317
|
+
{/if}
|
|
318
|
+
|
|
319
|
+
<style>
|
|
320
|
+
.sv-fse-cell { display: flex; align-items: center; gap: 4px; height: 100%; padding: 0 6px; box-sizing: border-box; overflow: hidden; }
|
|
321
|
+
.sv-fse-chosen { display: flex; flex-wrap: wrap; gap: 4px; padding: 2px 2px 8px; }
|
|
322
|
+
.sv-fse-pill {
|
|
323
|
+
display: inline-flex; align-items: center; gap: 4px; max-width: 100%; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
|
|
324
|
+
padding: 0 8px; border-radius: 999px; font-size: 12px; line-height: 20px;
|
|
325
|
+
}
|
|
326
|
+
.sv-fse-pill.is-plain { background: var(--sg-header-bg, #eef0f3); color: var(--sg-fg, #0f172a); border: 1px solid var(--sg-border, #e2e8f0); line-height: 18px; }
|
|
327
|
+
.sv-fse-user { display: inline-flex; align-items: center; gap: 6px; min-width: 0; max-width: 100%; font-size: 13px; }
|
|
328
|
+
.sv-fse-user.is-chip { padding: 2px 6px 2px 2px; border-radius: 999px; background: var(--sg-header-bg, #eef0f3); border: 1px solid var(--sg-border, #e2e8f0); }
|
|
329
|
+
.sv-fse-user-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
330
|
+
.sv-fse-x { display: inline-flex; padding: 2px; margin-inline-end: -4px; border: 0; border-radius: 999px; background: none; color: inherit; opacity: 0.7; cursor: pointer; }
|
|
331
|
+
.sv-fse-x:hover { opacity: 1; background: color-mix(in srgb, currentColor 18%, transparent); }
|
|
332
|
+
.sv-fse-panel {
|
|
333
|
+
position: fixed; z-index: 2147483646; display: flex; flex-direction: column; box-sizing: border-box; padding: 6px;
|
|
334
|
+
border: 1px solid var(--sg-border, #e2e8f0); border-radius: var(--sg-radius, 6px);
|
|
335
|
+
background: var(--sg-bg, #fff); color: var(--sg-fg, #0f172a);
|
|
336
|
+
box-shadow: 0 8px 24px rgb(0 0 0 / 0.16); font-size: 13px;
|
|
337
|
+
}
|
|
338
|
+
.sv-fse-search {
|
|
339
|
+
flex: none; height: 30px; padding: 0 8px; font: inherit; box-sizing: border-box; width: 100%;
|
|
340
|
+
border: 1px solid var(--sg-input-border, var(--sg-border, #e2e8f0)); border-radius: var(--sg-radius, 6px);
|
|
341
|
+
background: var(--sg-input-bg, var(--sg-bg, #fff)); color: var(--sg-fg, #0f172a);
|
|
342
|
+
}
|
|
343
|
+
.sv-fse-search:focus { outline: none; border-color: var(--sg-accent, #2563eb); }
|
|
344
|
+
.sv-fse-list { list-style: none; margin: 6px 0 0; padding: 0; overflow: auto; min-height: 0; }
|
|
345
|
+
.sv-fse-option { display: flex; align-items: center; gap: 8px; min-height: 30px; padding: 0 6px; border-radius: 4px; cursor: pointer; }
|
|
346
|
+
.sv-fse-option.is-active { background: var(--sg-row-hover-bg, #f1f5f9); }
|
|
347
|
+
.sv-fse-check { margin-inline-start: auto; flex: none; }
|
|
348
|
+
.sv-fse-add-label { flex: none; color: var(--sg-muted, #64748b); }
|
|
349
|
+
.sv-fse-empty { padding: 8px 6px; color: var(--sg-muted, #64748b); }
|
|
350
|
+
</style>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
value: unknown;
|
|
3
|
+
typed?: boolean;
|
|
4
|
+
row?: unknown;
|
|
5
|
+
columnDef?: Record<string, unknown>;
|
|
6
|
+
columnId: string;
|
|
7
|
+
grid?: unknown;
|
|
8
|
+
onChange?: (v: unknown) => void;
|
|
9
|
+
onCommit: (v?: unknown) => void;
|
|
10
|
+
onCancel: () => void;
|
|
11
|
+
onCommitAndMove?: (v: unknown, direction: 1 | -1) => void;
|
|
12
|
+
/** False: no "Add option", only the field's own options (a form). */
|
|
13
|
+
addOptions?: boolean;
|
|
14
|
+
};
|
|
15
|
+
declare const SelectEditor: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
16
|
+
type SelectEditor = ReturnType<typeof SelectEditor>;
|
|
17
|
+
export default SelectEditor;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* A field's cell in the footer summary row (the grid's `summary` prop): the
|
|
4
|
+
* summary it shows, named ("Sum $4,320", "Empty 3"), or a quiet
|
|
5
|
+
* "Summarize" on hover. A click opens the summaries that fit the field
|
|
6
|
+
* (sum, average... for numbers; checked for a checkbox; earliest, latest
|
|
7
|
+
* for dates; empty, filled, unique for all); the choice is kept in the
|
|
8
|
+
* field (`fieldProps.summary`, through onColumnChange). The primary field,
|
|
9
|
+
* until one is picked for it, shows how many records the view holds.
|
|
10
|
+
*/
|
|
11
|
+
import { portalToBody } from '@svgrid/grid'
|
|
12
|
+
import { fieldPropsOf } from './registry.svelte'
|
|
13
|
+
import { setFieldSummary } from './field-ops'
|
|
14
|
+
import { summaryChoices, summaryLabel, type SummaryKind } from './summaries'
|
|
15
|
+
import { groupLeaves, leafFilter, primaryIdOf } from './grid-access'
|
|
16
|
+
|
|
17
|
+
let { ctrl, column, text }: { ctrl: any; column: { id: string; columnDef: Record<string, any> }; text: string } = $props()
|
|
18
|
+
|
|
19
|
+
const isField = $derived(!!column.columnDef.fieldType)
|
|
20
|
+
const kind = $derived((fieldPropsOf(column.columnDef as never).summary as SummaryKind | undefined) ?? 'none')
|
|
21
|
+
const choices = $derived(isField ? summaryChoices(column.columnDef) : [])
|
|
22
|
+
// The record count, in the primary field's cell while it has no summary of its own.
|
|
23
|
+
const showsCount = $derived(kind === 'none' && primaryIdOf(ctrl) === column.id)
|
|
24
|
+
type CountRow = { depth?: number; leafCount?: number; getCanExpand?: () => boolean; __treeRow?: boolean }
|
|
25
|
+
const count = $derived.by(() => {
|
|
26
|
+
if (!showsCount) return 0
|
|
27
|
+
const rows = (ctrl.allRows ?? []) as CountRow[]
|
|
28
|
+
// Grouped: a collapsed group hides its rows, which are still records; the
|
|
29
|
+
// grid groups before it filters, so a group's size counts the view's filter.
|
|
30
|
+
const tops = rows.filter((r) => (r.depth ?? 0) === 0 && !r.__treeRow && typeof r.getCanExpand === 'function' && r.getCanExpand() && r.leafCount != null)
|
|
31
|
+
if (!tops.length) return rows.length
|
|
32
|
+
const keep = leafFilter(ctrl)
|
|
33
|
+
return keep ? tops.reduce((n, r) => n + groupLeaves(r as never, keep).length, 0) : tops.reduce((n, r) => n + (r.leafCount ?? 0), 0)
|
|
34
|
+
})
|
|
35
|
+
const right = $derived(column.columnDef.align === 'right' || (column.columnDef.align == null && column.columnDef.cellDataType === 'number'))
|
|
36
|
+
|
|
37
|
+
let button = $state<HTMLButtonElement | null>(null)
|
|
38
|
+
let menu = $state<HTMLElement | null>(null)
|
|
39
|
+
let at = $state<{ left: number; bottom: number; width: number } | null>(null)
|
|
40
|
+
|
|
41
|
+
function toggle() {
|
|
42
|
+
if (at) return close()
|
|
43
|
+
const r = button!.getBoundingClientRect()
|
|
44
|
+
// The footer sits at the grid's bottom: the menu opens upward.
|
|
45
|
+
at = { left: Math.max(8, Math.min(r.left, window.innerWidth - 220)), bottom: window.innerHeight - r.top + 4, width: Math.max(r.width, 200) }
|
|
46
|
+
queueMicrotask(() => menu?.querySelector<HTMLElement>('[aria-checked="true"]')?.focus())
|
|
47
|
+
}
|
|
48
|
+
function close() {
|
|
49
|
+
at = null
|
|
50
|
+
}
|
|
51
|
+
function pick(value: SummaryKind) {
|
|
52
|
+
close()
|
|
53
|
+
setFieldSummary(ctrl, column.id, value)
|
|
54
|
+
button?.focus()
|
|
55
|
+
}
|
|
56
|
+
function onMenuKey(e: KeyboardEvent) {
|
|
57
|
+
const items = [...(menu?.querySelectorAll<HTMLElement>('[role="menuitemradio"]') ?? [])]
|
|
58
|
+
const i = items.indexOf(document.activeElement as HTMLElement)
|
|
59
|
+
if (e.key === 'Escape') {
|
|
60
|
+
close()
|
|
61
|
+
button?.focus()
|
|
62
|
+
} else if (e.key === 'ArrowDown') items[(i + 1) % items.length]?.focus()
|
|
63
|
+
else if (e.key === 'ArrowUp') items[(i - 1 + items.length) % items.length]?.focus()
|
|
64
|
+
else return
|
|
65
|
+
e.preventDefault()
|
|
66
|
+
}
|
|
67
|
+
function dismissOutside(node: HTMLElement) {
|
|
68
|
+
const onDown = (e: PointerEvent) => {
|
|
69
|
+
if (!node.contains(e.target as Node) && !button?.contains(e.target as Node)) close()
|
|
70
|
+
}
|
|
71
|
+
document.addEventListener('pointerdown', onDown, true)
|
|
72
|
+
return { destroy: () => document.removeEventListener('pointerdown', onDown, true) }
|
|
73
|
+
}
|
|
74
|
+
</script>
|
|
75
|
+
|
|
76
|
+
{#if !isField}
|
|
77
|
+
<span class="sv-grid-cell-text">{text}</span>
|
|
78
|
+
{:else}
|
|
79
|
+
<button
|
|
80
|
+
type="button"
|
|
81
|
+
class="sv-fsm"
|
|
82
|
+
class:is-right={right}
|
|
83
|
+
class:is-set={kind !== 'none' || showsCount}
|
|
84
|
+
bind:this={button}
|
|
85
|
+
aria-haspopup="menu"
|
|
86
|
+
aria-expanded={!!at}
|
|
87
|
+
aria-label={showsCount ? `${count} ${count === 1 ? 'record' : 'records'}, summarize ${column.columnDef.header ?? column.id}` : kind === 'none' ? `Summarize ${column.columnDef.header ?? column.id}` : `${summaryLabel(kind)} ${text}, change summary`}
|
|
88
|
+
onclick={toggle}
|
|
89
|
+
>
|
|
90
|
+
{#if showsCount}
|
|
91
|
+
<span class="sv-fsm-value">{count}</span>
|
|
92
|
+
<span class="sv-fsm-label">{count === 1 ? 'record' : 'records'}</span>
|
|
93
|
+
{:else if kind === 'none'}
|
|
94
|
+
<span class="sv-fsm-hint">Summarize</span>
|
|
95
|
+
<svg viewBox="0 0 24 24" width="12" height="12" aria-hidden="true"><path d="M6 9l6 6 6-6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /></svg>
|
|
96
|
+
{:else}
|
|
97
|
+
<span class="sv-fsm-label">{summaryLabel(kind)}</span>
|
|
98
|
+
<span class="sv-fsm-value">{text}</span>
|
|
99
|
+
{/if}
|
|
100
|
+
</button>
|
|
101
|
+
{#if at}
|
|
102
|
+
<div
|
|
103
|
+
class="sv-fsm-menu"
|
|
104
|
+
role="menu"
|
|
105
|
+
tabindex="-1"
|
|
106
|
+
aria-label="Summary"
|
|
107
|
+
bind:this={menu}
|
|
108
|
+
use:portalToBody
|
|
109
|
+
use:dismissOutside
|
|
110
|
+
style:left={`${at.left}px`}
|
|
111
|
+
style:bottom={`${at.bottom}px`}
|
|
112
|
+
style:min-width={`${at.width}px`}
|
|
113
|
+
onkeydown={onMenuKey}
|
|
114
|
+
>
|
|
115
|
+
{#each choices as c (c.value)}
|
|
116
|
+
<button type="button" role="menuitemradio" aria-checked={c.value === kind} class="sv-fsm-item" onclick={() => pick(c.value)}>
|
|
117
|
+
<span>{c.label}</span>
|
|
118
|
+
{#if c.value === kind}<svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true"><path d="M5 12l5 5 9-10" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" /></svg>{/if}
|
|
119
|
+
</button>
|
|
120
|
+
{/each}
|
|
121
|
+
</div>
|
|
122
|
+
{/if}
|
|
123
|
+
{/if}
|
|
124
|
+
|
|
125
|
+
<style>
|
|
126
|
+
.sv-fsm {
|
|
127
|
+
display: flex; align-items: center; gap: 6px; width: 100%; height: 100%; min-height: 28px; padding: 0 8px; box-sizing: border-box;
|
|
128
|
+
border: 0; background: none; color: var(--sg-fg, #0f172a); font: inherit; font-size: 13px; font-weight: 400; cursor: pointer; text-align: start;
|
|
129
|
+
}
|
|
130
|
+
.sv-fsm.is-right { justify-content: flex-end; }
|
|
131
|
+
.sv-fsm-hint { color: var(--sg-muted, #64748b); }
|
|
132
|
+
/* "Summarize" is quiet: it shows on the footer row's hover and on focus. */
|
|
133
|
+
.sv-fsm:not(.is-set) { opacity: 0; }
|
|
134
|
+
:global(.sv-grid-summary-row:hover) .sv-fsm:not(.is-set),
|
|
135
|
+
.sv-fsm:not(.is-set):focus-visible,
|
|
136
|
+
.sv-fsm[aria-expanded='true'] { opacity: 1; }
|
|
137
|
+
/* A base's summary row is drawn as the "+ Add row" line under it is: on
|
|
138
|
+
the column headers' ground, one line above, no column lines through it.
|
|
139
|
+
Every cell of it (row number, pinned, spacers) the same; `--sg-summary-bg`
|
|
140
|
+
picks a ground of its own. */
|
|
141
|
+
:global(.sv-fbase .sv-grid-foot > .sv-grid-summary-row > :is(th, td)) {
|
|
142
|
+
background: var(--sg-summary-bg, var(--sg-header-bg, #f5f7fb));
|
|
143
|
+
border-top: 1px solid var(--sg-border, #e2e8f0);
|
|
144
|
+
border-bottom: 1px solid var(--sg-border, #e2e8f0);
|
|
145
|
+
/* One band: no column lines through it, no seam by the row numbers. */
|
|
146
|
+
border-inline-end-color: transparent;
|
|
147
|
+
}
|
|
148
|
+
.sv-fsm-label { color: var(--sg-muted, #64748b); font-weight: 400; }
|
|
149
|
+
.sv-fsm-value { font-weight: 600; font-variant-numeric: tabular-nums; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
150
|
+
.sv-fsm-menu {
|
|
151
|
+
position: fixed; z-index: 2147483646; display: flex; flex-direction: column; padding: 4px; max-height: 60vh; overflow: auto;
|
|
152
|
+
border: 1px solid var(--sg-border, #e2e8f0); border-radius: var(--sg-radius, 6px);
|
|
153
|
+
background: var(--sg-bg, #fff); color: var(--sg-fg, #0f172a); box-shadow: 0 8px 24px rgb(0 0 0 / 0.16);
|
|
154
|
+
}
|
|
155
|
+
.sv-fsm-item {
|
|
156
|
+
display: flex; align-items: center; justify-content: space-between; gap: 12px; height: 30px; padding: 0 10px;
|
|
157
|
+
border: 0; border-radius: 4px; background: none; color: inherit; font: inherit; font-size: 13px; text-align: start; cursor: pointer;
|
|
158
|
+
}
|
|
159
|
+
.sv-fsm-item:hover, .sv-fsm-item:focus-visible { background: var(--sg-row-hover-bg, #f1f5f9); outline: none; }
|
|
160
|
+
</style>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
ctrl: any;
|
|
3
|
+
column: {
|
|
4
|
+
id: string;
|
|
5
|
+
columnDef: Record<string, any>;
|
|
6
|
+
};
|
|
7
|
+
text: string;
|
|
8
|
+
};
|
|
9
|
+
declare const SummaryCell: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
10
|
+
type SummaryCell = ReturnType<typeof SummaryCell>;
|
|
11
|
+
export default SummaryCell;
|