@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,295 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* SvBaseForm - a form that adds a record to the table: a title, a line
|
|
4
|
+
* under it, then each field with its name and the editor its cell uses
|
|
5
|
+
* (the select panel, the number box, the date picker, the file dialog).
|
|
6
|
+
* The fields start with the defaults a new record gets. Submit checks
|
|
7
|
+
* required fields, email / URL / phone formats and each column's
|
|
8
|
+
* `validate`, adds the row the way "+ Add row" does (field defaults, the
|
|
9
|
+
* next autonumber, `onRowAdd`), then offers to fill in another. A select
|
|
10
|
+
* offers only the field's options: a form never adds one.
|
|
11
|
+
*/
|
|
12
|
+
import { untrack } from 'svelte'
|
|
13
|
+
import { parseEditorValue, type CellEditorType } from '@svgrid/grid'
|
|
14
|
+
import { fieldColumns, gridOf, primaryIdOf, type FieldColumn } from './grid-access'
|
|
15
|
+
import { newRowValues } from './field-ops'
|
|
16
|
+
import { linkHref } from './links'
|
|
17
|
+
import FieldInput from './FieldInput.svelte'
|
|
18
|
+
import FieldIcon from './viewbar/FieldIcon.svelte'
|
|
19
|
+
|
|
20
|
+
let {
|
|
21
|
+
api,
|
|
22
|
+
fields,
|
|
23
|
+
required = [],
|
|
24
|
+
title = 'New record',
|
|
25
|
+
description = '',
|
|
26
|
+
submitLabel = 'Submit',
|
|
27
|
+
defaults,
|
|
28
|
+
onSubmit,
|
|
29
|
+
}: {
|
|
30
|
+
/** The grid's api. */
|
|
31
|
+
api: any
|
|
32
|
+
/** The fields asked for, by column id, in order. Default: every field a person can fill in. */
|
|
33
|
+
fields?: ReadonlyArray<string>
|
|
34
|
+
/** Fields that must be filled in before Submit. */
|
|
35
|
+
required?: ReadonlyArray<string>
|
|
36
|
+
title?: string
|
|
37
|
+
description?: string
|
|
38
|
+
submitLabel?: string
|
|
39
|
+
/** Values each record from this form starts with, under the row adder's `defaults` (`<SvBase>` gives an id). */
|
|
40
|
+
defaults?: () => Record<string, unknown>
|
|
41
|
+
/**
|
|
42
|
+
* After the row was added. Return a Promise to wait on it: the button is
|
|
43
|
+
* busy until it settles, and a rejection takes the row back out of the
|
|
44
|
+
* grid, keeps what was typed and shows the error's message.
|
|
45
|
+
*/
|
|
46
|
+
onSubmit?: (row: Record<string, unknown>) => void | Promise<unknown>
|
|
47
|
+
} = $props()
|
|
48
|
+
|
|
49
|
+
// Computed by the table, never typed in.
|
|
50
|
+
const COMPUTED = new Set(['formula', 'lookup', 'rollup', 'count', 'autoNumber', 'createdTime', 'lastModifiedTime', 'createdBy', 'lastModifiedBy', 'button'])
|
|
51
|
+
const ctrl = $derived(api ? gridOf(api) : undefined)
|
|
52
|
+
const columns = $derived.by<FieldColumn[]>(() => {
|
|
53
|
+
if (!ctrl) return []
|
|
54
|
+
const all = fieldColumns(ctrl).filter((c) => !COMPUTED.has(c.fieldType ?? '') && c.def.editable !== false && !c.def.fieldFn)
|
|
55
|
+
if (!fields) {
|
|
56
|
+
// The primary field first, as the record's name.
|
|
57
|
+
const p = primaryIdOf(ctrl)
|
|
58
|
+
return [...all.filter((c) => c.id === p), ...all.filter((c) => c.id !== p)]
|
|
59
|
+
}
|
|
60
|
+
return fields.map((id) => all.find((c) => c.id === id)).filter((c): c is FieldColumn => !!c)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
/** What an empty field says it wants. */
|
|
64
|
+
function placeholderFor(c: FieldColumn): string {
|
|
65
|
+
const t = c.fieldType ?? ''
|
|
66
|
+
if (t === 'singleSelect' || t === 'multiSelect' || t === 'user') return 'Choose'
|
|
67
|
+
if (t === 'date') return 'Pick a date'
|
|
68
|
+
if (t === 'attachment') return 'Add files'
|
|
69
|
+
if (t === 'link') return 'Link records'
|
|
70
|
+
return 'Type here'
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let draft = $state<Record<string, unknown>>({})
|
|
74
|
+
let editing = $state<string | null>(null)
|
|
75
|
+
// Column id -> what is wrong with its value, shown under the field.
|
|
76
|
+
let errors = $state<Record<string, string>>({})
|
|
77
|
+
let sent = $state(false)
|
|
78
|
+
let busy = $state(false)
|
|
79
|
+
let failure = $state('')
|
|
80
|
+
// The values the record starts with (row adder defaults, the form's own),
|
|
81
|
+
// read once per record so an id made there is the record's id.
|
|
82
|
+
let seed: Record<string, unknown> | null = null
|
|
83
|
+
// Field -> the default it was filled with.
|
|
84
|
+
let prefilled: Record<string, unknown> = {}
|
|
85
|
+
|
|
86
|
+
const isBlank = (v: unknown) => v == null || v === '' || (Array.isArray(v) && v.length === 0)
|
|
87
|
+
|
|
88
|
+
/** A fresh record: the defaults it would get, shown in the fields asked for. */
|
|
89
|
+
function prefill() {
|
|
90
|
+
if (!ctrl) return
|
|
91
|
+
const cfg = ctrl.props.rowAdder
|
|
92
|
+
const fromAdder = cfg && typeof cfg === 'object' && typeof cfg.defaults === 'function' ? cfg.defaults() : {}
|
|
93
|
+
seed = { ...(defaults?.() ?? {}), ...fromAdder }
|
|
94
|
+
const start = newRowValues(ctrl, seed)
|
|
95
|
+
const next: Record<string, unknown> = {}
|
|
96
|
+
for (const c of columns) {
|
|
97
|
+
const v = start[c.field]
|
|
98
|
+
if (v !== undefined && !isBlank(v)) next[c.field] = Array.isArray(v) ? v.slice() : v
|
|
99
|
+
}
|
|
100
|
+
prefilled = { ...next }
|
|
101
|
+
draft = next
|
|
102
|
+
}
|
|
103
|
+
// Once the grid is there; a remount (another form view) starts again.
|
|
104
|
+
$effect.pre(() => {
|
|
105
|
+
if (ctrl && !seed) untrack(prefill)
|
|
106
|
+
})
|
|
107
|
+
/** A date picked for a column that keeps dates as text keeps it as 'YYYY-MM-DD'. */
|
|
108
|
+
function stored(c: FieldColumn, v: unknown): unknown {
|
|
109
|
+
if (v instanceof Date && c.def.cellDataType === 'dateString') {
|
|
110
|
+
const p = (n: number) => String(n).padStart(2, '0')
|
|
111
|
+
return `${v.getFullYear()}-${p(v.getMonth() + 1)}-${p(v.getDate())}`
|
|
112
|
+
}
|
|
113
|
+
return v
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* The editor's value as the grid would store it from a cell edit: coerced
|
|
117
|
+
* for the editor type, then through the column's valueParser, so a custom
|
|
118
|
+
* field type parses the same in a form.
|
|
119
|
+
*/
|
|
120
|
+
function parsed(c: FieldColumn, raw: unknown): unknown {
|
|
121
|
+
const def = c.def as Record<string, any>
|
|
122
|
+
const value = parseEditorValue(String(def.editorType ?? 'text') as CellEditorType, stored(c, raw), {
|
|
123
|
+
multiple: def.editorMultiple === true,
|
|
124
|
+
dateOnly: def.cellDataType === 'dateString',
|
|
125
|
+
})
|
|
126
|
+
const parser = def.valueParser as ((p: Record<string, unknown>) => unknown) | undefined
|
|
127
|
+
return parser ? parser({ newValue: value, oldValue: draft[c.field], rawInput: raw, data: draft, columnId: c.id }) : value
|
|
128
|
+
}
|
|
129
|
+
function set(c: FieldColumn, v: unknown) {
|
|
130
|
+
draft = { ...draft, [c.field]: parsed(c, v) }
|
|
131
|
+
if (errors[c.id]) {
|
|
132
|
+
const rest = { ...errors }
|
|
133
|
+
delete rest[c.id]
|
|
134
|
+
errors = rest
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const FORMATS: Record<string, { kind: 'email' | 'url' | 'phone'; message: string }> = {
|
|
138
|
+
email: { kind: 'email', message: 'Not a valid email address' },
|
|
139
|
+
url: { kind: 'url', message: 'Not a valid web address' },
|
|
140
|
+
phone: { kind: 'phone', message: 'Not a valid phone number' },
|
|
141
|
+
}
|
|
142
|
+
/** What is wrong with a field's value, or '' when nothing is. */
|
|
143
|
+
function problem(c: FieldColumn, v: unknown): string {
|
|
144
|
+
const blank = isBlank(v)
|
|
145
|
+
if (blank && required.includes(c.id)) return `${c.header} is required`
|
|
146
|
+
// The format its cell links by: mailto, https, tel.
|
|
147
|
+
const format = FORMATS[c.fieldType ?? '']
|
|
148
|
+
if (!blank && format && !linkHref(format.kind, v)) return format.message
|
|
149
|
+
// The column's own rule, as the grid runs it on a cell.
|
|
150
|
+
const validate = c.def.validate as ((p: Record<string, unknown>) => unknown) | undefined
|
|
151
|
+
if (typeof validate === 'function') {
|
|
152
|
+
const verdict = validate({ value: v, row: draft, rowIndex: -1, column: { id: c.id, columnDef: c.def } })
|
|
153
|
+
if (verdict === false) return `${c.header} is not valid`
|
|
154
|
+
if (typeof verdict === 'string' && verdict) return verdict
|
|
155
|
+
}
|
|
156
|
+
return ''
|
|
157
|
+
}
|
|
158
|
+
function editBeside(c: FieldColumn, dir: 1 | -1) {
|
|
159
|
+
const at = columns.findIndex((x) => x.id === c.id)
|
|
160
|
+
const next = columns[at + dir]
|
|
161
|
+
editing = next && next.def.editorType !== 'checkbox' ? next.id : null
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* What the form sends: what the fields show. A field left empty is left
|
|
165
|
+
* out, so it gets no value; one whose default was cleared stays empty.
|
|
166
|
+
*/
|
|
167
|
+
function given(): Record<string, unknown> {
|
|
168
|
+
const out: Record<string, unknown> = {}
|
|
169
|
+
for (const [field, v] of Object.entries(draft)) {
|
|
170
|
+
if (!isBlank(v) || field in prefilled) out[field] = v
|
|
171
|
+
}
|
|
172
|
+
return out
|
|
173
|
+
}
|
|
174
|
+
async function submit(e: SubmitEvent) {
|
|
175
|
+
e.preventDefault()
|
|
176
|
+
if (!ctrl || busy) return
|
|
177
|
+
editing = null
|
|
178
|
+
failure = ''
|
|
179
|
+
const found: Record<string, string> = {}
|
|
180
|
+
for (const c of columns) {
|
|
181
|
+
const why = problem(c, draft[c.field])
|
|
182
|
+
if (why) found[c.id] = why
|
|
183
|
+
}
|
|
184
|
+
errors = found
|
|
185
|
+
if (Object.keys(found).length) return
|
|
186
|
+
if (!seed) prefill()
|
|
187
|
+
// Field defaults, the next autonumber, the created time; then what the fields show.
|
|
188
|
+
const row: Record<string, unknown> = newRowValues(ctrl, { ...seed, ...given() })
|
|
189
|
+
api.addRow(row, 'bottom')
|
|
190
|
+
ctrl.props.onRowAdd?.({ row })
|
|
191
|
+
let result: unknown
|
|
192
|
+
try {
|
|
193
|
+
result = onSubmit?.(row)
|
|
194
|
+
} catch (err) {
|
|
195
|
+
return fail(row, err)
|
|
196
|
+
}
|
|
197
|
+
if (result && typeof (result as Promise<unknown>).then === 'function') {
|
|
198
|
+
busy = true
|
|
199
|
+
try {
|
|
200
|
+
await result
|
|
201
|
+
} catch (err) {
|
|
202
|
+
return fail(row, err)
|
|
203
|
+
} finally {
|
|
204
|
+
busy = false
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
sent = true
|
|
208
|
+
}
|
|
209
|
+
/** onSubmit failed: the row comes out of the grid again, the fields keep their values. */
|
|
210
|
+
function fail(row: Record<string, unknown>, err: unknown) {
|
|
211
|
+
const at = ctrl ? ctrl.internalData.indexOf(row) : -1
|
|
212
|
+
if (at >= 0) api.removeRows([at])
|
|
213
|
+
failure = err instanceof Error && err.message ? err.message : 'The record could not be sent. Try again.'
|
|
214
|
+
}
|
|
215
|
+
function again() {
|
|
216
|
+
seed = null
|
|
217
|
+
errors = {}
|
|
218
|
+
failure = ''
|
|
219
|
+
prefill()
|
|
220
|
+
sent = false
|
|
221
|
+
}
|
|
222
|
+
</script>
|
|
223
|
+
|
|
224
|
+
{#if ctrl}
|
|
225
|
+
<div class="sv-fform-wrap">
|
|
226
|
+
{#if sent}
|
|
227
|
+
<div class="sv-fform sv-fform-done" role="status">
|
|
228
|
+
<h2 class="sv-fform-title">Thank you</h2>
|
|
229
|
+
<p class="sv-fform-desc">The record was added.</p>
|
|
230
|
+
<button type="button" class="sv-fform-submit" onclick={again}>Submit another</button>
|
|
231
|
+
</div>
|
|
232
|
+
{:else}
|
|
233
|
+
<form class="sv-fform" onsubmit={submit} novalidate>
|
|
234
|
+
<h2 class="sv-fform-title">{title}</h2>
|
|
235
|
+
{#if description}<p class="sv-fform-desc">{description}</p>{/if}
|
|
236
|
+
{#each columns as c (c.id)}
|
|
237
|
+
{@const v = draft[c.field]}
|
|
238
|
+
<div class="sv-fform-field" class:is-missing={!!errors[c.id]}>
|
|
239
|
+
<div class="sv-fform-label">
|
|
240
|
+
<FieldIcon column={c} />
|
|
241
|
+
<span>{c.header}</span>
|
|
242
|
+
{#if required.includes(c.id)}<span class="sv-fform-req" aria-label="required">*</span>{/if}
|
|
243
|
+
</div>
|
|
244
|
+
<FieldInput
|
|
245
|
+
{ctrl}
|
|
246
|
+
column={{ id: c.id, columnDef: c.def }}
|
|
247
|
+
value={v}
|
|
248
|
+
row={{ id: '', original: draft }}
|
|
249
|
+
label={c.header}
|
|
250
|
+
editable
|
|
251
|
+
editing={editing === c.id}
|
|
252
|
+
placeholder={placeholderFor(c)}
|
|
253
|
+
onStart={() => (editing = c.id)}
|
|
254
|
+
onStop={() => (editing = null)}
|
|
255
|
+
onCommit={(next) => set(c, next)}
|
|
256
|
+
onTab={(dir) => editBeside(c, dir)}
|
|
257
|
+
addOptions={false}
|
|
258
|
+
/>
|
|
259
|
+
{#if errors[c.id]}<p class="sv-fform-error">{errors[c.id]}</p>{/if}
|
|
260
|
+
{#if c.def.description}<p class="sv-fform-hint">{String(c.def.description)}</p>{/if}
|
|
261
|
+
</div>
|
|
262
|
+
{/each}
|
|
263
|
+
{#if failure}<p class="sv-fform-error sv-fform-failure" role="alert">{failure}</p>{/if}
|
|
264
|
+
<button type="submit" class="sv-fform-submit" disabled={busy} aria-busy={busy}>{submitLabel}</button>
|
|
265
|
+
</form>
|
|
266
|
+
{/if}
|
|
267
|
+
</div>
|
|
268
|
+
{/if}
|
|
269
|
+
|
|
270
|
+
<style>
|
|
271
|
+
.sv-fform-wrap {
|
|
272
|
+
height: 100%; overflow: auto; box-sizing: border-box; padding: 24px 16px;
|
|
273
|
+
background: color-mix(in srgb, var(--sg-fg, #0f172a) 4%, var(--sg-bg, #fff));
|
|
274
|
+
font-family: var(--sg-font, inherit); color: var(--sg-fg, #0f172a);
|
|
275
|
+
}
|
|
276
|
+
.sv-fform {
|
|
277
|
+
max-width: 640px; margin: 0 auto; padding: 24px 28px; display: flex; flex-direction: column; gap: 18px;
|
|
278
|
+
background: var(--sg-bg, #fff); border: 1px solid var(--sg-border, #e2e8f0); border-radius: 12px;
|
|
279
|
+
}
|
|
280
|
+
.sv-fform-title { margin: 0; font-size: 22px; }
|
|
281
|
+
.sv-fform-desc { margin: -10px 0 0; color: var(--sg-muted, #64748b); }
|
|
282
|
+
.sv-fform-field { display: flex; flex-direction: column; gap: 6px; }
|
|
283
|
+
.sv-fform-label { display: flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 600; }
|
|
284
|
+
.sv-fform-req { color: var(--sg-danger, #dc2626); }
|
|
285
|
+
.sv-fform-field.is-missing :global(.sv-frv-box) { border-color: var(--sg-danger, #dc2626); }
|
|
286
|
+
.sv-fform-error { margin: 0; font-size: 12px; color: var(--sg-danger, #dc2626); }
|
|
287
|
+
.sv-fform-hint { margin: 0; font-size: 12px; color: var(--sg-muted, #64748b); }
|
|
288
|
+
.sv-fform-submit {
|
|
289
|
+
align-self: flex-start; border: 0; border-radius: 8px; padding: 8px 18px; font: inherit; font-weight: 600; cursor: pointer;
|
|
290
|
+
background: var(--sg-accent, #2563eb); color: var(--sg-accent-fg, #fff);
|
|
291
|
+
}
|
|
292
|
+
.sv-fform-submit:disabled { opacity: 0.6; cursor: progress; }
|
|
293
|
+
.sv-fform-failure { font-size: 13px; }
|
|
294
|
+
.sv-fform-done { align-items: flex-start; }
|
|
295
|
+
</style>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
/** The grid's api. */
|
|
3
|
+
api: any;
|
|
4
|
+
/** The fields asked for, by column id, in order. Default: every field a person can fill in. */
|
|
5
|
+
fields?: ReadonlyArray<string>;
|
|
6
|
+
/** Fields that must be filled in before Submit. */
|
|
7
|
+
required?: ReadonlyArray<string>;
|
|
8
|
+
title?: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
submitLabel?: string;
|
|
11
|
+
/** Values each record from this form starts with, under the row adder's `defaults` (`<SvBase>` gives an id). */
|
|
12
|
+
defaults?: () => Record<string, unknown>;
|
|
13
|
+
/**
|
|
14
|
+
* After the row was added. Return a Promise to wait on it: the button is
|
|
15
|
+
* busy until it settles, and a rejection takes the row back out of the
|
|
16
|
+
* grid, keeps what was typed and shows the error's message.
|
|
17
|
+
*/
|
|
18
|
+
onSubmit?: (row: Record<string, unknown>) => void | Promise<unknown>;
|
|
19
|
+
};
|
|
20
|
+
declare const SvBaseForm: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
21
|
+
type SvBaseForm = ReturnType<typeof SvBaseForm>;
|
|
22
|
+
export default SvBaseForm;
|