@svgrid/enterprise 3.1.8 → 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 +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-Obk8sI77.js → index-BPU2KNc5.js} +307 -307
- 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 +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,172 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* The link field's editor (registered as 'fieldLink'): the linked table's
|
|
4
|
+
* rows in a dialog with a search box over the primary field and the
|
|
5
|
+
* table's text fields; tick the ones to link (one when the
|
|
6
|
+
* field allows a single record). Done commits the ids, Cancel leaves the
|
|
7
|
+
* cell as it was. When the table can add records (`create`), a search that
|
|
8
|
+
* names no record offers "+ Create" for it, picked at once.
|
|
9
|
+
*/
|
|
10
|
+
import { SvModal } from '@svgrid/grid'
|
|
11
|
+
import { fieldPropsOf } from './registry.svelte'
|
|
12
|
+
import { getLinkedTable, idOf, rowsOf } from './sources.svelte'
|
|
13
|
+
|
|
14
|
+
let {
|
|
15
|
+
value,
|
|
16
|
+
columnDef,
|
|
17
|
+
onCommit,
|
|
18
|
+
onCancel,
|
|
19
|
+
}: { value: unknown; columnDef?: Record<string, unknown>; onCommit: (v?: unknown) => void; onCancel: () => void } = $props()
|
|
20
|
+
|
|
21
|
+
const props = $derived(fieldPropsOf((columnDef ?? {}) as { fieldType?: unknown; fieldProps?: Record<string, unknown> }) as { table?: string; multiple?: boolean })
|
|
22
|
+
const table = $derived(getLinkedTable(props.table))
|
|
23
|
+
const multiple = $derived(props.multiple !== false)
|
|
24
|
+
// svelte-ignore state_referenced_locally
|
|
25
|
+
let picked = $state<string[]>(Array.isArray(value) ? value.map(String) : value == null || value === '' ? [] : [String(value)])
|
|
26
|
+
let search = $state('')
|
|
27
|
+
let open = $state(true)
|
|
28
|
+
let finished = false
|
|
29
|
+
|
|
30
|
+
// The list draws this many rows at most; the footer of the list says how many matched.
|
|
31
|
+
const LIMIT = 200
|
|
32
|
+
const TEXT_TYPES = new Set(['text', 'longText', 'email', 'url', 'phone', 'singleSelect', 'multiSelect'])
|
|
33
|
+
const rows = $derived(table ? rowsOf(table) : [])
|
|
34
|
+
// Besides the primary field, search reads the table's text fields (every
|
|
35
|
+
// text value of the record when the table declares no fields).
|
|
36
|
+
const others = $derived(
|
|
37
|
+
table?.fields?.length
|
|
38
|
+
? table.fields.filter((f) => f.field !== table.primaryField && (!f.fieldType || TEXT_TYPES.has(f.fieldType))).map((f) => ({ field: f.field, header: f.header }))
|
|
39
|
+
: null,
|
|
40
|
+
)
|
|
41
|
+
const textOf = (v: unknown): string => (typeof v === 'string' ? v : Array.isArray(v) ? v.filter((x) => typeof x === 'string').join(', ') : '')
|
|
42
|
+
/** Where `q` is found in a record other than its primary field, as "Email: maya@...". */
|
|
43
|
+
function hintFor(r: Record<string, unknown>, q: string): string | null {
|
|
44
|
+
const fields = others ?? Object.keys(r).filter((k) => k !== 'id' && k !== table!.primaryField).map((k) => ({ field: k, header: k }))
|
|
45
|
+
for (const f of fields) {
|
|
46
|
+
const text = textOf(r[f.field])
|
|
47
|
+
if (text && text.toLowerCase().includes(q)) return `${f.header}: ${text}`
|
|
48
|
+
}
|
|
49
|
+
return null
|
|
50
|
+
}
|
|
51
|
+
const found = $derived.by(() => {
|
|
52
|
+
const q = search.trim().toLowerCase()
|
|
53
|
+
const out: Array<{ id: string; label: string; hint: string | null }> = []
|
|
54
|
+
let total = 0
|
|
55
|
+
let exact = false
|
|
56
|
+
for (const r of rows) {
|
|
57
|
+
const label = String(r[table!.primaryField] ?? '')
|
|
58
|
+
let hint: string | null = null
|
|
59
|
+
if (q && !label.toLowerCase().includes(q)) {
|
|
60
|
+
hint = hintFor(r, q)
|
|
61
|
+
if (hint == null) continue
|
|
62
|
+
}
|
|
63
|
+
if (q && label.toLowerCase() === q) exact = true
|
|
64
|
+
total += 1
|
|
65
|
+
if (out.length < LIMIT) out.push({ id: idOf(table!, r), label, hint })
|
|
66
|
+
}
|
|
67
|
+
return { rows: out, total, exact }
|
|
68
|
+
})
|
|
69
|
+
const shown = $derived(found.rows)
|
|
70
|
+
|
|
71
|
+
const term = $derived(search.trim())
|
|
72
|
+
const canCreate = $derived(!!table?.create && !!term && !found.exact)
|
|
73
|
+
let creating = $state(false)
|
|
74
|
+
async function create() {
|
|
75
|
+
if (!table?.create || creating) return
|
|
76
|
+
creating = true
|
|
77
|
+
try {
|
|
78
|
+
const row = await table.create(term)
|
|
79
|
+
const id = idOf(table, row)
|
|
80
|
+
picked = multiple ? [...picked.filter((x) => x !== id), id] : [id]
|
|
81
|
+
search = ''
|
|
82
|
+
} finally {
|
|
83
|
+
creating = false
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function toggle(id: string) {
|
|
88
|
+
if (!multiple) picked = picked[0] === id ? [] : [id]
|
|
89
|
+
else picked = picked.includes(id) ? picked.filter((x) => x !== id) : [...picked, id]
|
|
90
|
+
}
|
|
91
|
+
function done() {
|
|
92
|
+
finished = true
|
|
93
|
+
open = false
|
|
94
|
+
onCommit([...picked])
|
|
95
|
+
}
|
|
96
|
+
function cancel() {
|
|
97
|
+
if (finished) return
|
|
98
|
+
finished = true
|
|
99
|
+
open = false
|
|
100
|
+
onCancel()
|
|
101
|
+
}
|
|
102
|
+
const inputValue = (e: Event) => (e.currentTarget as HTMLInputElement).value
|
|
103
|
+
</script>
|
|
104
|
+
|
|
105
|
+
<SvModal bind:open title={table ? `Link to ${table.name}` : 'Link to records'} width={460} onClose={cancel}>
|
|
106
|
+
{#if !table}
|
|
107
|
+
<p class="sv-flp-empty">The table this field links to is not available.</p>
|
|
108
|
+
{:else}
|
|
109
|
+
<input class="sv-flp-search" placeholder={`Search ${table.name}`} aria-label={`Search ${table.name}`} value={search} oninput={(e) => (search = inputValue(e))} />
|
|
110
|
+
<ul class="sv-flp-list" role="listbox" aria-multiselectable={multiple}>
|
|
111
|
+
{#each shown as r (r.id)}
|
|
112
|
+
<li>
|
|
113
|
+
<button type="button" class="sv-flp-row" class:is-on={picked.includes(r.id)} role="option" aria-selected={picked.includes(r.id)} onclick={() => toggle(r.id)}>
|
|
114
|
+
<span class="sv-flp-mark" aria-hidden="true">{picked.includes(r.id) ? '✓' : ''}</span>
|
|
115
|
+
<span class="sv-flp-label">{r.label || 'Unnamed'}</span>
|
|
116
|
+
{#if r.hint}<span class="sv-flp-hint">{r.hint}</span>{/if}
|
|
117
|
+
</button>
|
|
118
|
+
</li>
|
|
119
|
+
{:else}
|
|
120
|
+
{#if !canCreate}<li class="sv-flp-empty">No records match.</li>{/if}
|
|
121
|
+
{/each}
|
|
122
|
+
{#if canCreate}
|
|
123
|
+
<li>
|
|
124
|
+
<button type="button" class="sv-flp-row sv-flp-create" disabled={creating} onclick={create}>
|
|
125
|
+
<span class="sv-flp-mark" aria-hidden="true">+</span>
|
|
126
|
+
<span class="sv-flp-label">Create “{term}”</span>
|
|
127
|
+
</button>
|
|
128
|
+
</li>
|
|
129
|
+
{/if}
|
|
130
|
+
</ul>
|
|
131
|
+
{#if found.total > shown.length}
|
|
132
|
+
<p class="sv-flp-more" role="status">Showing {shown.length.toLocaleString()} of {found.total.toLocaleString()}. Type to narrow.</p>
|
|
133
|
+
{/if}
|
|
134
|
+
{/if}
|
|
135
|
+
{#snippet footer()}
|
|
136
|
+
<div class="sv-flp-footer">
|
|
137
|
+
<span class="sv-flp-count">{picked.length} linked</span>
|
|
138
|
+
<button type="button" class="sv-flp-btn" onclick={cancel}>Cancel</button>
|
|
139
|
+
<button type="button" class="sv-flp-btn is-primary" onclick={done}>Done</button>
|
|
140
|
+
</div>
|
|
141
|
+
{/snippet}
|
|
142
|
+
</SvModal>
|
|
143
|
+
|
|
144
|
+
<style>
|
|
145
|
+
.sv-flp-search {
|
|
146
|
+
width: 100%; box-sizing: border-box; height: 34px; padding: 0 10px; font: inherit; font-size: 14px;
|
|
147
|
+
border: 1px solid var(--sg-input-border, var(--sg-border, #e2e8f0)); border-radius: var(--sg-radius, 6px);
|
|
148
|
+
background: var(--sg-input-bg, var(--sg-bg, #fff)); color: var(--sg-fg, #0f172a);
|
|
149
|
+
}
|
|
150
|
+
.sv-flp-search:focus { outline: none; border-color: var(--sg-accent, #2563eb); }
|
|
151
|
+
.sv-flp-list { list-style: none; margin: 10px 0 0; padding: 0; max-height: 320px; overflow: auto; }
|
|
152
|
+
.sv-flp-row {
|
|
153
|
+
display: flex; align-items: center; gap: 8px; width: 100%; height: 34px; padding: 0 8px; border: 0;
|
|
154
|
+
border-radius: var(--sg-radius, 6px); background: none; color: var(--sg-fg, #0f172a); font: inherit; font-size: 14px; text-align: start; cursor: pointer;
|
|
155
|
+
}
|
|
156
|
+
.sv-flp-row:hover { background: var(--sg-row-hover-bg, #f1f5f9); }
|
|
157
|
+
.sv-flp-row.is-on { background: var(--sg-selection-bg, #eef4ff); }
|
|
158
|
+
.sv-flp-create { color: var(--sg-accent, #2563eb); }
|
|
159
|
+
.sv-flp-mark { width: 16px; color: var(--sg-accent, #2563eb); font-weight: 700; }
|
|
160
|
+
.sv-flp-label { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
161
|
+
.sv-flp-hint { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; color: var(--sg-muted, #64748b); }
|
|
162
|
+
.sv-flp-more { margin: 8px 0 0; padding: 0 8px; font-size: 12px; color: var(--sg-muted, #64748b); }
|
|
163
|
+
.sv-flp-empty { padding: 12px 8px; color: var(--sg-muted, #64748b); font-size: 13px; }
|
|
164
|
+
.sv-flp-footer { display: flex; align-items: center; gap: 8px; width: 100%; }
|
|
165
|
+
.sv-flp-count { flex: 1; font-size: 13px; color: var(--sg-muted, #64748b); }
|
|
166
|
+
.sv-flp-btn {
|
|
167
|
+
height: 34px; padding: 0 16px; font: inherit; font-size: 13px; cursor: pointer;
|
|
168
|
+
border: 1px solid var(--sg-border, #e2e8f0); border-radius: var(--sg-radius, 6px);
|
|
169
|
+
background: var(--sg-bg, #fff); color: var(--sg-fg, #0f172a);
|
|
170
|
+
}
|
|
171
|
+
.sv-flp-btn.is-primary { background: var(--sg-fg, #0f172a); color: var(--sg-bg, #fff); border-color: var(--sg-fg, #0f172a); }
|
|
172
|
+
</style>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
value: unknown;
|
|
3
|
+
columnDef?: Record<string, unknown>;
|
|
4
|
+
onCommit: (v?: unknown) => void;
|
|
5
|
+
onCancel: () => void;
|
|
6
|
+
};
|
|
7
|
+
declare const LinkPickerEditor: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
8
|
+
type LinkPickerEditor = ReturnType<typeof LinkPickerEditor>;
|
|
9
|
+
export default LinkPickerEditor;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* The card a linked record shows while the pointer rests on its chip: the
|
|
4
|
+
* record's name, its table and its first few fields. One per grid; the chip
|
|
5
|
+
* says where it is (showLinkPreview).
|
|
6
|
+
*/
|
|
7
|
+
import { portalToBody } from '@svgrid/grid'
|
|
8
|
+
import { showLinkPreview, type LinkPreviewRequest } from './editor-request.svelte'
|
|
9
|
+
import { getLinkedTable } from './sources.svelte'
|
|
10
|
+
import { linkedFields, linkedRow, linkedText, linkedValue } from './linked-fields'
|
|
11
|
+
|
|
12
|
+
let { request }: { request: LinkPreviewRequest } = $props()
|
|
13
|
+
|
|
14
|
+
const MAX_FIELDS = 4
|
|
15
|
+
const table = $derived(getLinkedTable(request.table))
|
|
16
|
+
const row = $derived(table ? linkedRow(table, request.rowId) : undefined)
|
|
17
|
+
// The first four fields that have a value: an empty one does not use up a place.
|
|
18
|
+
const fields = $derived.by(() => {
|
|
19
|
+
if (!table || !row) return []
|
|
20
|
+
const out: Array<{ id: string; header: string; text: string }> = []
|
|
21
|
+
for (const f of linkedFields(table, row)) {
|
|
22
|
+
if (f.id === table.primaryField) continue
|
|
23
|
+
const text = linkedText(f, linkedValue(f, row), row)
|
|
24
|
+
if (text) out.push({ id: f.id, header: f.header, text })
|
|
25
|
+
if (out.length === MAX_FIELDS) break
|
|
26
|
+
}
|
|
27
|
+
return out
|
|
28
|
+
})
|
|
29
|
+
// Below the chip, kept inside the window.
|
|
30
|
+
const left = $derived(Math.max(8, Math.min(request.x, (typeof window !== 'undefined' ? window.innerWidth : 1200) - 268)))
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
{#if table && row}
|
|
34
|
+
<div
|
|
35
|
+
class="sv-flpv"
|
|
36
|
+
use:portalToBody
|
|
37
|
+
style:left={`${left}px`}
|
|
38
|
+
style:top={`${request.y + 6}px`}
|
|
39
|
+
role="tooltip"
|
|
40
|
+
onpointerleave={() => showLinkPreview(null)}
|
|
41
|
+
>
|
|
42
|
+
<div class="sv-flpv-title">{String(row[table.primaryField] ?? '') || 'Unnamed'}</div>
|
|
43
|
+
<div class="sv-flpv-table">{table.name}</div>
|
|
44
|
+
{#each fields as f (f.id)}
|
|
45
|
+
<div class="sv-flpv-field"><span class="sv-flpv-label">{f.header}</span>{f.text}</div>
|
|
46
|
+
{/each}
|
|
47
|
+
</div>
|
|
48
|
+
{/if}
|
|
49
|
+
|
|
50
|
+
<style>
|
|
51
|
+
.sv-flpv {
|
|
52
|
+
position: fixed; z-index: 2147483645; width: 260px; box-sizing: border-box; padding: 10px 12px;
|
|
53
|
+
background: var(--sg-bg, #fff); color: var(--sg-fg, #0f172a); font-size: 12px; line-height: 1.5;
|
|
54
|
+
border: 1px solid var(--sg-border, #e2e8f0); border-radius: 10px; box-shadow: 0 12px 32px -12px rgba(15, 23, 42, 0.4);
|
|
55
|
+
}
|
|
56
|
+
.sv-flpv-title { font-size: 14px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
57
|
+
.sv-flpv-table { color: var(--sg-muted, #64748b); margin-bottom: 6px; }
|
|
58
|
+
.sv-flpv-field { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
59
|
+
.sv-flpv-label { color: var(--sg-muted, #64748b); margin-inline-end: 6px; }
|
|
60
|
+
</style>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type LinkPreviewRequest } from './editor-request.svelte.js';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
request: LinkPreviewRequest;
|
|
4
|
+
};
|
|
5
|
+
declare const LinkPreview: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
6
|
+
type LinkPreview = ReturnType<typeof LinkPreview>;
|
|
7
|
+
export default LinkPreview;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* A record of a linked table, opened from a link chip: its fields with
|
|
4
|
+
* their names and values, drawn as their cells draw them. The table belongs
|
|
5
|
+
* to the app, so the record is read here, not edited; a table that can show
|
|
6
|
+
* its records (another tab of a base, `open`) offers "Open in {table}".
|
|
7
|
+
*/
|
|
8
|
+
import { SvModal } from '@svgrid/grid'
|
|
9
|
+
import FieldCell from './FieldCell.svelte'
|
|
10
|
+
import { getLinkedTable } from './sources.svelte'
|
|
11
|
+
import { closeAllLinkedRecords } from './editor-request.svelte'
|
|
12
|
+
import { linkedFields, linkedRow, linkedText, linkedValue } from './linked-fields'
|
|
13
|
+
import { getFieldType, typeDrawsCell } from './registry.svelte'
|
|
14
|
+
|
|
15
|
+
let { ctrl, table: tableId, rowId, onClose }: { ctrl: any; table: string; rowId: string; onClose: () => void } = $props()
|
|
16
|
+
|
|
17
|
+
let open = $state(true)
|
|
18
|
+
const table = $derived(getLinkedTable(tableId))
|
|
19
|
+
const row = $derived(table ? linkedRow(table, rowId) : undefined)
|
|
20
|
+
const fields = $derived(table ? linkedFields(table, row) : [])
|
|
21
|
+
const title = $derived(table && row ? String(row[table.primaryField] ?? '') || 'Unnamed' : 'Record')
|
|
22
|
+
const draws = (def: Record<string, any>) => !!def.fieldType && def.cell == null && typeDrawsCell(def)
|
|
23
|
+
/** Show the record in its own table, and close every linked record over this grid. */
|
|
24
|
+
function openWhere() {
|
|
25
|
+
const go = table?.open
|
|
26
|
+
open = false
|
|
27
|
+
closeAllLinkedRecords()
|
|
28
|
+
go?.(rowId)
|
|
29
|
+
}
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<SvModal bind:open {title} width={520} {onClose}>
|
|
33
|
+
{#if !table || !row}
|
|
34
|
+
<p class="sv-flrv-gone">This record is not in its table any more.</p>
|
|
35
|
+
{:else}
|
|
36
|
+
<p class="sv-flrv-table">{table.name}</p>
|
|
37
|
+
<div class="sv-flrv-fields">
|
|
38
|
+
{#each fields as f (f.id)}
|
|
39
|
+
{@const v = linkedValue(f, row)}
|
|
40
|
+
<div class="sv-flrv-field">
|
|
41
|
+
<div class="sv-flrv-label">
|
|
42
|
+
{#if f.def.fieldType && getFieldType(f.def.fieldType)?.icon}
|
|
43
|
+
<svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true"><path d={getFieldType(f.def.fieldType)?.icon} fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" /></svg>
|
|
44
|
+
{/if}
|
|
45
|
+
{f.header}
|
|
46
|
+
</div>
|
|
47
|
+
<div class="sv-flrv-value">
|
|
48
|
+
{#if draws(f.def)}
|
|
49
|
+
<FieldCell {ctrl} row={{ id: rowId, original: row }} column={{ id: f.id, columnDef: f.def }} value={v} />
|
|
50
|
+
{:else}
|
|
51
|
+
{linkedText(f, v, row) || ' '}
|
|
52
|
+
{/if}
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
{/each}
|
|
56
|
+
</div>
|
|
57
|
+
{/if}
|
|
58
|
+
{#snippet footer()}
|
|
59
|
+
<div class="sv-flrv-footer">
|
|
60
|
+
{#if table?.open && row}
|
|
61
|
+
<button
|
|
62
|
+
type="button"
|
|
63
|
+
class="sv-flrv-btn sv-flrv-btn-quiet"
|
|
64
|
+
onclick={openWhere}>Open in {table.name}</button
|
|
65
|
+
>
|
|
66
|
+
{/if}
|
|
67
|
+
<button
|
|
68
|
+
type="button"
|
|
69
|
+
class="sv-flrv-btn"
|
|
70
|
+
onclick={() => {
|
|
71
|
+
open = false
|
|
72
|
+
onClose()
|
|
73
|
+
}}>Close</button
|
|
74
|
+
>
|
|
75
|
+
</div>
|
|
76
|
+
{/snippet}
|
|
77
|
+
</SvModal>
|
|
78
|
+
|
|
79
|
+
<style>
|
|
80
|
+
.sv-flrv-table { margin: -6px 0 12px; font-size: 12px; color: var(--sg-muted, #64748b); }
|
|
81
|
+
.sv-flrv-fields { display: flex; flex-direction: column; gap: 12px; }
|
|
82
|
+
.sv-flrv-label { display: flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 600; color: var(--sg-muted, #64748b); margin-bottom: 4px; }
|
|
83
|
+
.sv-flrv-value {
|
|
84
|
+
display: flex; align-items: center; min-height: 34px; padding: 0 10px; box-sizing: border-box; font-size: 14px;
|
|
85
|
+
border: 1px solid var(--sg-border, #e2e8f0); border-radius: var(--sg-radius, 6px); background: var(--sg-header-bg, #f8fafc);
|
|
86
|
+
}
|
|
87
|
+
.sv-flrv-gone { color: var(--sg-muted, #64748b); }
|
|
88
|
+
.sv-flrv-footer { display: flex; justify-content: flex-end; gap: 8px; width: 100%; }
|
|
89
|
+
.sv-flrv-btn {
|
|
90
|
+
height: 32px; padding: 0 14px; font: inherit; font-size: 13px; cursor: pointer; border-radius: var(--sg-radius, 6px);
|
|
91
|
+
background: var(--sg-fg, #0f172a); color: var(--sg-bg, #fff); border: 1px solid var(--sg-fg, #0f172a);
|
|
92
|
+
}
|
|
93
|
+
.sv-flrv-btn-quiet { background: transparent; color: var(--sg-fg, #0f172a); border-color: var(--sg-border, #e2e8f0); }
|
|
94
|
+
</style>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
ctrl: any;
|
|
3
|
+
table: string;
|
|
4
|
+
rowId: string;
|
|
5
|
+
onClose: () => void;
|
|
6
|
+
};
|
|
7
|
+
declare const LinkedRecordView: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
8
|
+
type LinkedRecordView = ReturnType<typeof LinkedRecordView>;
|
|
9
|
+
export default LinkedRecordView;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Link-to-records cells: one chip per linked row, showing its primary
|
|
4
|
+
* field. A click on a chip opens that record; resting the pointer on one
|
|
5
|
+
* previews it.
|
|
6
|
+
*/
|
|
7
|
+
import type { FieldCellProps } from './registry.svelte'
|
|
8
|
+
import { getLinkedTable, idOf, linkedRows } from './sources.svelte'
|
|
9
|
+
import { openLinkedRecord, showLinkPreview } from './editor-request.svelte'
|
|
10
|
+
|
|
11
|
+
let { ctrl, value, props }: FieldCellProps<{ table?: string }> = $props()
|
|
12
|
+
const table = $derived(getLinkedTable(props.table))
|
|
13
|
+
const chips = $derived(
|
|
14
|
+
linkedRows(table, value).map((r) => ({ id: table ? idOf(table, r) : '', name: String(r[table?.primaryField ?? 'name'] ?? '') })),
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
let timer: ReturnType<typeof setTimeout> | undefined
|
|
18
|
+
function enter(e: PointerEvent, id: string) {
|
|
19
|
+
if (!table || e.pointerType === 'touch') return
|
|
20
|
+
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect()
|
|
21
|
+
clearTimeout(timer)
|
|
22
|
+
timer = setTimeout(() => showLinkPreview({ ctrl, table: table.id, rowId: id, x: rect.left, y: rect.bottom }), 350)
|
|
23
|
+
}
|
|
24
|
+
function leave(e: PointerEvent) {
|
|
25
|
+
clearTimeout(timer)
|
|
26
|
+
// Onto the card itself: it closes when the pointer leaves it.
|
|
27
|
+
const to = e.relatedTarget as Element | null
|
|
28
|
+
if (!to?.closest?.('.sv-flpv')) showLinkPreview(null)
|
|
29
|
+
}
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<span class="sv-flr">
|
|
33
|
+
{#each chips as chip, i (i)}
|
|
34
|
+
<button
|
|
35
|
+
type="button"
|
|
36
|
+
class="sv-flr-chip"
|
|
37
|
+
tabindex="-1"
|
|
38
|
+
onpointerdown={(e) => e.stopPropagation()}
|
|
39
|
+
onpointerenter={(e) => enter(e, chip.id)}
|
|
40
|
+
onpointerleave={leave}
|
|
41
|
+
onclick={(e) => {
|
|
42
|
+
e.stopPropagation()
|
|
43
|
+
clearTimeout(timer)
|
|
44
|
+
if (table) openLinkedRecord({ ctrl, table: table.id, rowId: chip.id })
|
|
45
|
+
}}>{chip.name || 'Unnamed'}</button
|
|
46
|
+
>
|
|
47
|
+
{/each}
|
|
48
|
+
</span>
|
|
49
|
+
|
|
50
|
+
<style>
|
|
51
|
+
.sv-flr { display: inline-flex; gap: 4px; min-width: 0; overflow: hidden; }
|
|
52
|
+
.sv-flr-chip {
|
|
53
|
+
flex: none; max-width: 160px; padding: 1px 8px; border-radius: 4px; font: inherit; font-size: 12px; color: inherit; cursor: pointer;
|
|
54
|
+
background: color-mix(in srgb, #2d7ff9 12%, transparent);
|
|
55
|
+
border: 1px solid color-mix(in srgb, #2d7ff9 30%, transparent);
|
|
56
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
57
|
+
}
|
|
58
|
+
.sv-flr-chip:hover { background: color-mix(in srgb, #2d7ff9 22%, transparent); }
|
|
59
|
+
</style>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Link-to-records cells: one chip per linked row, showing its primary
|
|
3
|
+
* field. A click on a chip opens that record; resting the pointer on one
|
|
4
|
+
* previews it.
|
|
5
|
+
*/
|
|
6
|
+
import type { FieldCellProps } from './registry.svelte.js';
|
|
7
|
+
declare const LinkedRecordsCell: import("svelte").Component<FieldCellProps<{
|
|
8
|
+
table?: string;
|
|
9
|
+
}>, {}, "">;
|
|
10
|
+
type LinkedRecordsCell = ReturnType<typeof LinkedRecordsCell>;
|
|
11
|
+
export default LinkedRecordsCell;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* The long text field's editor (registered as 'fieldLongText'): the cell
|
|
4
|
+
* grows into a panel over itself with room for paragraphs. Enter starts a
|
|
5
|
+
* new line, as in any document; Ctrl+Enter (Cmd+Enter), Tab or a click
|
|
6
|
+
* outside saves, and Escape leaves the cell as it was. With rich text on,
|
|
7
|
+
* the footer names the markup the cell draws.
|
|
8
|
+
*/
|
|
9
|
+
import { portalToBody } from '@svgrid/grid'
|
|
10
|
+
import { onMount } from 'svelte'
|
|
11
|
+
import { fieldPropsOf } from './registry.svelte'
|
|
12
|
+
|
|
13
|
+
let {
|
|
14
|
+
value,
|
|
15
|
+
typed = false,
|
|
16
|
+
columnDef,
|
|
17
|
+
onCommit,
|
|
18
|
+
onCancel,
|
|
19
|
+
onCommitAndMove,
|
|
20
|
+
}: {
|
|
21
|
+
value: unknown
|
|
22
|
+
typed?: boolean
|
|
23
|
+
columnDef?: Record<string, unknown>
|
|
24
|
+
onCommit: (v?: unknown) => void
|
|
25
|
+
onCancel: () => void
|
|
26
|
+
onCommitAndMove?: (v: unknown, direction: 1 | -1) => void
|
|
27
|
+
} = $props()
|
|
28
|
+
|
|
29
|
+
const rich = $derived(!!fieldPropsOf((columnDef ?? {}) as { fieldType?: unknown; fieldProps?: Record<string, unknown> }).richText)
|
|
30
|
+
// svelte-ignore state_referenced_locally
|
|
31
|
+
let text = $state(value == null ? '' : String(value))
|
|
32
|
+
let finished = false
|
|
33
|
+
|
|
34
|
+
let anchor = $state<HTMLElement | null>(null)
|
|
35
|
+
let panel = $state<HTMLElement | null>(null)
|
|
36
|
+
let area = $state<HTMLTextAreaElement | null>(null)
|
|
37
|
+
let rect = $state<{ top: number; left: number; width: number; maxHeight: number } | null>(null)
|
|
38
|
+
function place() {
|
|
39
|
+
const cell = anchor?.closest('td') ?? anchor
|
|
40
|
+
if (!cell) return
|
|
41
|
+
const r = cell.getBoundingClientRect()
|
|
42
|
+
const width = Math.min(Math.max(r.width + 2, 340), window.innerWidth - 16)
|
|
43
|
+
const left = Math.max(8, Math.min(r.left - 1, window.innerWidth - width - 8))
|
|
44
|
+
const top = Math.max(8, Math.min(r.top - 1, window.innerHeight - 220))
|
|
45
|
+
rect = { top, left, width, maxHeight: window.innerHeight - top - 8 }
|
|
46
|
+
}
|
|
47
|
+
/** The box grows with its lines, up to the panel's room. */
|
|
48
|
+
function fit() {
|
|
49
|
+
if (!area) return
|
|
50
|
+
area.style.height = 'auto'
|
|
51
|
+
area.style.height = `${Math.min(area.scrollHeight + 2, Math.max(120, (rect?.maxHeight ?? 400) - 48))}px`
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
onMount(() => {
|
|
55
|
+
place()
|
|
56
|
+
const takeFocus = () => {
|
|
57
|
+
if (!area || document.activeElement === area) return
|
|
58
|
+
area.focus({ preventScroll: true })
|
|
59
|
+
// A typed key continues after itself; opening an existing text puts
|
|
60
|
+
// the caret at its end, where more usually goes.
|
|
61
|
+
area.setSelectionRange(text.length, text.length)
|
|
62
|
+
fit()
|
|
63
|
+
}
|
|
64
|
+
queueMicrotask(takeFocus)
|
|
65
|
+
const frame = requestAnimationFrame(takeFocus)
|
|
66
|
+
const onDown = (e: PointerEvent) => {
|
|
67
|
+
const t = e.target as Node
|
|
68
|
+
if (finished || panel?.contains(t) || anchor?.contains(t)) return
|
|
69
|
+
finish('commit')
|
|
70
|
+
}
|
|
71
|
+
const onScroll = (e: Event) => {
|
|
72
|
+
if (!panel?.contains(e.target as Node)) place()
|
|
73
|
+
}
|
|
74
|
+
window.addEventListener('resize', place)
|
|
75
|
+
window.addEventListener('scroll', onScroll, true)
|
|
76
|
+
document.addEventListener('pointerdown', onDown, true)
|
|
77
|
+
return () => {
|
|
78
|
+
cancelAnimationFrame(frame)
|
|
79
|
+
window.removeEventListener('resize', place)
|
|
80
|
+
window.removeEventListener('scroll', onScroll, true)
|
|
81
|
+
document.removeEventListener('pointerdown', onDown, true)
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
function finish(how: 'commit' | 'cancel' | 'next' | 'prev') {
|
|
86
|
+
if (finished) return
|
|
87
|
+
finished = true
|
|
88
|
+
if (how === 'cancel') return onCancel()
|
|
89
|
+
const v = text === '' ? null : text
|
|
90
|
+
if ((how === 'next' || how === 'prev') && onCommitAndMove) onCommitAndMove(v, how === 'next' ? 1 : -1)
|
|
91
|
+
else onCommit(v)
|
|
92
|
+
}
|
|
93
|
+
function onKey(e: KeyboardEvent) {
|
|
94
|
+
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) finish('commit')
|
|
95
|
+
else if (e.key === 'Escape') finish('cancel')
|
|
96
|
+
else if (e.key === 'Tab') finish(e.shiftKey ? 'prev' : 'next')
|
|
97
|
+
else {
|
|
98
|
+
// Enter, arrows and the rest are the text box's own.
|
|
99
|
+
e.stopPropagation()
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
e.preventDefault()
|
|
103
|
+
e.stopPropagation()
|
|
104
|
+
}
|
|
105
|
+
const areaValue = (e: Event) => (e.currentTarget as HTMLTextAreaElement).value
|
|
106
|
+
const mod = typeof navigator !== 'undefined' && /Mac|iPhone|iPad/.test(navigator.platform) ? 'Cmd' : 'Ctrl'
|
|
107
|
+
</script>
|
|
108
|
+
|
|
109
|
+
<span class="sv-flt-cell" bind:this={anchor}>{text.split('\n')[0]}</span>
|
|
110
|
+
|
|
111
|
+
{#if rect}
|
|
112
|
+
<div
|
|
113
|
+
class="sv-flt-panel"
|
|
114
|
+
bind:this={panel}
|
|
115
|
+
use:portalToBody
|
|
116
|
+
style:top={`${rect.top}px`}
|
|
117
|
+
style:left={`${rect.left}px`}
|
|
118
|
+
style:width={`${rect.width}px`}
|
|
119
|
+
style:max-height={`${rect.maxHeight}px`}
|
|
120
|
+
>
|
|
121
|
+
<textarea
|
|
122
|
+
bind:this={area}
|
|
123
|
+
class="sv-flt-area"
|
|
124
|
+
aria-label={String((columnDef as { header?: unknown } | undefined)?.header ?? 'Long text')}
|
|
125
|
+
value={text}
|
|
126
|
+
oninput={(e) => {
|
|
127
|
+
text = areaValue(e)
|
|
128
|
+
fit()
|
|
129
|
+
}}
|
|
130
|
+
onkeydown={onKey}
|
|
131
|
+
></textarea>
|
|
132
|
+
<div class="sv-flt-foot">
|
|
133
|
+
{#if rich}<span class="sv-flt-markup">**bold** *italic* `code` [link](https://...) - list</span>{/if}
|
|
134
|
+
<span class="sv-flt-keys"><kbd>{mod}+Enter</kbd> save <kbd>Esc</kbd> cancel</span>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
{/if}
|
|
138
|
+
|
|
139
|
+
<style>
|
|
140
|
+
.sv-flt-cell { display: block; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; padding: 0 8px; }
|
|
141
|
+
.sv-flt-panel {
|
|
142
|
+
position: fixed; z-index: 2147483646; display: flex; flex-direction: column; gap: 6px; box-sizing: border-box; padding: 6px;
|
|
143
|
+
border: 1px solid var(--sg-border, #e2e8f0); border-radius: var(--sg-radius, 6px);
|
|
144
|
+
background: var(--sg-bg, #fff); color: var(--sg-fg, #0f172a); box-shadow: 0 8px 24px rgb(0 0 0 / 0.16);
|
|
145
|
+
}
|
|
146
|
+
.sv-flt-area {
|
|
147
|
+
display: block; box-sizing: border-box; width: 100%; min-height: 120px; resize: none; overflow: auto;
|
|
148
|
+
padding: 8px 10px; font: inherit; font-size: 14px; line-height: 1.5;
|
|
149
|
+
border: 1px solid var(--sg-input-border, var(--sg-border, #e2e8f0)); border-radius: var(--sg-radius, 6px);
|
|
150
|
+
background: var(--sg-input-bg, var(--sg-bg, #fff)); color: var(--sg-fg, #0f172a);
|
|
151
|
+
}
|
|
152
|
+
.sv-flt-area:focus { outline: none; border-color: var(--sg-accent, #2563eb); }
|
|
153
|
+
.sv-flt-foot { display: flex; align-items: center; gap: 12px; padding: 0 2px; font-size: 12px; color: var(--sg-muted, #64748b); }
|
|
154
|
+
.sv-flt-markup { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: ui-monospace, monospace; }
|
|
155
|
+
.sv-flt-keys { margin-inline-start: auto; flex: none; }
|
|
156
|
+
.sv-flt-keys kbd { padding: 0 4px; border: 1px solid var(--sg-border, #e2e8f0); border-radius: 3px; font: inherit; font-size: 11px; }
|
|
157
|
+
</style>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
value: unknown;
|
|
3
|
+
typed?: boolean;
|
|
4
|
+
columnDef?: Record<string, unknown>;
|
|
5
|
+
onCommit: (v?: unknown) => void;
|
|
6
|
+
onCancel: () => void;
|
|
7
|
+
onCommitAndMove?: (v: unknown, direction: 1 | -1) => void;
|
|
8
|
+
};
|
|
9
|
+
declare const LongTextEditor: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
10
|
+
type LongTextEditor = ReturnType<typeof LongTextEditor>;
|
|
11
|
+
export default LongTextEditor;
|