@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,185 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* One table of a base: the view bar over a `<SvGrid>`, and the form view
|
|
4
|
+
* beside it. Grid, Kanban, calendar, Gantt and gallery views are the grid
|
|
5
|
+
* itself in another mode (`board`, `scheduler`, `gantt`, `gallery`); a form
|
|
6
|
+
* view asks for the same fields. Every prop it does not name goes to the
|
|
7
|
+
* grid. `<SvBase>` draws one, under its table tabs when it has several.
|
|
8
|
+
*
|
|
9
|
+
* With a `runner`, the view bar has the table's Automations; an
|
|
10
|
+
* automation's "Color the record" action paints the grid while a record
|
|
11
|
+
* matches its condition.
|
|
12
|
+
*/
|
|
13
|
+
import { SvGrid, hasGanttView } from '@svgrid/grid'
|
|
14
|
+
import SvBaseViewBar from './SvBaseViewBar.svelte'
|
|
15
|
+
import SvBaseForm from './SvBaseForm.svelte'
|
|
16
|
+
import BaseChartView from './BaseChartView.svelte'
|
|
17
|
+
import BaseAutomations from './BaseAutomations.svelte'
|
|
18
|
+
import { gridOf } from './grid-access'
|
|
19
|
+
import { fieldCopyText } from './copy-text'
|
|
20
|
+
import { recordMenu } from './record-menu'
|
|
21
|
+
import { matches, type AutomationRun, type AutomationRunner, type BaseAutomation } from './automations.svelte'
|
|
22
|
+
import type { BaseTable } from './base-tables'
|
|
23
|
+
import type { FieldView, FieldViewType } from './view-model'
|
|
24
|
+
|
|
25
|
+
let {
|
|
26
|
+
data,
|
|
27
|
+
columns,
|
|
28
|
+
views = $bindable<FieldView[]>([{ id: 'grid', name: 'Grid view', type: 'grid' }]),
|
|
29
|
+
activeView = $bindable<string>(''),
|
|
30
|
+
onViewsChange,
|
|
31
|
+
viewTypes,
|
|
32
|
+
rowHeights,
|
|
33
|
+
containerHeight = 560,
|
|
34
|
+
onApiReady,
|
|
35
|
+
bare = false,
|
|
36
|
+
table,
|
|
37
|
+
tables,
|
|
38
|
+
runner,
|
|
39
|
+
serverRuns,
|
|
40
|
+
onAutomationsChange,
|
|
41
|
+
onFormSubmit,
|
|
42
|
+
newRecord,
|
|
43
|
+
...grid
|
|
44
|
+
}: {
|
|
45
|
+
/** The grid's rows and columns. */
|
|
46
|
+
data: any[]
|
|
47
|
+
columns: any[]
|
|
48
|
+
views?: FieldView[]
|
|
49
|
+
activeView?: string
|
|
50
|
+
onViewsChange?: (views: FieldView[]) => void
|
|
51
|
+
viewTypes?: ReadonlyArray<FieldViewType>
|
|
52
|
+
/** The row heights the view bar's row height menu offers. */
|
|
53
|
+
rowHeights?: ReadonlyArray<{ label: string; height: number }>
|
|
54
|
+
containerHeight?: number | string
|
|
55
|
+
onApiReady?: (api: any) => void
|
|
56
|
+
/** No border of its own: it sits under the table tabs, inside their frame. */
|
|
57
|
+
bare?: boolean
|
|
58
|
+
/** The table shown, with its automations (from `<SvBase tables>`). */
|
|
59
|
+
table?: BaseTable
|
|
60
|
+
/** Every table of the base, for the automations that create records. */
|
|
61
|
+
tables?: () => BaseTable[]
|
|
62
|
+
/** Runs the base's automations; without it there is no Automations button. */
|
|
63
|
+
runner?: AutomationRunner
|
|
64
|
+
serverRuns?: () => Promise<ReadonlyArray<AutomationRun>>
|
|
65
|
+
onAutomationsChange?: (automations: BaseAutomation[]) => void
|
|
66
|
+
/** A form view sent a record. A Promise keeps the form busy until it settles; a rejection shows its message. */
|
|
67
|
+
onFormSubmit?: (row: Record<string, unknown>, viewId: string) => void | Promise<unknown>
|
|
68
|
+
/** Values a record from a form view starts with (`<SvBase tables>` gives an id). */
|
|
69
|
+
newRecord?: () => Record<string, unknown>
|
|
70
|
+
[prop: string]: any
|
|
71
|
+
} = $props()
|
|
72
|
+
|
|
73
|
+
// Raw: a proxied api is another object, and the view bar finds the grid by the api itself.
|
|
74
|
+
let api = $state.raw<any>(null)
|
|
75
|
+
// Right-click on a record: expand, insert, duplicate, copy, paste, delete. The app's own `contextMenu` wins.
|
|
76
|
+
const menu = recordMenu(() => api, () => (api ? gridOf(api) : undefined))
|
|
77
|
+
const view = $derived(views.find((v) => v.id === activeView) ?? views[0])
|
|
78
|
+
const type = $derived(view?.type ?? 'grid')
|
|
79
|
+
// A form or a chart draws over the grid, which stays mounted: it holds the
|
|
80
|
+
// records, the filter and the api they read.
|
|
81
|
+
const besideGrid = $derived(type === 'form' || type === 'chart')
|
|
82
|
+
// Gantt only once the app has turned it on.
|
|
83
|
+
const types = $derived(viewTypes ?? (['grid', 'kanban', 'calendar', ...(hasGanttView() ? ['gantt'] : []), 'gallery', 'chart', 'form'] as FieldViewType[]))
|
|
84
|
+
const height = $derived(typeof containerHeight === 'number' ? `${containerHeight}px` : containerHeight)
|
|
85
|
+
|
|
86
|
+
// ---- Automations ----------------------------------------------------------
|
|
87
|
+
let automationsOpen = $state(false)
|
|
88
|
+
const automationList = $derived(table?.automations ?? [])
|
|
89
|
+
// "Color the record": a format per action, drawn while the record matches.
|
|
90
|
+
const highlightFormats = $derived.by(() => {
|
|
91
|
+
const t = table
|
|
92
|
+
if (!t) return []
|
|
93
|
+
const out: any[] = []
|
|
94
|
+
for (const a of automationList) {
|
|
95
|
+
if (!a.enabled || !a.condition) continue
|
|
96
|
+
for (const action of a.actions) {
|
|
97
|
+
if (action.type !== 'highlight') continue
|
|
98
|
+
out.push({
|
|
99
|
+
type: 'rule',
|
|
100
|
+
...(action.fields?.length ? { columns: action.fields } : {}),
|
|
101
|
+
when: ({ row }: { row: Record<string, unknown> }) => matches(t, a.condition, row),
|
|
102
|
+
background: action.background,
|
|
103
|
+
...(action.color ? { color: action.color } : {}),
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return out
|
|
108
|
+
})
|
|
109
|
+
const conditionalFormats = $derived(highlightFormats.length ? [...((grid.conditionalFormats as any[]) ?? []), ...highlightFormats] : grid.conditionalFormats)
|
|
110
|
+
</script>
|
|
111
|
+
|
|
112
|
+
<div class="sv-fbase" class:is-bare={bare} style:height>
|
|
113
|
+
{#if api}
|
|
114
|
+
<SvBaseViewBar
|
|
115
|
+
{api}
|
|
116
|
+
bind:views
|
|
117
|
+
bind:activeView
|
|
118
|
+
{onViewsChange}
|
|
119
|
+
viewTypes={types}
|
|
120
|
+
{rowHeights}
|
|
121
|
+
automations={runner && table ? { count: automationList.length, onManage: () => (automationsOpen = true) } : null}
|
|
122
|
+
/>
|
|
123
|
+
{/if}
|
|
124
|
+
<div class="sv-fbase-body">
|
|
125
|
+
<!-- The grid stays mounted under a form: it holds the api the form adds rows through. -->
|
|
126
|
+
<div class="sv-fbase-grid" class:is-hidden={besideGrid}>
|
|
127
|
+
<SvGrid
|
|
128
|
+
{...grid}
|
|
129
|
+
{data}
|
|
130
|
+
{columns}
|
|
131
|
+
{conditionalFormats}
|
|
132
|
+
contextMenu={grid.contextMenu ?? menu}
|
|
133
|
+
processCellForClipboard={grid.processCellForClipboard ?? fieldCopyText}
|
|
134
|
+
containerHeight="100%"
|
|
135
|
+
onApiReady={(a: any) => {
|
|
136
|
+
api = a
|
|
137
|
+
onApiReady?.(a)
|
|
138
|
+
}}
|
|
139
|
+
/>
|
|
140
|
+
</div>
|
|
141
|
+
{#if api && type === 'chart'}
|
|
142
|
+
<BaseChartView {api} setting={view?.chart} />
|
|
143
|
+
{/if}
|
|
144
|
+
{#if api && type === 'form'}
|
|
145
|
+
<!-- One form per view: another form view starts with its own empty fields. -->
|
|
146
|
+
{#key view?.id}
|
|
147
|
+
<SvBaseForm
|
|
148
|
+
{api}
|
|
149
|
+
fields={view?.formFields}
|
|
150
|
+
required={view?.formRequired}
|
|
151
|
+
title={view?.formTitle ?? view?.name}
|
|
152
|
+
description={view?.formDescription}
|
|
153
|
+
submitLabel={view?.formSubmitLabel}
|
|
154
|
+
defaults={newRecord}
|
|
155
|
+
onSubmit={(row) => (view ? onFormSubmit?.(row, view.id) : undefined)}
|
|
156
|
+
/>
|
|
157
|
+
{/key}
|
|
158
|
+
{/if}
|
|
159
|
+
</div>
|
|
160
|
+
{#if runner && table && api && automationsOpen}
|
|
161
|
+
<BaseAutomations
|
|
162
|
+
bind:open={automationsOpen}
|
|
163
|
+
{table}
|
|
164
|
+
tables={tables?.() ?? [table]}
|
|
165
|
+
ctrl={gridOf(api)}
|
|
166
|
+
{views}
|
|
167
|
+
{runner}
|
|
168
|
+
{serverRuns}
|
|
169
|
+
onChange={(list) => onAutomationsChange?.(list)}
|
|
170
|
+
/>
|
|
171
|
+
{/if}
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
<style>
|
|
175
|
+
.sv-fbase {
|
|
176
|
+
display: flex; flex-direction: column; min-height: 0; box-sizing: border-box;
|
|
177
|
+
border: 1px solid var(--sg-border, #e2e8f0); border-radius: var(--sg-radius-lg, 10px); overflow: hidden;
|
|
178
|
+
background: var(--sg-bg, #fff);
|
|
179
|
+
}
|
|
180
|
+
.sv-fbase.is-bare { border: 0; border-radius: 0; }
|
|
181
|
+
.sv-fbase-body { position: relative; flex: 1 1 auto; min-height: 0; }
|
|
182
|
+
.sv-fbase-grid { height: 100%; }
|
|
183
|
+
.sv-fbase-grid.is-hidden { visibility: hidden; position: absolute; inset: 0; pointer-events: none; }
|
|
184
|
+
.sv-fbase-body > :global(.sv-fform-wrap) { position: absolute; inset: 0; }
|
|
185
|
+
</style>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type AutomationRun, type AutomationRunner, type BaseAutomation } from './automations.svelte.js';
|
|
2
|
+
import type { BaseTable } from './base-tables.js';
|
|
3
|
+
import type { FieldView, FieldViewType } from './view-model.js';
|
|
4
|
+
type $$ComponentProps = {
|
|
5
|
+
/** The grid's rows and columns. */
|
|
6
|
+
data: any[];
|
|
7
|
+
columns: any[];
|
|
8
|
+
views?: FieldView[];
|
|
9
|
+
activeView?: string;
|
|
10
|
+
onViewsChange?: (views: FieldView[]) => void;
|
|
11
|
+
viewTypes?: ReadonlyArray<FieldViewType>;
|
|
12
|
+
/** The row heights the view bar's row height menu offers. */
|
|
13
|
+
rowHeights?: ReadonlyArray<{
|
|
14
|
+
label: string;
|
|
15
|
+
height: number;
|
|
16
|
+
}>;
|
|
17
|
+
containerHeight?: number | string;
|
|
18
|
+
onApiReady?: (api: any) => void;
|
|
19
|
+
/** No border of its own: it sits under the table tabs, inside their frame. */
|
|
20
|
+
bare?: boolean;
|
|
21
|
+
/** The table shown, with its automations (from `<SvBase tables>`). */
|
|
22
|
+
table?: BaseTable;
|
|
23
|
+
/** Every table of the base, for the automations that create records. */
|
|
24
|
+
tables?: () => BaseTable[];
|
|
25
|
+
/** Runs the base's automations; without it there is no Automations button. */
|
|
26
|
+
runner?: AutomationRunner;
|
|
27
|
+
serverRuns?: () => Promise<ReadonlyArray<AutomationRun>>;
|
|
28
|
+
onAutomationsChange?: (automations: BaseAutomation[]) => void;
|
|
29
|
+
/** A form view sent a record. A Promise keeps the form busy until it settles; a rejection shows its message. */
|
|
30
|
+
onFormSubmit?: (row: Record<string, unknown>, viewId: string) => void | Promise<unknown>;
|
|
31
|
+
/** Values a record from a form view starts with (`<SvBase tables>` gives an id). */
|
|
32
|
+
newRecord?: () => Record<string, unknown>;
|
|
33
|
+
[prop: string]: any;
|
|
34
|
+
};
|
|
35
|
+
declare const BaseTableView: import("svelte").Component<$$ComponentProps, {}, "views" | "activeView">;
|
|
36
|
+
type BaseTableView = ReturnType<typeof BaseTableView>;
|
|
37
|
+
export default BaseTableView;
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* The tables of a base as tabs: click one to show it, double-click to
|
|
4
|
+
* rename it, and the active tab's menu renames, duplicates or deletes it.
|
|
5
|
+
* "+ Add table" makes a new one. Deleting asks first and says which link
|
|
6
|
+
* fields of other tables go with it, and the lookups, rollups and counts
|
|
7
|
+
* that read them.
|
|
8
|
+
*/
|
|
9
|
+
import { tick } from 'svelte'
|
|
10
|
+
import { SvModal, SvPopover } from '@svgrid/grid'
|
|
11
|
+
import { linkReaders, type BaseTable } from './base-tables'
|
|
12
|
+
|
|
13
|
+
let {
|
|
14
|
+
tables,
|
|
15
|
+
activeId,
|
|
16
|
+
onSwitch,
|
|
17
|
+
onAdd,
|
|
18
|
+
onRename,
|
|
19
|
+
onDuplicate,
|
|
20
|
+
onDelete,
|
|
21
|
+
onExportSql,
|
|
22
|
+
status = null,
|
|
23
|
+
}: {
|
|
24
|
+
tables: ReadonlyArray<BaseTable>
|
|
25
|
+
activeId: string
|
|
26
|
+
onSwitch: (id: string) => void
|
|
27
|
+
onAdd: () => void
|
|
28
|
+
onRename: (id: string, name: string) => void
|
|
29
|
+
onDuplicate: (id: string) => void
|
|
30
|
+
onDelete: (id: string) => void
|
|
31
|
+
/** Download the base as Postgres SQL (from the list of tables). */
|
|
32
|
+
onExportSql?: () => void
|
|
33
|
+
/** A line at the end of the bar: loading, saving, a failed save. */
|
|
34
|
+
status?: { text: string; tone?: 'muted' | 'error'; title?: string } | null
|
|
35
|
+
} = $props()
|
|
36
|
+
|
|
37
|
+
let menuOpen = $state(false)
|
|
38
|
+
let listOpen = $state(false)
|
|
39
|
+
let renaming = $state<string | null>(null)
|
|
40
|
+
let draft = $state('')
|
|
41
|
+
let deleting = $state<BaseTable | null>(null)
|
|
42
|
+
let confirmOpen = $state(false)
|
|
43
|
+
let strip = $state<HTMLElement | null>(null)
|
|
44
|
+
|
|
45
|
+
const active = $derived(tables.find((t) => t.id === activeId))
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The fields of other tables that link to `t`, as "Deals: Account", each
|
|
49
|
+
* followed by the fields that read it, as "Deals: Industry (reads Account)".
|
|
50
|
+
*/
|
|
51
|
+
function linksTo(t: BaseTable): string[] {
|
|
52
|
+
const out: string[] = []
|
|
53
|
+
for (const o of tables) {
|
|
54
|
+
if (o.id === t.id) continue
|
|
55
|
+
for (const d of o.columns) {
|
|
56
|
+
if (d.fieldType !== 'link' || d.fieldProps?.table !== t.id) continue
|
|
57
|
+
const name = String(d.header ?? d.field)
|
|
58
|
+
out.push(`${o.name}: ${name}`)
|
|
59
|
+
for (const r of linkReaders(o, d)) out.push(`${o.name}: ${String(r.header ?? r.field)} (reads ${name})`)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return out
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function startRename(id: string) {
|
|
66
|
+
menuOpen = false
|
|
67
|
+
renaming = id
|
|
68
|
+
draft = tables.find((t) => t.id === id)?.name ?? ''
|
|
69
|
+
await tick()
|
|
70
|
+
const input = strip?.querySelector<HTMLInputElement>('.sv-btabs-input')
|
|
71
|
+
input?.focus()
|
|
72
|
+
input?.select()
|
|
73
|
+
}
|
|
74
|
+
function commitRename() {
|
|
75
|
+
const id = renaming
|
|
76
|
+
renaming = null
|
|
77
|
+
const name = draft.trim()
|
|
78
|
+
if (id && name && name !== tables.find((t) => t.id === id)?.name) onRename(id, name)
|
|
79
|
+
}
|
|
80
|
+
function renameKey(e: KeyboardEvent) {
|
|
81
|
+
if (e.key === 'Enter') commitRename()
|
|
82
|
+
else if (e.key === 'Escape') renaming = null
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function askDelete() {
|
|
86
|
+
menuOpen = false
|
|
87
|
+
deleting = active ?? null
|
|
88
|
+
confirmOpen = !!deleting
|
|
89
|
+
}
|
|
90
|
+
function confirmDelete() {
|
|
91
|
+
const t = deleting
|
|
92
|
+
confirmOpen = false
|
|
93
|
+
deleting = null
|
|
94
|
+
if (t) onDelete(t.id)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Arrow keys walk the tabs, Home and End jump, as a tab list does. */
|
|
98
|
+
function tabKey(e: KeyboardEvent, i: number) {
|
|
99
|
+
let to = -1
|
|
100
|
+
if (e.key === 'ArrowRight') to = (i + 1) % tables.length
|
|
101
|
+
else if (e.key === 'ArrowLeft') to = (i - 1 + tables.length) % tables.length
|
|
102
|
+
else if (e.key === 'Home') to = 0
|
|
103
|
+
else if (e.key === 'End') to = tables.length - 1
|
|
104
|
+
else if (e.key === 'F2') {
|
|
105
|
+
e.preventDefault()
|
|
106
|
+
void startRename(tables[i]!.id)
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
if (to < 0) return
|
|
110
|
+
e.preventDefault()
|
|
111
|
+
onSwitch(tables[to]!.id)
|
|
112
|
+
void tick().then(() => strip?.querySelectorAll<HTMLElement>('[role="tab"]')[to]?.focus())
|
|
113
|
+
}
|
|
114
|
+
const inputValue = (e: Event) => (e.currentTarget as HTMLInputElement).value
|
|
115
|
+
</script>
|
|
116
|
+
|
|
117
|
+
<div class="sv-btabs" bind:this={strip}>
|
|
118
|
+
<div class="sv-btabs-list" role="tablist" aria-label="Tables">
|
|
119
|
+
{#each tables as t, i (t.id)}
|
|
120
|
+
{@const on = t.id === activeId}
|
|
121
|
+
<div class="sv-btabs-tab" class:is-active={on}>
|
|
122
|
+
{#if renaming === t.id}
|
|
123
|
+
<input
|
|
124
|
+
class="sv-btabs-input"
|
|
125
|
+
aria-label="Table name"
|
|
126
|
+
value={draft}
|
|
127
|
+
oninput={(e) => (draft = inputValue(e))}
|
|
128
|
+
onkeydown={renameKey}
|
|
129
|
+
onblur={commitRename}
|
|
130
|
+
/>
|
|
131
|
+
{:else}
|
|
132
|
+
<button
|
|
133
|
+
type="button"
|
|
134
|
+
role="tab"
|
|
135
|
+
class="sv-btabs-name"
|
|
136
|
+
aria-selected={on}
|
|
137
|
+
tabindex={on ? 0 : -1}
|
|
138
|
+
title={t.name}
|
|
139
|
+
onclick={() => onSwitch(t.id)}
|
|
140
|
+
ondblclick={() => startRename(t.id)}
|
|
141
|
+
onkeydown={(e) => tabKey(e, i)}>{t.name}</button
|
|
142
|
+
>
|
|
143
|
+
{#if on}
|
|
144
|
+
<SvPopover bind:open={menuOpen} arrow={false} placement="bottom-start" ariaLabel="Table actions">
|
|
145
|
+
{#snippet anchor()}
|
|
146
|
+
<button type="button" class="sv-btabs-more" aria-label={`${t.name} table options`} aria-haspopup="menu" aria-expanded={menuOpen}>
|
|
147
|
+
<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" /></svg>
|
|
148
|
+
</button>
|
|
149
|
+
{/snippet}
|
|
150
|
+
<div class="sv-btabs-menu" role="menu">
|
|
151
|
+
<button type="button" role="menuitem" class="sv-btabs-item" onclick={() => startRename(t.id)}>Rename table</button>
|
|
152
|
+
<button type="button" role="menuitem" class="sv-btabs-item" onclick={() => { menuOpen = false; onDuplicate(t.id) }}>Duplicate table</button>
|
|
153
|
+
<button type="button" role="menuitem" class="sv-btabs-item is-danger" disabled={tables.length < 2} title={tables.length < 2 ? 'A base keeps at least one table' : undefined} onclick={askDelete}>Delete table</button>
|
|
154
|
+
</div>
|
|
155
|
+
</SvPopover>
|
|
156
|
+
{/if}
|
|
157
|
+
{/if}
|
|
158
|
+
</div>
|
|
159
|
+
{/each}
|
|
160
|
+
</div>
|
|
161
|
+
<div class="sv-btabs-tools">
|
|
162
|
+
<!-- Every table in a list: the way to one whose tab has scrolled out of sight. -->
|
|
163
|
+
<SvPopover bind:open={listOpen} arrow={false} placement="bottom-start" ariaLabel="All tables">
|
|
164
|
+
{#snippet anchor()}
|
|
165
|
+
<button type="button" class="sv-btabs-tool sv-btabs-icon" aria-label="All tables" title="All tables" aria-haspopup="menu" aria-expanded={listOpen}>
|
|
166
|
+
<svg viewBox="0 0 24 24" width="15" height="15" aria-hidden="true"><path d="M6 9l6 6 6-6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" /></svg>
|
|
167
|
+
</button>
|
|
168
|
+
{/snippet}
|
|
169
|
+
<div class="sv-btabs-menu" role="menu">
|
|
170
|
+
{#each tables as t (t.id)}
|
|
171
|
+
<button type="button" role="menuitem" class="sv-btabs-item" class:is-current={t.id === activeId} onclick={() => { listOpen = false; onSwitch(t.id) }}>
|
|
172
|
+
<span class="sv-btabs-check" aria-hidden="true">{t.id === activeId ? '✓' : ''}</span>{t.name}
|
|
173
|
+
</button>
|
|
174
|
+
{/each}
|
|
175
|
+
{#if onExportSql}
|
|
176
|
+
<div class="sv-btabs-sep" role="separator"></div>
|
|
177
|
+
<button type="button" role="menuitem" class="sv-btabs-item" title="The tables, links, records, views and automations, ready for Postgres or Supabase" onclick={() => { listOpen = false; onExportSql() }}>
|
|
178
|
+
<span class="sv-btabs-check" aria-hidden="true"></span>Download as SQL
|
|
179
|
+
</button>
|
|
180
|
+
{/if}
|
|
181
|
+
</div>
|
|
182
|
+
</SvPopover>
|
|
183
|
+
<button type="button" class="sv-btabs-tool sv-btabs-add" onclick={onAdd}>
|
|
184
|
+
<svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true"><path d="M12 5v14M5 12h14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" /></svg>
|
|
185
|
+
<span>Add table</span>
|
|
186
|
+
</button>
|
|
187
|
+
</div>
|
|
188
|
+
{#if status}
|
|
189
|
+
<span class="sv-btabs-status" class:is-error={status.tone === 'error'} role="status" title={status.title ?? status.text}>{status.text}</span>
|
|
190
|
+
{/if}
|
|
191
|
+
</div>
|
|
192
|
+
|
|
193
|
+
{#if deleting}
|
|
194
|
+
{@const links = linksTo(deleting)}
|
|
195
|
+
<SvModal bind:open={confirmOpen} title={`Delete ${deleting.name}?`} width={440} onClose={() => (deleting = null)}>
|
|
196
|
+
<p class="sv-btabs-warn">
|
|
197
|
+
Its {deleting.data.length} {deleting.data.length === 1 ? 'record goes' : 'records go'} with it, and so do its views.
|
|
198
|
+
</p>
|
|
199
|
+
{#if links.length}
|
|
200
|
+
<p class="sv-btabs-warn">These fields of other tables link to it, or read a link to it, and are deleted too:</p>
|
|
201
|
+
<ul class="sv-btabs-links">
|
|
202
|
+
{#each links as l (l)}<li>{l}</li>{/each}
|
|
203
|
+
</ul>
|
|
204
|
+
{/if}
|
|
205
|
+
{#snippet footer()}
|
|
206
|
+
<div class="sv-btabs-foot">
|
|
207
|
+
<button type="button" class="sv-btabs-btn" onclick={() => { confirmOpen = false; deleting = null }}>Cancel</button>
|
|
208
|
+
<button type="button" class="sv-btabs-btn is-danger" onclick={confirmDelete}>Delete table</button>
|
|
209
|
+
</div>
|
|
210
|
+
{/snippet}
|
|
211
|
+
</SvModal>
|
|
212
|
+
{/if}
|
|
213
|
+
|
|
214
|
+
<style>
|
|
215
|
+
/* The table bar, from the theme's own tokens. The tables sit on a band of
|
|
216
|
+
the theme's muted surface, closed by a hairline. The other tables are
|
|
217
|
+
muted text divided by hairlines. The shown table is a tab of the
|
|
218
|
+
toolbar's surface, outlined like the grid, open at the bottom: it covers
|
|
219
|
+
the band's hairline and runs into the toolbar below. The theme's accent
|
|
220
|
+
marks it along the top, as the accent marks a selection everywhere. */
|
|
221
|
+
.sv-btabs {
|
|
222
|
+
--sv-btabs-h: 33px;
|
|
223
|
+
display: flex; align-items: flex-end; gap: 2px; box-sizing: border-box;
|
|
224
|
+
height: calc(var(--sv-btabs-h) + 7px); padding: 0 6px 0 0;
|
|
225
|
+
font-family: var(--sg-font, inherit); font-size: 13px; line-height: 1;
|
|
226
|
+
background: var(--sg-muted-bg, var(--sg-header-bg, #f5f5f4));
|
|
227
|
+
box-shadow: inset 0 -1px 0 var(--sg-border, #e7e5e4);
|
|
228
|
+
}
|
|
229
|
+
.sv-btabs-list { display: flex; align-items: flex-end; min-width: 0; height: 100%; padding: 0 0 0 8px; overflow-x: auto; scrollbar-width: none; }
|
|
230
|
+
.sv-btabs-list::-webkit-scrollbar { display: none; }
|
|
231
|
+
.sv-btabs-tab {
|
|
232
|
+
position: relative; flex: none; display: flex; align-items: center; gap: 2px; box-sizing: border-box;
|
|
233
|
+
height: var(--sv-btabs-h); padding: 0 6px 1px 4px;
|
|
234
|
+
border: 1px solid transparent; border-bottom: 0; border-radius: var(--sg-radius, 6px) var(--sg-radius, 6px) 0 0;
|
|
235
|
+
color: var(--sg-muted, #78716c);
|
|
236
|
+
transition: color 120ms ease, background-color 120ms ease;
|
|
237
|
+
}
|
|
238
|
+
/* A hairline between two tables, unless one of them is the shown one. */
|
|
239
|
+
.sv-btabs-tab:not(.is-active) + .sv-btabs-tab:not(.is-active)::before {
|
|
240
|
+
content: ''; position: absolute; left: -2px; top: 10px; bottom: 10px; width: 1px;
|
|
241
|
+
background: var(--sg-border, #e7e5e4);
|
|
242
|
+
}
|
|
243
|
+
/* No hairline beside a hovered tab: its own surface marks it. */
|
|
244
|
+
.sv-btabs-tab:hover::before, .sv-btabs-tab:hover + .sv-btabs-tab::before { opacity: 0; }
|
|
245
|
+
.sv-btabs-tab:not(.is-active):hover {
|
|
246
|
+
color: var(--sg-fg, #1c1917);
|
|
247
|
+
background: color-mix(in srgb, var(--sg-bg, #fff) 55%, transparent);
|
|
248
|
+
}
|
|
249
|
+
.sv-btabs-tab.is-active {
|
|
250
|
+
z-index: 1; height: calc(var(--sv-btabs-h) + 1px); margin-bottom: -1px; padding-bottom: 2px;
|
|
251
|
+
color: var(--sg-fg, #1c1917); background: var(--sg-bg, #fff); border-color: var(--sg-border, #e7e5e4);
|
|
252
|
+
box-shadow: inset 0 2px 0 var(--sg-accent, #2563eb);
|
|
253
|
+
}
|
|
254
|
+
.sv-btabs-name {
|
|
255
|
+
max-width: 220px; height: 26px; padding: 0 8px; border: 0; border-radius: var(--sg-radius, 6px); background: none;
|
|
256
|
+
color: inherit; font: inherit; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; cursor: pointer;
|
|
257
|
+
}
|
|
258
|
+
.is-active .sv-btabs-name { font-weight: 600; padding-right: 2px; }
|
|
259
|
+
.sv-btabs-more {
|
|
260
|
+
display: grid; place-items: center; width: 22px; height: 22px; padding: 0; border: 0; border-radius: var(--sg-radius, 6px);
|
|
261
|
+
background: none; color: var(--sg-muted, #78716c); cursor: pointer;
|
|
262
|
+
}
|
|
263
|
+
/* A tint of the text color, not a surface token: it reads on any theme, light or dark. */
|
|
264
|
+
.sv-btabs-more:hover { background: color-mix(in srgb, var(--sg-fg, #1c1917) 10%, transparent); color: var(--sg-fg, #1c1917); }
|
|
265
|
+
.sv-btabs-input {
|
|
266
|
+
width: 160px; height: 24px; margin: 0 2px; padding: 0 6px; box-sizing: border-box; font: inherit; font-weight: 600;
|
|
267
|
+
color: var(--sg-fg, #1c1917); background: var(--sg-input-bg, var(--sg-bg, #fff));
|
|
268
|
+
border: 1px solid var(--sg-accent, #2563eb); border-radius: var(--sg-radius, 6px); outline: none;
|
|
269
|
+
}
|
|
270
|
+
.sv-btabs-tools { flex: none; display: flex; align-items: center; gap: 2px; height: var(--sv-btabs-h); margin-left: 0; }
|
|
271
|
+
.sv-btabs-status {
|
|
272
|
+
flex: 0 1 auto; align-self: center; min-width: 0; margin-left: auto; padding: 0 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
273
|
+
font-size: 12px; color: var(--sg-muted, #78716c);
|
|
274
|
+
}
|
|
275
|
+
.sv-btabs-status.is-error { color: var(--sg-danger, #dc2626); }
|
|
276
|
+
.sv-btabs-tool {
|
|
277
|
+
display: inline-flex; align-items: center; gap: 6px; height: 26px; padding: 0 8px; border: 0; border-radius: var(--sg-radius, 6px);
|
|
278
|
+
background: none; color: var(--sg-muted, #78716c); font: inherit; cursor: pointer; white-space: nowrap;
|
|
279
|
+
transition: color 120ms ease, background-color 120ms ease;
|
|
280
|
+
}
|
|
281
|
+
.sv-btabs-icon { width: 26px; padding: 0; justify-content: center; }
|
|
282
|
+
.sv-btabs-tool:hover { color: var(--sg-fg, #1c1917); background: color-mix(in srgb, var(--sg-bg, #fff) 55%, transparent); }
|
|
283
|
+
.sv-btabs-name:focus-visible, .sv-btabs-more:focus-visible, .sv-btabs-tool:focus-visible {
|
|
284
|
+
outline: 2px solid var(--sg-focus-ring, var(--sg-accent, #2563eb)); outline-offset: -2px;
|
|
285
|
+
}
|
|
286
|
+
@media (prefers-reduced-motion: reduce) {
|
|
287
|
+
.sv-btabs-tab, .sv-btabs-tool { transition: none; }
|
|
288
|
+
}
|
|
289
|
+
.sv-btabs-menu { display: flex; flex-direction: column; min-width: 190px; max-height: 320px; overflow-y: auto; padding: 4px; font-size: 13px; }
|
|
290
|
+
.sv-btabs-sep { height: 1px; margin: 4px 6px; background: var(--sg-border, #e2e8f0); }
|
|
291
|
+
.sv-btabs-item {
|
|
292
|
+
display: flex; align-items: center; gap: 6px; height: 32px; padding: 0 10px; text-align: start; border: 0; border-radius: 6px;
|
|
293
|
+
background: none; color: var(--sg-fg, #0f172a); font: inherit; cursor: pointer;
|
|
294
|
+
}
|
|
295
|
+
.sv-btabs-item:hover:not(:disabled), .sv-btabs-item:focus-visible { background: color-mix(in srgb, var(--sg-fg, #0f172a) 6%, transparent); outline: none; }
|
|
296
|
+
.sv-btabs-item:disabled { color: var(--sg-muted, #64748b); cursor: default; }
|
|
297
|
+
.sv-btabs-item.is-danger:not(:disabled) { color: var(--sg-danger, #dc2626); }
|
|
298
|
+
.sv-btabs-item.is-current { font-weight: 600; }
|
|
299
|
+
.sv-btabs-check { width: 14px; flex: none; color: var(--sg-muted, #64748b); }
|
|
300
|
+
.sv-btabs-warn { margin: 0 0 8px; font-size: 14px; color: var(--sg-fg, #0f172a); }
|
|
301
|
+
.sv-btabs-links { margin: 0 0 4px; padding-left: 18px; font-size: 13px; color: var(--sg-muted, #64748b); }
|
|
302
|
+
.sv-btabs-foot { display: flex; justify-content: flex-end; gap: 8px; width: 100%; }
|
|
303
|
+
.sv-btabs-btn {
|
|
304
|
+
height: 32px; padding: 0 14px; font: inherit; font-size: 13px; cursor: pointer; border-radius: var(--sg-radius, 6px);
|
|
305
|
+
background: transparent; color: var(--sg-fg, #0f172a); border: 1px solid var(--sg-border, #e2e8f0);
|
|
306
|
+
}
|
|
307
|
+
.sv-btabs-btn.is-danger { background: var(--sg-danger, #dc2626); border-color: var(--sg-danger, #dc2626); color: #fff; }
|
|
308
|
+
</style>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type BaseTable } from './base-tables.js';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
tables: ReadonlyArray<BaseTable>;
|
|
4
|
+
activeId: string;
|
|
5
|
+
onSwitch: (id: string) => void;
|
|
6
|
+
onAdd: () => void;
|
|
7
|
+
onRename: (id: string, name: string) => void;
|
|
8
|
+
onDuplicate: (id: string) => void;
|
|
9
|
+
onDelete: (id: string) => void;
|
|
10
|
+
/** Download the base as Postgres SQL (from the list of tables). */
|
|
11
|
+
onExportSql?: () => void;
|
|
12
|
+
/** A line at the end of the bar: loading, saving, a failed save. */
|
|
13
|
+
status?: {
|
|
14
|
+
text: string;
|
|
15
|
+
tone?: 'muted' | 'error';
|
|
16
|
+
title?: string;
|
|
17
|
+
} | null;
|
|
18
|
+
};
|
|
19
|
+
declare const BaseTabs: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
20
|
+
type BaseTabs = ReturnType<typeof BaseTabs>;
|
|
21
|
+
export default BaseTabs;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Button cells. The action is the column's `fieldProps.onClick` when the app
|
|
4
|
+
* set one, then an automation that runs on this button (`<SvBase>` hands the
|
|
5
|
+
* grid `fieldButtons`), otherwise the URL template with `{Field name}` filled in from the
|
|
6
|
+
* row (values URL-encoded); only an http(s) result opens.
|
|
7
|
+
*/
|
|
8
|
+
import type { FieldCellProps } from './registry.svelte'
|
|
9
|
+
import { buttonUrl } from './button'
|
|
10
|
+
|
|
11
|
+
// `onClick` gets the row's id and the grid's api too, so it can call
|
|
12
|
+
// `openRecord({ api, rowId })`.
|
|
13
|
+
type ButtonClick = { row: unknown; columnId: string; rowId: string; api: unknown }
|
|
14
|
+
let { ctrl, row, column, props }: FieldCellProps<{ label?: string; color?: string; url?: string; onClick?: (ctx: ButtonClick) => void }> = $props()
|
|
15
|
+
const g = $derived(row as { original: Record<string, unknown> })
|
|
16
|
+
const automation = $derived(props.onClick ? null : ((ctrl as { props?: { fieldButtons?: (id: string) => ((row: unknown) => void) | null } })?.props?.fieldButtons?.(column.id) ?? null))
|
|
17
|
+
const href = $derived(props.onClick || automation ? null : buttonUrl(String(props.url ?? ''), g.original, (column.columnDef as { __fieldNames?: Record<string, string> }).__fieldNames))
|
|
18
|
+
const disabled = $derived(!props.onClick && !automation && !href)
|
|
19
|
+
|
|
20
|
+
function run(e: MouseEvent) {
|
|
21
|
+
e.stopPropagation()
|
|
22
|
+
if (props.onClick) props.onClick({ row: g.original, columnId: column.id, rowId: (row as { id: string }).id, api: (ctrl as { buildApi?: () => unknown })?.buildApi?.() })
|
|
23
|
+
else if (automation) automation(g.original)
|
|
24
|
+
else if (href) window.open(href, '_blank', 'noopener,noreferrer')
|
|
25
|
+
}
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<button
|
|
29
|
+
type="button"
|
|
30
|
+
class="sv-fbt"
|
|
31
|
+
style:--c={props.color || '#2d7ff9'}
|
|
32
|
+
{disabled}
|
|
33
|
+
title={href ?? undefined}
|
|
34
|
+
onpointerdown={(e) => e.stopPropagation()}
|
|
35
|
+
onclick={run}
|
|
36
|
+
>{props.label || 'Open'}</button>
|
|
37
|
+
|
|
38
|
+
<style>
|
|
39
|
+
.sv-fbt {
|
|
40
|
+
max-width: 100%; height: 24px; padding: 0 10px; border-radius: 6px; font: inherit; font-size: 12px; font-weight: 500;
|
|
41
|
+
border: 1px solid color-mix(in srgb, var(--c) 45%, transparent);
|
|
42
|
+
background: color-mix(in srgb, var(--c) 12%, transparent); color: var(--sg-fg, #0f172a);
|
|
43
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; cursor: pointer;
|
|
44
|
+
}
|
|
45
|
+
.sv-fbt:hover:not(:disabled) { background: color-mix(in srgb, var(--c) 22%, transparent); }
|
|
46
|
+
.sv-fbt:disabled { opacity: 0.45; cursor: default; }
|
|
47
|
+
</style>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Button cells. The action is the column's `fieldProps.onClick` when the app
|
|
3
|
+
* set one, then an automation that runs on this button (`<SvBase>` hands the
|
|
4
|
+
* grid `fieldButtons`), otherwise the URL template with `{Field name}` filled in from the
|
|
5
|
+
* row (values URL-encoded); only an http(s) result opens.
|
|
6
|
+
*/
|
|
7
|
+
import type { FieldCellProps } from './registry.svelte.js';
|
|
8
|
+
declare const ButtonCell: import("svelte").Component<FieldCellProps<{
|
|
9
|
+
label?: string;
|
|
10
|
+
color?: string;
|
|
11
|
+
url?: string;
|
|
12
|
+
onClick?: (ctx: {
|
|
13
|
+
row: unknown;
|
|
14
|
+
columnId: string;
|
|
15
|
+
rowId: string;
|
|
16
|
+
api: unknown;
|
|
17
|
+
}) => void;
|
|
18
|
+
}>, {}, "">;
|
|
19
|
+
type ButtonCell = ReturnType<typeof ButtonCell>;
|
|
20
|
+
export default ButtonCell;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Checkbox cells with the field's icon and color. The default icon is a
|
|
4
|
+
* box with a check; star, heart and the rest show filled when checked and as
|
|
5
|
+
* a faint outline when not. Clicking and Space still toggle through the grid
|
|
6
|
+
* (the column's editor is 'checkbox'); this only draws.
|
|
7
|
+
*/
|
|
8
|
+
import type { FieldCellProps } from './registry.svelte'
|
|
9
|
+
import { ICONS, STROKE_ICONS, type IconName } from './palette'
|
|
10
|
+
|
|
11
|
+
let { value, props }: FieldCellProps<{ icon?: IconName; color?: string }> = $props()
|
|
12
|
+
const on = $derived(value === true || value === 'true' || value === 1)
|
|
13
|
+
const icon = $derived<IconName>(props.icon && props.icon in ICONS ? props.icon : 'check')
|
|
14
|
+
const color = $derived(props.color || '#20c933')
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<span class="sv-fcb" role="checkbox" aria-checked={on} aria-readonly="true" aria-label={on ? 'Checked' : 'Not checked'} style:--c={color}>
|
|
18
|
+
{#if icon === 'check' || icon === 'x'}
|
|
19
|
+
<span class="sv-fcb-box" class:is-on={on}>
|
|
20
|
+
{#if on}<svg viewBox="0 0 24 24" width="12" height="12" aria-hidden="true"><path d={ICONS[icon]} fill="none" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" /></svg>{/if}
|
|
21
|
+
</span>
|
|
22
|
+
{:else}
|
|
23
|
+
<svg class="sv-fcb-icon" class:is-on={on} viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
|
|
24
|
+
<path
|
|
25
|
+
d={ICONS[icon]}
|
|
26
|
+
fill={on && !STROKE_ICONS.has(icon) ? 'var(--c)' : 'none'}
|
|
27
|
+
stroke={on ? 'var(--c)' : 'currentColor'}
|
|
28
|
+
stroke-width="1.8"
|
|
29
|
+
stroke-linecap="round"
|
|
30
|
+
stroke-linejoin="round"
|
|
31
|
+
/>
|
|
32
|
+
</svg>
|
|
33
|
+
{/if}
|
|
34
|
+
</span>
|
|
35
|
+
|
|
36
|
+
<style>
|
|
37
|
+
.sv-fcb { display: inline-flex; align-items: center; justify-content: center; width: 100%; color: var(--sg-muted, #94a3b8); }
|
|
38
|
+
.sv-fcb-box {
|
|
39
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
40
|
+
width: 16px; height: 16px; border-radius: 4px; box-sizing: border-box;
|
|
41
|
+
border: 1.5px solid color-mix(in srgb, var(--sg-muted, #94a3b8) 70%, transparent);
|
|
42
|
+
}
|
|
43
|
+
.sv-fcb-box.is-on { background: var(--c); border-color: var(--c); }
|
|
44
|
+
.sv-fcb-icon { opacity: 0.45; }
|
|
45
|
+
.sv-fcb-icon.is-on { opacity: 1; }
|
|
46
|
+
</style>
|