@svgrid/enterprise 3.1.7 → 3.1.9
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 +11375 -10805
- 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-DtBIaQno.js → index-BPU2KNc5.js} +396 -395
- package/dist/designer/assets/index-BmvG99kR.css +1 -0
- 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 +141 -20
- 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/emit-project.js +25 -6
- package/dist/studio/sveltekit3.js +13 -7
- package/dist/studio/ui-components.generated.js +94 -2
- 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 +7561 -7618
- 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,310 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* A row opened as a record (`expandRows`, Space on a cell, a gallery card,
|
|
4
|
+
* a linked record's chip): every field of the row, its name with the
|
|
5
|
+
* type's icon over its value, the value drawn as the cell draws it. A
|
|
6
|
+
* click on an editable value opens the same editor the cell uses; a value
|
|
7
|
+
* is written through the grid's own edit path, so undo, validation,
|
|
8
|
+
* onCellValueChange and "last modified" fields behave as in the cell.
|
|
9
|
+
* Fields hidden in the grid are listed under the others, folded. Beside
|
|
10
|
+
* the fields, the record's activity: its changes and its comments.
|
|
11
|
+
* Previous and next step through the rows in the grid's order.
|
|
12
|
+
*/
|
|
13
|
+
import { onMount, untrack } from 'svelte'
|
|
14
|
+
import { getRecordRequest, type RecordRequest } from './editor-request.svelte'
|
|
15
|
+
import { SvModal } from '@svgrid/grid'
|
|
16
|
+
import FieldInput from './FieldInput.svelte'
|
|
17
|
+
import { getFieldType } from './registry.svelte'
|
|
18
|
+
import { cellText, fieldColumns, primaryIdOf, type FieldColumn } from './grid-access'
|
|
19
|
+
import { addComment, changesOf, commentsOf, timeAgo } from './activity.svelte'
|
|
20
|
+
import { findFieldUser } from './sources.svelte'
|
|
21
|
+
|
|
22
|
+
let {
|
|
23
|
+
ctrl,
|
|
24
|
+
rowId,
|
|
25
|
+
editPrimary = false,
|
|
26
|
+
focus,
|
|
27
|
+
onClose,
|
|
28
|
+
}: {
|
|
29
|
+
ctrl: any
|
|
30
|
+
rowId: string
|
|
31
|
+
editPrimary?: boolean
|
|
32
|
+
/** 'comment': open with the comment box focused (also read from the open request). */
|
|
33
|
+
focus?: 'comment'
|
|
34
|
+
onClose: () => void
|
|
35
|
+
} = $props()
|
|
36
|
+
|
|
37
|
+
type Col = { id: string; columnDef: Record<string, any> }
|
|
38
|
+
type Row = { id: string; original: unknown; getCellValueByColumnId: (id: string) => unknown }
|
|
39
|
+
|
|
40
|
+
// svelte-ignore state_referenced_locally
|
|
41
|
+
let current = $state(rowId)
|
|
42
|
+
let open = $state(true)
|
|
43
|
+
let editing = $state<string | null>(null)
|
|
44
|
+
let showHidden = $state(false)
|
|
45
|
+
let comment = $state('')
|
|
46
|
+
|
|
47
|
+
const rows = $derived((ctrl.allRows ?? []) as Row[])
|
|
48
|
+
const index = $derived(rows.findIndex((r) => r.id === current))
|
|
49
|
+
const row = $derived(index >= 0 ? rows[index]! : null)
|
|
50
|
+
// Where the record was last seen. An edit can take it out of the view (a
|
|
51
|
+
// filter it no longer matches); Previous and Next then go on from there:
|
|
52
|
+
// the row that followed it now sits at that position.
|
|
53
|
+
let lastIndex = 0
|
|
54
|
+
const at = $derived.by(() => {
|
|
55
|
+
if (index >= 0) lastIndex = index
|
|
56
|
+
return lastIndex
|
|
57
|
+
})
|
|
58
|
+
// Both read `at` on every change of `index`, so it never misses a position.
|
|
59
|
+
const canPrev = $derived(at > 0 && rows.length > 0)
|
|
60
|
+
const canNext = $derived(at < rows.length - (index >= 0 ? 1 : 0))
|
|
61
|
+
const columns = $derived(
|
|
62
|
+
((ctrl.allColumns ?? []) as Col[]).filter((c) => !c.id.startsWith('__') && (c.columnDef.field != null || c.columnDef.fieldFn != null)),
|
|
63
|
+
)
|
|
64
|
+
// Fields the grid hides: shown here, read-only (an edit goes through the grid's cells).
|
|
65
|
+
const hidden = $derived.by<Col[]>(() => {
|
|
66
|
+
const shownIds = new Set(columns.map((c) => c.id))
|
|
67
|
+
return fieldColumns(ctrl)
|
|
68
|
+
.filter((c) => !shownIds.has(c.id))
|
|
69
|
+
.map((c) => ({ id: c.id, columnDef: c.def }))
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
// Through the grid's edit overlay, as the cells read it: an edit writes
|
|
73
|
+
// there (and into the row object), so a value changed here or in the grid
|
|
74
|
+
// shows at once.
|
|
75
|
+
function valueOf(c: Col): unknown {
|
|
76
|
+
if (!row) return undefined
|
|
77
|
+
const base = c.columnDef.fieldFn ? c.columnDef.fieldFn(row.original) : (row.original as Record<string, unknown>)?.[c.columnDef.field]
|
|
78
|
+
return ctrl.getCellDisplayValue(row.id, c.id, columns.includes(c) ? row.getCellValueByColumnId(c.id) : base)
|
|
79
|
+
}
|
|
80
|
+
const asField = (c: Col): FieldColumn => ({ id: c.id, field: String(c.columnDef.field ?? c.id), header: label(c), fieldType: c.columnDef.fieldType, def: c.columnDef, visible: true })
|
|
81
|
+
// The record's name: the primary field, else its first text field, else the first field.
|
|
82
|
+
const primaryId = $derived(primaryIdOf(ctrl))
|
|
83
|
+
const primary = $derived(columns.find((c) => c.id === primaryId) ?? columns.find((c) => c.columnDef.fieldType === 'text' || (!c.columnDef.fieldType && c.columnDef.editorType == null)) ?? columns[0])
|
|
84
|
+
const title = $derived(row && primary ? cellText(asField(primary), valueOf(primary), row) || 'Untitled' : 'Record')
|
|
85
|
+
function label(c: Col): string {
|
|
86
|
+
return typeof c.columnDef.header === 'string' ? c.columnDef.header : c.id
|
|
87
|
+
}
|
|
88
|
+
const iconOf = (c: Col) => (c.columnDef.fieldType ? getFieldType(c.columnDef.fieldType)?.icon : undefined)
|
|
89
|
+
function editable(c: Col): boolean {
|
|
90
|
+
if (!row || !ctrl.editingEnabled) return false
|
|
91
|
+
const ci = (ctrl.allColumns as Col[]).findIndex((x) => x.id === c.id)
|
|
92
|
+
if (ci < 0) return false
|
|
93
|
+
return ctrl.isCellEditableAt ? !!ctrl.isCellEditableAt(index, ci) : c.columnDef.editable !== false
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// A record just made opens with its name being typed.
|
|
97
|
+
onMount(() => {
|
|
98
|
+
if (editPrimary && primary && editable(primary)) editing = primary.id
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
// "Comment" in a record or card menu opens the record with `focus: 'comment'`:
|
|
102
|
+
// the comment box takes focus, on open or when the request comes later.
|
|
103
|
+
let commentBox = $state<HTMLTextAreaElement | null>(null)
|
|
104
|
+
const commentFocusFor = (r: RecordRequest | null) => r?.focus === 'comment' && r.ctrl === ctrl
|
|
105
|
+
const initialFocus = () => (focus === 'comment' || commentFocusFor(getRecordRequest()) ? commentBox : null)
|
|
106
|
+
$effect(() => {
|
|
107
|
+
const r = getRecordRequest()
|
|
108
|
+
if (!commentFocusFor(r)) return
|
|
109
|
+
untrack(() => {
|
|
110
|
+
if (r!.rowId !== current) current = r!.rowId
|
|
111
|
+
queueMicrotask(() => commentBox?.focus())
|
|
112
|
+
})
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
/** Write a value the way an edit in the cell does. */
|
|
116
|
+
function commit(c: Col, v: unknown) {
|
|
117
|
+
editing = null
|
|
118
|
+
if (!row || v === undefined) return
|
|
119
|
+
const at = (ctrl.allRows as Row[]).findIndex((r) => r.id === current)
|
|
120
|
+
if (at < 0 || !ctrl.startEditing(at, c.id)) return
|
|
121
|
+
ctrl.updateEditingCellValue(v)
|
|
122
|
+
ctrl.saveEditingCell()
|
|
123
|
+
}
|
|
124
|
+
/** Tab from a field: the next (or previous) editable one. */
|
|
125
|
+
function editBeside(c: Col, dir: 1 | -1) {
|
|
126
|
+
const at = columns.findIndex((x) => x.id === c.id)
|
|
127
|
+
for (let i = at + dir; i >= 0 && i < columns.length; i += dir) {
|
|
128
|
+
const next = columns[i]!
|
|
129
|
+
if (editable(next) && next.columnDef.editorType !== 'checkbox') {
|
|
130
|
+
editing = next.id
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function go(by: 1 | -1) {
|
|
136
|
+
const next = index >= 0 ? rows[index + by] : rows[Math.min(at, rows.length) - (by < 0 ? 1 : 0)]
|
|
137
|
+
if (!next) return
|
|
138
|
+
editing = null
|
|
139
|
+
current = next.id
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ---- Activity ---------------------------------------------------------------
|
|
143
|
+
const allCols = $derived([...columns, ...hidden])
|
|
144
|
+
const feed = $derived.by(() => {
|
|
145
|
+
if (!row) return []
|
|
146
|
+
const edits = changesOf(ctrl, row.id).map((ch) => {
|
|
147
|
+
const c = allCols.find((x) => x.id === ch.columnId)
|
|
148
|
+
return { key: `e${ch.at}${ch.columnId}`, at: ch.at, by: ch.by, automation: ch.automation, kind: 'edit' as const, field: c ? label(c) : ch.columnId, text: c ? cellText(asField(c), ch.value, row) : String(ch.value ?? '') }
|
|
149
|
+
})
|
|
150
|
+
const notes = commentsOf(ctrl, row.id).map((n) => ({ key: n.id, at: n.at, by: n.by, automation: undefined, kind: 'comment' as const, field: '', text: n.text }))
|
|
151
|
+
return [...edits, ...notes].sort((a, b) => a.at - b.at)
|
|
152
|
+
})
|
|
153
|
+
const who = (item: { by?: string; automation?: string }) =>
|
|
154
|
+
item.automation ? `Automation: ${item.automation}` : item.by ? (findFieldUser(item.by)?.name ?? 'Someone') : 'You'
|
|
155
|
+
function send() {
|
|
156
|
+
if (!row || !comment.trim()) return
|
|
157
|
+
addComment(ctrl, row.id, comment)
|
|
158
|
+
comment = ''
|
|
159
|
+
}
|
|
160
|
+
</script>
|
|
161
|
+
|
|
162
|
+
{#snippet field(c: Col, canEdit: boolean)}
|
|
163
|
+
<div class="sv-frv-field">
|
|
164
|
+
<div class="sv-frv-label">
|
|
165
|
+
{#if iconOf(c)}<svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true"><path d={iconOf(c)} fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" /></svg>{/if}
|
|
166
|
+
<span>{label(c)}</span>
|
|
167
|
+
{#if !canEdit}<span class="sv-frv-ro">{columns.includes(c) ? 'read-only' : 'hidden in the grid'}</span>{/if}
|
|
168
|
+
</div>
|
|
169
|
+
<FieldInput
|
|
170
|
+
{ctrl}
|
|
171
|
+
column={c}
|
|
172
|
+
value={valueOf(c)}
|
|
173
|
+
row={row ? { id: row.id, original: row.original } : null}
|
|
174
|
+
rowId={row?.id ?? ''}
|
|
175
|
+
label={label(c)}
|
|
176
|
+
editable={canEdit}
|
|
177
|
+
editing={editing === c.id}
|
|
178
|
+
onStart={() => (editing = c.id)}
|
|
179
|
+
onStop={() => (editing = null)}
|
|
180
|
+
onCommit={(v) => commit(c, v)}
|
|
181
|
+
onTab={(dir) => editBeside(c, dir)}
|
|
182
|
+
/>
|
|
183
|
+
</div>
|
|
184
|
+
{/snippet}
|
|
185
|
+
|
|
186
|
+
<SvModal bind:open {title} width={940} {onClose} {initialFocus}>
|
|
187
|
+
{#if !row}
|
|
188
|
+
<p class="sv-frv-gone">This row is not in the grid's view any more.</p>
|
|
189
|
+
{:else}
|
|
190
|
+
<div class="sv-frv-layout">
|
|
191
|
+
<div class="sv-frv-fields">
|
|
192
|
+
{#each columns as c (c.id)}
|
|
193
|
+
{@render field(c, editable(c))}
|
|
194
|
+
{/each}
|
|
195
|
+
{#if hidden.length}
|
|
196
|
+
<button type="button" class="sv-frv-hidden-toggle" aria-expanded={showHidden} onclick={() => (showHidden = !showHidden)}>
|
|
197
|
+
{showHidden ? 'Hide' : 'Show'} {hidden.length} hidden field{hidden.length === 1 ? '' : 's'}
|
|
198
|
+
</button>
|
|
199
|
+
{#if showHidden}
|
|
200
|
+
{#each hidden as c (c.id)}
|
|
201
|
+
{@render field(c, false)}
|
|
202
|
+
{/each}
|
|
203
|
+
{/if}
|
|
204
|
+
{/if}
|
|
205
|
+
</div>
|
|
206
|
+
<aside class="sv-frv-activity" aria-label="Activity">
|
|
207
|
+
<h3 class="sv-frv-activity-title">Activity</h3>
|
|
208
|
+
<ol class="sv-frv-feed">
|
|
209
|
+
{#each feed as item (item.key)}
|
|
210
|
+
<li class="sv-frv-item" class:is-comment={item.kind === 'comment'}>
|
|
211
|
+
<div class="sv-frv-item-head"><strong>{who(item)}</strong>{item.kind === 'edit' ? ` changed ${item.field}` : ''}<span class="sv-frv-when">{timeAgo(item.at)}</span></div>
|
|
212
|
+
<div class="sv-frv-item-body">{item.text || (item.kind === 'edit' ? '(cleared)' : '')}</div>
|
|
213
|
+
</li>
|
|
214
|
+
{:else}
|
|
215
|
+
<li class="sv-frv-none">No changes or comments yet.</li>
|
|
216
|
+
{/each}
|
|
217
|
+
</ol>
|
|
218
|
+
<form
|
|
219
|
+
class="sv-frv-comment"
|
|
220
|
+
onsubmit={(e) => {
|
|
221
|
+
e.preventDefault()
|
|
222
|
+
send()
|
|
223
|
+
}}
|
|
224
|
+
>
|
|
225
|
+
<textarea
|
|
226
|
+
bind:this={commentBox}
|
|
227
|
+
rows="2"
|
|
228
|
+
placeholder="Leave a comment"
|
|
229
|
+
aria-label="Leave a comment"
|
|
230
|
+
bind:value={comment}
|
|
231
|
+
onkeydown={(e) => {
|
|
232
|
+
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
|
|
233
|
+
e.preventDefault()
|
|
234
|
+
send()
|
|
235
|
+
}
|
|
236
|
+
}}
|
|
237
|
+
></textarea>
|
|
238
|
+
<button type="submit" class="sv-frv-btn" disabled={!comment.trim()}>Comment</button>
|
|
239
|
+
</form>
|
|
240
|
+
</aside>
|
|
241
|
+
</div>
|
|
242
|
+
{/if}
|
|
243
|
+
{#snippet footer()}
|
|
244
|
+
<div class="sv-frv-footer">
|
|
245
|
+
<button type="button" class="sv-frv-btn" disabled={!canPrev} onclick={() => go(-1)} aria-label="Previous record">
|
|
246
|
+
<svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true"><path d="M6 15l6-6 6 6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /></svg>
|
|
247
|
+
</button>
|
|
248
|
+
<button type="button" class="sv-frv-btn" disabled={!canNext} onclick={() => go(1)} aria-label="Next record">
|
|
249
|
+
<svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true"><path d="M6 9l6 6 6-6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /></svg>
|
|
250
|
+
</button>
|
|
251
|
+
<span class="sv-frv-pos">{index >= 0 ? `${index + 1} of ${rows.length}` : `Not in view (${rows.length} records)`}</span>
|
|
252
|
+
<!-- The modal tells onClose only when it closes itself (X, Escape, backdrop). -->
|
|
253
|
+
<button
|
|
254
|
+
type="button"
|
|
255
|
+
class="sv-frv-btn is-primary"
|
|
256
|
+
onclick={() => {
|
|
257
|
+
open = false
|
|
258
|
+
onClose()
|
|
259
|
+
}}>Close</button
|
|
260
|
+
>
|
|
261
|
+
</div>
|
|
262
|
+
{/snippet}
|
|
263
|
+
</SvModal>
|
|
264
|
+
|
|
265
|
+
<style>
|
|
266
|
+
.sv-frv-layout { display: grid; grid-template-columns: minmax(0, 1fr) 280px; gap: 20px; align-items: start; }
|
|
267
|
+
.sv-frv-fields { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
|
|
268
|
+
.sv-frv-field { display: flex; flex-direction: column; gap: 6px; }
|
|
269
|
+
.sv-frv-label { display: flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 600; color: var(--sg-muted, #64748b); }
|
|
270
|
+
.sv-frv-ro { font-weight: 400; font-size: 11px; opacity: 0.8; }
|
|
271
|
+
.sv-frv-hidden-toggle {
|
|
272
|
+
align-self: flex-start; border: 0; padding: 2px 0; background: none; font: inherit; font-size: 13px;
|
|
273
|
+
color: var(--sg-accent, #2563eb); cursor: pointer;
|
|
274
|
+
}
|
|
275
|
+
.sv-frv-activity {
|
|
276
|
+
position: sticky; top: 0; display: flex; flex-direction: column; gap: 8px; max-height: 70vh;
|
|
277
|
+
padding-inline-start: 16px; border-inline-start: 1px solid var(--sg-border, #e2e8f0);
|
|
278
|
+
}
|
|
279
|
+
.sv-frv-activity-title { margin: 0; font-size: 13px; font-weight: 600; }
|
|
280
|
+
.sv-frv-feed { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; overflow: auto; min-height: 60px; }
|
|
281
|
+
.sv-frv-item { font-size: 12px; }
|
|
282
|
+
.sv-frv-item-head { color: var(--sg-muted, #64748b); }
|
|
283
|
+
.sv-frv-item-head strong { color: var(--sg-fg, #0f172a); font-weight: 600; }
|
|
284
|
+
.sv-frv-when { margin-inline-start: 6px; opacity: 0.8; }
|
|
285
|
+
.sv-frv-item-body { margin-top: 2px; white-space: pre-wrap; word-break: break-word; }
|
|
286
|
+
.sv-frv-item.is-comment .sv-frv-item-body {
|
|
287
|
+
padding: 6px 8px; border-radius: 8px; background: color-mix(in srgb, var(--sg-fg, #0f172a) 5%, transparent);
|
|
288
|
+
}
|
|
289
|
+
.sv-frv-none { color: var(--sg-muted, #64748b); font-size: 12px; }
|
|
290
|
+
.sv-frv-comment { display: grid; gap: 6px; }
|
|
291
|
+
.sv-frv-comment textarea {
|
|
292
|
+
width: 100%; box-sizing: border-box; padding: 6px 8px; font: inherit; font-size: 13px; resize: vertical;
|
|
293
|
+
color: var(--sg-fg, #0f172a); background: var(--sg-bg, #fff); border: 1px solid var(--sg-border, #e2e8f0); border-radius: 6px;
|
|
294
|
+
}
|
|
295
|
+
.sv-frv-comment .sv-frv-btn { justify-self: end; }
|
|
296
|
+
.sv-frv-gone { color: var(--sg-muted, #64748b); }
|
|
297
|
+
.sv-frv-footer { display: flex; align-items: center; gap: 6px; width: 100%; }
|
|
298
|
+
.sv-frv-pos { flex: 1; font-size: 13px; color: var(--sg-muted, #64748b); }
|
|
299
|
+
.sv-frv-btn {
|
|
300
|
+
display: inline-flex; align-items: center; justify-content: center; height: 32px; min-width: 32px; padding: 0 12px;
|
|
301
|
+
font: inherit; font-size: 13px; cursor: pointer; border: 1px solid var(--sg-border, #e2e8f0); border-radius: var(--sg-radius, 6px);
|
|
302
|
+
background: var(--sg-bg, #fff); color: var(--sg-fg, #0f172a);
|
|
303
|
+
}
|
|
304
|
+
.sv-frv-btn:disabled { opacity: 0.4; cursor: default; }
|
|
305
|
+
.sv-frv-btn.is-primary { background: var(--sg-fg, #0f172a); color: var(--sg-bg, #fff); border-color: var(--sg-fg, #0f172a); }
|
|
306
|
+
@media (max-width: 760px) {
|
|
307
|
+
.sv-frv-layout { grid-template-columns: minmax(0, 1fr); }
|
|
308
|
+
.sv-frv-activity { position: static; padding-inline-start: 0; border-inline-start: 0; border-top: 1px solid var(--sg-border, #e2e8f0); padding-top: 12px; }
|
|
309
|
+
}
|
|
310
|
+
</style>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
ctrl: any;
|
|
3
|
+
rowId: string;
|
|
4
|
+
editPrimary?: boolean;
|
|
5
|
+
/** 'comment': open with the comment box focused (also read from the open request). */
|
|
6
|
+
focus?: 'comment';
|
|
7
|
+
onClose: () => void;
|
|
8
|
+
};
|
|
9
|
+
declare const RecordView: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
10
|
+
type RecordView = ReturnType<typeof RecordView>;
|
|
11
|
+
export default RecordView;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/** Long-text cells with rich text on: the formatted lines, one after another, clipped to the row. */
|
|
3
|
+
import type { FieldCellProps } from './registry.svelte'
|
|
4
|
+
import { parseRichText } from './rich-text'
|
|
5
|
+
|
|
6
|
+
let { value }: FieldCellProps = $props()
|
|
7
|
+
const lines = $derived(parseRichText(value))
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<span class="sv-frx">
|
|
11
|
+
{#each lines as line, i (i)}
|
|
12
|
+
{#if i > 0}<span class="sv-frx-sep" aria-hidden="true"> </span>{/if}
|
|
13
|
+
{#if line.bullet}<span class="sv-frx-bullet" aria-hidden="true">•</span>{/if}
|
|
14
|
+
{#each line.parts as p, j (j)}
|
|
15
|
+
{#if p.kind === 'b'}<strong>{p.text}</strong>
|
|
16
|
+
{:else if p.kind === 'i'}<em>{p.text}</em>
|
|
17
|
+
{:else if p.kind === 'code'}<code>{p.text}</code>
|
|
18
|
+
{:else if p.kind === 'a'}<a href={p.href} target="_blank" rel="noopener noreferrer" onpointerdown={(e) => e.stopPropagation()}>{p.text}</a>
|
|
19
|
+
{:else}{p.text}{/if}
|
|
20
|
+
{/each}
|
|
21
|
+
{/each}
|
|
22
|
+
</span>
|
|
23
|
+
|
|
24
|
+
<style>
|
|
25
|
+
.sv-frx { display: block; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
26
|
+
.sv-frx code { font-size: 0.9em; padding: 0 3px; border-radius: 3px; background: var(--sg-header-bg, #f8fafc); }
|
|
27
|
+
.sv-frx a { color: inherit; text-decoration: underline; text-decoration-color: var(--sg-muted, #94a3b8); }
|
|
28
|
+
.sv-frx-bullet { margin-inline-end: 4px; color: var(--sg-muted, #64748b); }
|
|
29
|
+
.sv-frx-sep { margin-inline: 4px; }
|
|
30
|
+
</style>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Long-text cells with rich text on: the formatted lines, one after another, clipped to the row. */
|
|
2
|
+
import type { FieldCellProps } from './registry.svelte.js';
|
|
3
|
+
declare const RichTextCell: import("svelte").Component<FieldCellProps, {}, "">;
|
|
4
|
+
type RichTextCell = ReturnType<typeof RichTextCell>;
|
|
5
|
+
export default RichTextCell;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* The "+ Add row" line after the last body row (`rowAdder` prop). The grid
|
|
4
|
+
* renders it through registerGridExtension's `bodyEnd` slot, inside the
|
|
5
|
+
* body, so it scrolls with the rows and sits under the last one.
|
|
6
|
+
*
|
|
7
|
+
* A click appends a row (with `rowAdder.defaults()`), scrolls to it and
|
|
8
|
+
* starts editing its first editable cell, then fires `onRowAdd` so the app
|
|
9
|
+
* can persist the row.
|
|
10
|
+
*/
|
|
11
|
+
import { tick } from 'svelte'
|
|
12
|
+
import { newRowValues } from './field-ops'
|
|
13
|
+
|
|
14
|
+
type Config = { label?: string; defaults?: () => Record<string, unknown> }
|
|
15
|
+
let { ctrl, foot = false }: { ctrl: any; foot?: boolean } = $props()
|
|
16
|
+
|
|
17
|
+
// With a summary row the line goes under it (`foot`), so the sums sit right after the records.
|
|
18
|
+
const config = $derived<Config | null>(
|
|
19
|
+
ctrl.props.rowAdder && foot === !!ctrl.rowSummariesEnabled ? (typeof ctrl.props.rowAdder === 'object' ? ctrl.props.rowAdder : {}) : null,
|
|
20
|
+
)
|
|
21
|
+
const label = $derived(config?.label ?? 'Add row')
|
|
22
|
+
const span = $derived((ctrl.allColumns?.length ?? 0) + 3)
|
|
23
|
+
|
|
24
|
+
async function add() {
|
|
25
|
+
const api = ctrl.buildApi?.()
|
|
26
|
+
if (!api || !config) return
|
|
27
|
+
// Field defaults, the next autonumber, the created time; the prop's own defaults win.
|
|
28
|
+
const row: Record<string, unknown> = newRowValues(ctrl, config.defaults?.() ?? {})
|
|
29
|
+
api.addRow(row, 'bottom')
|
|
30
|
+
await tick()
|
|
31
|
+
// Sorting or grouping can put the new row anywhere; find it by identity.
|
|
32
|
+
const index = (ctrl.allRows as Array<{ original: unknown }>).findIndex((r) => r.original === row)
|
|
33
|
+
if (index >= 0) {
|
|
34
|
+
api.scrollToRow(index)
|
|
35
|
+
await tick()
|
|
36
|
+
for (const column of ctrl.allColumns as Array<{ id: string }>) {
|
|
37
|
+
if (api.startEditing(index, column.id)) break
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
ctrl.props.onRowAdd?.({ row })
|
|
41
|
+
}
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
{#if config}
|
|
45
|
+
<tr class="sv-grid-row sv-field-row-adder" class:is-foot={foot}>
|
|
46
|
+
<!-- overflow and flex inline, over the grid's cell rules: a clipping cell is its own
|
|
47
|
+
scroll box, and the sticky button would not stay at the left edge. -->
|
|
48
|
+
<td class="sv-grid-cell sv-field-row-adder-cell" colspan={span} style="overflow: visible; flex: 1 1 auto;">
|
|
49
|
+
<button type="button" class="sv-field-row-adder-button" onclick={add}>
|
|
50
|
+
<svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true">
|
|
51
|
+
<path d="M12 5v14M5 12h14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
|
|
52
|
+
</svg>
|
|
53
|
+
{label}
|
|
54
|
+
</button>
|
|
55
|
+
</td>
|
|
56
|
+
</tr>
|
|
57
|
+
{/if}
|
|
58
|
+
|
|
59
|
+
<style>
|
|
60
|
+
.sv-field-row-adder-cell {
|
|
61
|
+
flex: 1 1 auto;
|
|
62
|
+
padding: 0;
|
|
63
|
+
border-bottom: 1px solid var(--sg-border, #e2e8f0);
|
|
64
|
+
}
|
|
65
|
+
.sv-field-row-adder:hover > .sv-field-row-adder-cell { background: var(--sg-row-hover-bg, #f8fafc); }
|
|
66
|
+
/* The column headers' ground, as the summary row has: the grid's chrome, not
|
|
67
|
+
a record. In the footer the summary row draws the line between them. */
|
|
68
|
+
.sv-field-row-adder,
|
|
69
|
+
.sv-field-row-adder > .sv-field-row-adder-cell { background: var(--sg-summary-bg, var(--sg-header-bg, #f5f7fb)); }
|
|
70
|
+
.sv-field-row-adder.is-foot > .sv-field-row-adder-cell { border-bottom: 0; }
|
|
71
|
+
.sv-field-row-adder-button {
|
|
72
|
+
position: sticky;
|
|
73
|
+
inset-inline-start: 0;
|
|
74
|
+
display: inline-flex;
|
|
75
|
+
align-items: center;
|
|
76
|
+
gap: 6px;
|
|
77
|
+
height: 100%;
|
|
78
|
+
min-height: 34px;
|
|
79
|
+
padding: 0 14px;
|
|
80
|
+
border: 0;
|
|
81
|
+
background: transparent;
|
|
82
|
+
color: var(--sg-muted, #64748b);
|
|
83
|
+
font: inherit;
|
|
84
|
+
/* A secondary action: the size of the summary text, not of the records. */
|
|
85
|
+
font-size: 13px;
|
|
86
|
+
cursor: pointer;
|
|
87
|
+
}
|
|
88
|
+
.sv-field-row-adder-button:hover { color: var(--sg-fg, #0f172a); }
|
|
89
|
+
.sv-field-row-adder-button:focus-visible {
|
|
90
|
+
outline: 2px solid var(--sg-accent, #2563eb);
|
|
91
|
+
outline-offset: -2px;
|
|
92
|
+
}
|
|
93
|
+
</style>
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* The row-number cell with `expandRows`: the number, which on hover (or
|
|
4
|
+
* while the row is selected) turns into a checkbox that selects the row,
|
|
5
|
+
* and an expand button that opens the row as a record. A record with
|
|
6
|
+
* comments shows how many; a click opens it at its discussion.
|
|
7
|
+
*/
|
|
8
|
+
import { openRecord } from './editor-request.svelte'
|
|
9
|
+
import { commentsOf } from './activity.svelte'
|
|
10
|
+
|
|
11
|
+
let { ctrl, row, rowIndex }: { ctrl: any; row: { id: string; original: unknown }; rowIndex: number } = $props()
|
|
12
|
+
|
|
13
|
+
const selected = $derived(!!ctrl.isRowSelected?.(row.id, row.original))
|
|
14
|
+
const comments = $derived(commentsOf(ctrl, row.id).length)
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<span class="sv-frh" class:is-selected={selected} class:has-comments={comments > 0}>
|
|
18
|
+
<span class="sv-frh-num" aria-hidden="true">{rowIndex + 1}</span>
|
|
19
|
+
<button
|
|
20
|
+
type="button"
|
|
21
|
+
class="sv-frh-check"
|
|
22
|
+
role="checkbox"
|
|
23
|
+
aria-checked={selected}
|
|
24
|
+
aria-label={`Select row ${rowIndex + 1}`}
|
|
25
|
+
onclick={(e) => {
|
|
26
|
+
e.stopPropagation()
|
|
27
|
+
ctrl.toggleRowSelectionById(row.id, row.original)
|
|
28
|
+
}}
|
|
29
|
+
>
|
|
30
|
+
{#if selected}
|
|
31
|
+
<svg viewBox="0 0 24 24" width="12" height="12" aria-hidden="true"><path d="M5 12l5 5 9-10" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" /></svg>
|
|
32
|
+
{/if}
|
|
33
|
+
</button>
|
|
34
|
+
{#if comments}
|
|
35
|
+
<button
|
|
36
|
+
type="button"
|
|
37
|
+
class="sv-frh-comments"
|
|
38
|
+
aria-label={`${comments} ${comments === 1 ? 'comment' : 'comments'} on row ${rowIndex + 1}`}
|
|
39
|
+
title={`${comments} ${comments === 1 ? 'comment' : 'comments'}`}
|
|
40
|
+
onclick={(e) => {
|
|
41
|
+
e.stopPropagation()
|
|
42
|
+
openRecord({ ctrl, rowId: row.id, focus: 'comment' })
|
|
43
|
+
}}
|
|
44
|
+
>
|
|
45
|
+
<svg viewBox="0 0 16 16" width="11" height="11" aria-hidden="true"><path d="M2 3h12v8H8l-3 2v-2H2z" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round" /></svg>
|
|
46
|
+
{comments}
|
|
47
|
+
</button>
|
|
48
|
+
{/if}
|
|
49
|
+
<button
|
|
50
|
+
type="button"
|
|
51
|
+
class="sv-frh-expand"
|
|
52
|
+
aria-label={`Expand row ${rowIndex + 1}`}
|
|
53
|
+
title="Expand row"
|
|
54
|
+
onclick={(e) => {
|
|
55
|
+
e.stopPropagation()
|
|
56
|
+
openRecord({ ctrl, rowId: row.id })
|
|
57
|
+
}}
|
|
58
|
+
>
|
|
59
|
+
<svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true"><path d="M14 4h6v6M20 4l-7 7M10 20H4v-6M4 20l7-7" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /></svg>
|
|
60
|
+
</button>
|
|
61
|
+
</span>
|
|
62
|
+
|
|
63
|
+
<style>
|
|
64
|
+
.sv-frh { display: flex; align-items: center; gap: 4px; height: 100%; padding: 0 4px 0 8px; box-sizing: border-box; }
|
|
65
|
+
.sv-frh-num { flex: 1; min-width: 0; text-align: center; color: var(--sg-muted, #64748b); font-variant-numeric: tabular-nums; }
|
|
66
|
+
.sv-frh-check {
|
|
67
|
+
display: none; flex: none; align-items: center; justify-content: center; width: 16px; height: 16px; padding: 0;
|
|
68
|
+
border: 1.5px solid var(--sg-border, #cbd5e1); border-radius: 4px; background: var(--sg-bg, #fff); color: var(--sg-bg, #fff); cursor: pointer;
|
|
69
|
+
}
|
|
70
|
+
.sv-frh-check[aria-checked='true'] { border-color: var(--sg-accent, #2563eb); background: var(--sg-accent, #2563eb); }
|
|
71
|
+
.sv-frh-expand {
|
|
72
|
+
display: inline-flex; visibility: hidden; flex: none; align-items: center; justify-content: center; width: 20px; height: 20px; padding: 0;
|
|
73
|
+
margin-inline-start: auto; border: 0; border-radius: 4px; background: none; color: var(--sg-muted, #64748b); cursor: pointer;
|
|
74
|
+
}
|
|
75
|
+
.sv-frh-comments {
|
|
76
|
+
display: inline-flex; flex: none; align-items: center; gap: 2px; height: 18px; padding: 0 4px; border: 0; border-radius: 9px;
|
|
77
|
+
background: color-mix(in srgb, var(--sg-fg, #0f172a) 8%, transparent); color: var(--sg-fg, #0f172a);
|
|
78
|
+
font: inherit; font-size: 11px; font-variant-numeric: tabular-nums; cursor: pointer;
|
|
79
|
+
}
|
|
80
|
+
.sv-frh-comments:hover { background: color-mix(in srgb, var(--sg-fg, #0f172a) 16%, transparent); }
|
|
81
|
+
.sv-frh-expand:hover { background: var(--sg-row-hover-bg, #f1f5f9); color: var(--sg-fg, #0f172a); }
|
|
82
|
+
/* Hover, keyboard focus or a selected row: the checkbox takes the number's place. */
|
|
83
|
+
/* A record with comments keeps its count in the expand button's place: the
|
|
84
|
+
gutter has no room for both, and the count opens the same record, at its
|
|
85
|
+
discussion. */
|
|
86
|
+
.sv-frh.has-comments .sv-frh-expand { display: none; }
|
|
87
|
+
/* The checkbox and the count side by side: into the cell's start padding, gaps tightened. */
|
|
88
|
+
:global(.sv-grid-row:hover) .sv-frh.has-comments,
|
|
89
|
+
.sv-frh.has-comments:focus-within,
|
|
90
|
+
.sv-frh.has-comments.is-selected { margin-inline-start: -10px; padding-inline-start: 4px; gap: 2px; }
|
|
91
|
+
:global(.sv-grid-row:hover) .sv-frh-num,
|
|
92
|
+
.sv-frh:focus-within .sv-frh-num,
|
|
93
|
+
.sv-frh.is-selected .sv-frh-num { display: none; }
|
|
94
|
+
:global(.sv-grid-row:hover) .sv-frh-check,
|
|
95
|
+
.sv-frh:focus-within .sv-frh-check,
|
|
96
|
+
.sv-frh.is-selected .sv-frh-check { display: inline-flex; }
|
|
97
|
+
:global(.sv-grid-row:hover) .sv-frh-expand,
|
|
98
|
+
.sv-frh:focus-within .sv-frh-expand { visibility: visible; }
|
|
99
|
+
</style>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
ctrl: any;
|
|
3
|
+
row: {
|
|
4
|
+
id: string;
|
|
5
|
+
original: unknown;
|
|
6
|
+
};
|
|
7
|
+
rowIndex: number;
|
|
8
|
+
};
|
|
9
|
+
declare const RowHeader: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
10
|
+
type RowHeader = ReturnType<typeof RowHeader>;
|
|
11
|
+
export default RowHeader;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Single and multiple select cells: each choice as a pill filled with its
|
|
4
|
+
* option's color, the text dark or white for contrast. A value that is no
|
|
5
|
+
* longer one of the options keeps a plain gray pill, so it stays visible.
|
|
6
|
+
*/
|
|
7
|
+
import type { FieldCellProps } from './registry.svelte'
|
|
8
|
+
import { textOn } from './palette'
|
|
9
|
+
|
|
10
|
+
type Option = { value: unknown; label?: string; color?: string }
|
|
11
|
+
let { value, props }: FieldCellProps<{ options?: Option[] }> = $props()
|
|
12
|
+
|
|
13
|
+
const byValue = $derived(new Map((props.options ?? []).map((o) => [String(o.value), o])))
|
|
14
|
+
const items = $derived(
|
|
15
|
+
(Array.isArray(value) ? value : value == null || value === '' ? [] : [value]).map((v) => {
|
|
16
|
+
const o = byValue.get(String(v))
|
|
17
|
+
return { key: String(v), label: o?.label ?? String(v), color: o?.color }
|
|
18
|
+
}),
|
|
19
|
+
)
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<span class="sv-fsl">
|
|
23
|
+
{#each items as it (it.key)}
|
|
24
|
+
<span class="sv-fsl-pill" class:is-plain={!it.color} style:background={it.color} style:color={it.color ? textOn(it.color) : undefined}>{it.label}</span>
|
|
25
|
+
{/each}
|
|
26
|
+
</span>
|
|
27
|
+
|
|
28
|
+
<style>
|
|
29
|
+
.sv-fsl { display: flex; align-items: center; gap: 4px; min-width: 0; overflow: hidden; }
|
|
30
|
+
.sv-fsl-pill {
|
|
31
|
+
flex: none; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
32
|
+
padding: 0 8px; border-radius: 999px; font-size: 12px; line-height: 20px;
|
|
33
|
+
}
|
|
34
|
+
.sv-fsl-pill.is-plain { background: var(--sg-header-bg, #eef0f3); color: var(--sg-fg, #0f172a); border: 1px solid var(--sg-border, #e2e8f0); line-height: 18px; }
|
|
35
|
+
</style>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single and multiple select cells: each choice as a pill filled with its
|
|
3
|
+
* option's color, the text dark or white for contrast. A value that is no
|
|
4
|
+
* longer one of the options keeps a plain gray pill, so it stays visible.
|
|
5
|
+
*/
|
|
6
|
+
import type { FieldCellProps } from './registry.svelte.js';
|
|
7
|
+
declare const SelectCell: import("svelte").Component<FieldCellProps<{
|
|
8
|
+
options?: {
|
|
9
|
+
value: unknown;
|
|
10
|
+
label?: string;
|
|
11
|
+
color?: string;
|
|
12
|
+
}[];
|
|
13
|
+
}>, {}, "">;
|
|
14
|
+
type SelectCell = ReturnType<typeof SelectCell>;
|
|
15
|
+
export default SelectCell;
|