@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,17 @@
|
|
|
1
|
+
/** The URL a button field opens: `{Field name}` tokens filled from the row, http(s) only. */
|
|
2
|
+
export function buttonUrl(template, row, names) {
|
|
3
|
+
if (!template.trim() || !row)
|
|
4
|
+
return null;
|
|
5
|
+
const filled = template.replace(/\{([^}]+)\}/g, (_m, name) => {
|
|
6
|
+
const key = names?.[name.trim().toLowerCase()] ?? name.trim();
|
|
7
|
+
const v = row[key];
|
|
8
|
+
return encodeURIComponent(v == null ? '' : Array.isArray(v) ? v.join(',') : String(v));
|
|
9
|
+
});
|
|
10
|
+
try {
|
|
11
|
+
const url = new URL(filled);
|
|
12
|
+
return url.protocol === 'http:' || url.protocol === 'https:' ? url.href : null;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The chart view: the records the view shows, grouped by one field and
|
|
3
|
+
* counted, summed or averaged, as a bar, line, pie or donut chart. A select
|
|
4
|
+
* keeps its options' order and colors, a person and a linked record show by
|
|
5
|
+
* name, a date groups by month.
|
|
6
|
+
*/
|
|
7
|
+
import type { ChartSpec } from '@svgrid/grid';
|
|
8
|
+
import { type FieldColumn } from './grid-access.js';
|
|
9
|
+
type Row = Record<string, unknown>;
|
|
10
|
+
export type ChartKind = 'bar' | 'barH' | 'line' | 'pie' | 'donut';
|
|
11
|
+
/** What a chart view draws: its kind, the field on the axis, and the value (a count without `y`). */
|
|
12
|
+
export type ChartSetting = {
|
|
13
|
+
kind: ChartKind;
|
|
14
|
+
/** The field the records are grouped by. */
|
|
15
|
+
x: string;
|
|
16
|
+
/** A number field to add up; without it, each group counts its records. */
|
|
17
|
+
y?: string;
|
|
18
|
+
/** How `y` is combined: its sum (default) or its average. */
|
|
19
|
+
aggregation?: 'sum' | 'avg';
|
|
20
|
+
};
|
|
21
|
+
export declare const CHART_KINDS: ReadonlyArray<{
|
|
22
|
+
kind: ChartKind;
|
|
23
|
+
label: string;
|
|
24
|
+
}>;
|
|
25
|
+
/** Fields a chart can group by. */
|
|
26
|
+
export declare const chartXColumns: (ctrl: any) => FieldColumn[];
|
|
27
|
+
/** Fields a chart can add up. */
|
|
28
|
+
export declare const chartYColumns: (ctrl: any) => FieldColumn[];
|
|
29
|
+
/** A first setting for a new chart view: the first select (or any field it can group by), counted. */
|
|
30
|
+
export declare function defaultChart(ctrl: any): ChartSetting | null;
|
|
31
|
+
/** The chart for `rows` (the records the view shows), or null when its fields are gone. */
|
|
32
|
+
export declare function chartSpec(ctrl: any, setting: ChartSetting, rows: ReadonlyArray<Row>): ChartSpec | null;
|
|
33
|
+
/** How a value shows on the axis and in tooltips: as the summed field shows it ($48,000), a count as a whole number. */
|
|
34
|
+
export declare function chartFormatter(ctrl: any, setting: ChartSetting): ((value: number) => string) | undefined;
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { fieldColumns } from './grid-access.js';
|
|
2
|
+
import { fieldPropsOf, originalColumn } from './registry.svelte.js';
|
|
3
|
+
import { findFieldUser, getLinkedTable, linkedRows } from './sources.svelte.js';
|
|
4
|
+
export const CHART_KINDS = [
|
|
5
|
+
{ kind: 'bar', label: 'Bar' },
|
|
6
|
+
{ kind: 'barH', label: 'Horizontal bar' },
|
|
7
|
+
{ kind: 'line', label: 'Line' },
|
|
8
|
+
{ kind: 'pie', label: 'Pie' },
|
|
9
|
+
{ kind: 'donut', label: 'Donut' },
|
|
10
|
+
];
|
|
11
|
+
// A formula and a lookup group by the value they compute (Status: Low, OK, Out).
|
|
12
|
+
const X_TYPES = new Set(['singleSelect', 'multiSelect', 'user', 'date', 'checkbox', 'link', 'text', 'rating', 'createdTime', 'createdBy', 'formula', 'lookup']);
|
|
13
|
+
const NUMBER_TYPES = new Set(['number', 'currency', 'percent', 'rating', 'duration', 'count', 'rollup']);
|
|
14
|
+
const NUMERIC_RESULTS = new Set(['number', 'currency', 'percent']);
|
|
15
|
+
/** Fields a chart can group by. */
|
|
16
|
+
export const chartXColumns = (ctrl) => fieldColumns(ctrl).filter((c) => X_TYPES.has(c.fieldType ?? '') || c.def.cellDataType === 'date' || c.def.cellDataType === 'dateString');
|
|
17
|
+
/** Fields a chart can add up. */
|
|
18
|
+
export const chartYColumns = (ctrl) => fieldColumns(ctrl).filter((c) => NUMBER_TYPES.has(c.fieldType ?? '') || (c.fieldType === 'formula' && NUMERIC_RESULTS.has(String(fieldPropsOf(originalColumn(c.def)).result))) || (!c.fieldType && c.def.cellDataType === 'number'));
|
|
19
|
+
/** A first setting for a new chart view: the first select (or any field it can group by), counted. */
|
|
20
|
+
export function defaultChart(ctrl) {
|
|
21
|
+
const xs = chartXColumns(ctrl);
|
|
22
|
+
const x = xs.find((c) => c.fieldType === 'singleSelect') ?? xs[0];
|
|
23
|
+
return x ? { kind: 'bar', x: x.id } : null;
|
|
24
|
+
}
|
|
25
|
+
const EMPTY = 'Empty';
|
|
26
|
+
const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
27
|
+
/** The value a column shows for a row: computed for a formula, rollup or count. */
|
|
28
|
+
function valueOf(c, row) {
|
|
29
|
+
const fn = c.def.fieldFn;
|
|
30
|
+
return fn ? fn(row) : row?.[c.field];
|
|
31
|
+
}
|
|
32
|
+
/** The group keys a row falls in for column `c` (a multiple select or a link is in several). */
|
|
33
|
+
function keysOf(c, row) {
|
|
34
|
+
const v = valueOf(c, row);
|
|
35
|
+
const list = Array.isArray(v) ? v : v == null || v === '' ? [] : [v];
|
|
36
|
+
if (c.fieldType === 'checkbox')
|
|
37
|
+
return [v === true ? 'Checked' : 'Unchecked'];
|
|
38
|
+
if (!list.length)
|
|
39
|
+
return [EMPTY];
|
|
40
|
+
if (c.fieldType === 'user' || c.fieldType === 'createdBy')
|
|
41
|
+
return list.map((id) => findFieldUser(id)?.name ?? String(id));
|
|
42
|
+
if (c.fieldType === 'link') {
|
|
43
|
+
const t = getLinkedTable(fieldPropsOf(originalColumn(c.def)).table);
|
|
44
|
+
const names = t ? linkedRows(t, v).map((r) => String(r[t.primaryField] ?? '')) : [];
|
|
45
|
+
return names.length ? names : [EMPTY];
|
|
46
|
+
}
|
|
47
|
+
if (c.fieldType === 'lookup') {
|
|
48
|
+
// Each linked value as the lookup shows it (a linked date or amount in its own format).
|
|
49
|
+
const fmt = c.def.formatter;
|
|
50
|
+
const names = list.map((x) => (fmt ? String(fmt({ value: x }) ?? '') : String(x))).filter((s) => s !== '');
|
|
51
|
+
return names.length ? [...new Set(names)] : [EMPTY];
|
|
52
|
+
}
|
|
53
|
+
if (c.fieldType === 'formula') {
|
|
54
|
+
const dated = fieldPropsOf(originalColumn(c.def)).result === 'date' || c.def.cellDataType === 'date' || c.def.cellDataType === 'dateString';
|
|
55
|
+
const keys = list.map((x) => {
|
|
56
|
+
// A date value groups by its local month, as a stored date does.
|
|
57
|
+
if (x instanceof Date)
|
|
58
|
+
return Number.isNaN(x.getTime()) ? EMPTY : `${x.getFullYear()}-${String(x.getMonth() + 1).padStart(2, '0')}`;
|
|
59
|
+
if (typeof x === 'number')
|
|
60
|
+
return String(Math.round(x * 1e10) / 1e10);
|
|
61
|
+
if (dated && typeof x === 'string' && /^\d{4}-\d{2}-\d{2}/.test(x))
|
|
62
|
+
return x.slice(0, 7);
|
|
63
|
+
return String(x);
|
|
64
|
+
});
|
|
65
|
+
return [...new Set(keys)];
|
|
66
|
+
}
|
|
67
|
+
if (c.fieldType === 'date' || c.fieldType === 'createdTime' || c.def.cellDataType === 'date' || c.def.cellDataType === 'dateString') {
|
|
68
|
+
const s = String(list[0]);
|
|
69
|
+
const m = /^(\d{4})-(\d{2})/.exec(s);
|
|
70
|
+
return [m ? `${m[1]}-${m[2]}` : s];
|
|
71
|
+
}
|
|
72
|
+
return list.map((x) => String(x));
|
|
73
|
+
}
|
|
74
|
+
/** The category labels in their order: a select's options first, months by date, else as met (numbers ascending). */
|
|
75
|
+
function orderOf(c, keys) {
|
|
76
|
+
const colors = {};
|
|
77
|
+
const empty = keys.includes(EMPTY) ? [EMPTY] : [];
|
|
78
|
+
const rest = keys.filter((k) => k !== EMPTY);
|
|
79
|
+
if (c.fieldType === 'singleSelect' || c.fieldType === 'multiSelect') {
|
|
80
|
+
const options = fieldPropsOf(originalColumn(c.def)).options ?? [];
|
|
81
|
+
const rank = new Map(options.map((o, i) => [String(o.value), i]));
|
|
82
|
+
for (const o of options)
|
|
83
|
+
if (o.color)
|
|
84
|
+
colors[String(o.value)] = o.color;
|
|
85
|
+
rest.sort((a, b) => (rank.get(a) ?? 1e9) - (rank.get(b) ?? 1e9));
|
|
86
|
+
return { keys: [...rest, ...empty], label: (k) => k, colors };
|
|
87
|
+
}
|
|
88
|
+
const isMonth = rest.length > 0 && rest.every((k) => /^\d{4}-\d{2}$/.test(k));
|
|
89
|
+
if (isMonth) {
|
|
90
|
+
rest.sort();
|
|
91
|
+
return { keys: [...rest, ...empty], label: (k) => (k === EMPTY ? k : `${MONTHS[Number(k.slice(5)) - 1]} ${k.slice(0, 4)}`), colors };
|
|
92
|
+
}
|
|
93
|
+
if (rest.length && rest.every((k) => k !== '' && !Number.isNaN(Number(k))))
|
|
94
|
+
rest.sort((a, b) => Number(a) - Number(b));
|
|
95
|
+
return { keys: [...rest, ...empty], label: (k) => k, colors };
|
|
96
|
+
}
|
|
97
|
+
/** The chart for `rows` (the records the view shows), or null when its fields are gone. */
|
|
98
|
+
export function chartSpec(ctrl, setting, rows) {
|
|
99
|
+
const cols = fieldColumns(ctrl);
|
|
100
|
+
const x = cols.find((c) => c.id === setting.x);
|
|
101
|
+
if (!x)
|
|
102
|
+
return null;
|
|
103
|
+
const y = setting.y ? cols.find((c) => c.id === setting.y) : undefined;
|
|
104
|
+
const sums = new Map();
|
|
105
|
+
for (const row of rows) {
|
|
106
|
+
if (row == null)
|
|
107
|
+
continue;
|
|
108
|
+
const raw = y ? Number(valueOf(y, row)) : 1;
|
|
109
|
+
for (const k of keysOf(x, row)) {
|
|
110
|
+
const s = sums.get(k) ?? { total: 0, n: 0 };
|
|
111
|
+
if (!y || Number.isFinite(raw)) {
|
|
112
|
+
s.total += Number.isFinite(raw) ? raw : 0;
|
|
113
|
+
s.n += 1;
|
|
114
|
+
}
|
|
115
|
+
sums.set(k, s);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const { keys, label, colors } = orderOf(x, [...sums.keys()]);
|
|
119
|
+
const avg = !!y && setting.aggregation === 'avg';
|
|
120
|
+
const values = keys.map((k) => {
|
|
121
|
+
const s = sums.get(k);
|
|
122
|
+
return avg ? (s.n ? s.total / s.n : 0) : s.total;
|
|
123
|
+
});
|
|
124
|
+
const categories = keys.map(label);
|
|
125
|
+
const categoryColors = {};
|
|
126
|
+
keys.forEach((k, i) => {
|
|
127
|
+
if (colors[k])
|
|
128
|
+
categoryColors[categories[i]] = colors[k];
|
|
129
|
+
});
|
|
130
|
+
const kind = setting.kind;
|
|
131
|
+
return {
|
|
132
|
+
type: kind === 'barH' ? 'bar' : kind === 'donut' ? 'pie' : kind,
|
|
133
|
+
categories,
|
|
134
|
+
series: [
|
|
135
|
+
{
|
|
136
|
+
label: y ? `${avg ? 'Average' : 'Sum'} of ${y.header}` : 'Records',
|
|
137
|
+
values,
|
|
138
|
+
// Each bar in its option's color (a pie takes them from categoryColors).
|
|
139
|
+
...(Object.keys(categoryColors).length && (kind === 'bar' || kind === 'barH') ? { colors: keys.map((k) => colors[k] ?? null) } : {}),
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
...(Object.keys(categoryColors).length ? { categoryColors } : {}),
|
|
143
|
+
...(kind === 'barH' ? { orientation: 'horizontal' } : {}),
|
|
144
|
+
...(kind === 'donut' ? { innerRadius: 0.58 } : {}),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/** How a value shows on the axis and in tooltips: as the summed field shows it ($48,000), a count as a whole number. */
|
|
148
|
+
export function chartFormatter(ctrl, setting) {
|
|
149
|
+
const y = setting.y ? fieldColumns(ctrl).find((c) => c.id === setting.y) : undefined;
|
|
150
|
+
if (!y)
|
|
151
|
+
return (v) => String(Math.round(v));
|
|
152
|
+
const fmt = y.def.formatter;
|
|
153
|
+
return fmt ? (v) => String(fmt({ value: v }) ?? v) : undefined;
|
|
154
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The full column order with the shown columns in `visual` order and every
|
|
3
|
+
* other column (hidden ones) left in its slot, so reordering what is shown
|
|
4
|
+
* does not move what is hidden. (The grid's pin splitter keeps the same rule.)
|
|
5
|
+
*/
|
|
6
|
+
export declare function orderKeepingScreen(base: ReadonlyArray<string>, visual: ReadonlyArray<string>): string[];
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The full column order with the shown columns in `visual` order and every
|
|
3
|
+
* other column (hidden ones) left in its slot, so reordering what is shown
|
|
4
|
+
* does not move what is hidden. (The grid's pin splitter keeps the same rule.)
|
|
5
|
+
*/
|
|
6
|
+
export function orderKeepingScreen(base, visual) {
|
|
7
|
+
const inBase = new Set(base);
|
|
8
|
+
const shown = visual.filter((id) => inBase.has(id));
|
|
9
|
+
const shownSet = new Set(shown);
|
|
10
|
+
let i = 0;
|
|
11
|
+
return base.map((id) => (shownSet.has(id) ? (shown[i++] ?? id) : id));
|
|
12
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Whether switching a field to `to` keeps its stored values as they are. */
|
|
2
|
+
export declare const keepsStoredValues: (to: string) => boolean;
|
|
3
|
+
export declare const isEmptyValue: (v: unknown) => boolean;
|
|
4
|
+
/** A value as plain text, whatever its type. */
|
|
5
|
+
export declare function asText(v: unknown, fromType?: string): string;
|
|
6
|
+
/** `value` of a field of type `from`, as a value of type `to` (with `toProps`, the new field's properties). */
|
|
7
|
+
export declare function convertValue(value: unknown, from: string | undefined, to: string, toProps?: Record<string, unknown>): unknown;
|
|
8
|
+
/**
|
|
9
|
+
* How many of `values` are not empty now and would lose their value after
|
|
10
|
+
* converting: cleared, or cut to part of it (several names to a single select).
|
|
11
|
+
*/
|
|
12
|
+
export declare function countCleared(values: ReadonlyArray<unknown>, from: string | undefined, to: string, toProps?: Record<string, unknown>): number;
|
|
13
|
+
/** Select options for converted values: every distinct value, in first-seen order. */
|
|
14
|
+
export declare function optionsFromValues(values: ReadonlyArray<unknown>): Array<{
|
|
15
|
+
value: string;
|
|
16
|
+
}>;
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Changing a field's type converts its values where they can be: a number
|
|
3
|
+
* typed as text stays a number, a comma list becomes multiple-select values,
|
|
4
|
+
* URLs in text become attachments. What cannot convert becomes empty, and the
|
|
5
|
+
* field editor says how many cells that is before it applies the change.
|
|
6
|
+
*/
|
|
7
|
+
import { attachmentsFromText, attachmentsText, toAttachments } from './attachments.js';
|
|
8
|
+
import { parseDuration } from './format.js';
|
|
9
|
+
import { fillFieldColumn, getFieldType } from './registry.svelte.js';
|
|
10
|
+
import { splitNames } from './name-list.js';
|
|
11
|
+
import { getLinkedTable, resolveLinkId, resolveLinkIds } from './sources.svelte.js';
|
|
12
|
+
const TEXT_TYPES = new Set(['text', 'longText', 'url', 'email', 'phone', 'barcode']);
|
|
13
|
+
/** Types whose cells the module computes or writes itself: switching to one keeps the stored values and clears nothing. */
|
|
14
|
+
const COMPUTED_TYPES = new Set(['formula', 'lookup', 'rollup', 'count', 'createdTime', 'lastModifiedTime', 'createdBy', 'lastModifiedBy', 'autoNumber', 'button']);
|
|
15
|
+
const NUMBER_TYPES = new Set(['number', 'currency', 'percent', 'rating']);
|
|
16
|
+
/** Whether switching a field to `to` keeps its stored values as they are. */
|
|
17
|
+
export const keepsStoredValues = (to) => COMPUTED_TYPES.has(to);
|
|
18
|
+
export const isEmptyValue = (v) => v == null || v === '' || (Array.isArray(v) && v.length === 0) || (typeof v === 'number' && Number.isNaN(v));
|
|
19
|
+
/** A value as plain text, whatever its type. */
|
|
20
|
+
export function asText(v, fromType) {
|
|
21
|
+
if (v == null)
|
|
22
|
+
return '';
|
|
23
|
+
if (fromType === 'attachment')
|
|
24
|
+
return attachmentsText(v);
|
|
25
|
+
if (Array.isArray(v))
|
|
26
|
+
return v.map((x) => asText(x)).filter(Boolean).join(', ');
|
|
27
|
+
if (v instanceof Date)
|
|
28
|
+
return localDay(v);
|
|
29
|
+
return String(v);
|
|
30
|
+
}
|
|
31
|
+
/** A date's local calendar day as YYYY-MM-DD: a formula's TODAY() is local midnight. */
|
|
32
|
+
const localDay = (d) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
|
33
|
+
/** `value` of a field of type `from`, as a value of type `to` (with `toProps`, the new field's properties). */
|
|
34
|
+
export function convertValue(value, from, to, toProps) {
|
|
35
|
+
if (COMPUTED_TYPES.has(to))
|
|
36
|
+
return value;
|
|
37
|
+
if (isEmptyValue(value))
|
|
38
|
+
return to === 'checkbox' ? false : to === 'multiSelect' || to === 'attachment' ? [] : null;
|
|
39
|
+
if (from === to)
|
|
40
|
+
return value;
|
|
41
|
+
if (TEXT_TYPES.has(to))
|
|
42
|
+
return asText(value, from);
|
|
43
|
+
if (to === 'duration')
|
|
44
|
+
return typeof value === 'number' ? value : parseDuration(asText(value, from));
|
|
45
|
+
if (to === 'user')
|
|
46
|
+
return Array.isArray(value) ? value.map((x) => asText(x)) : asText(value, from) || null;
|
|
47
|
+
if (to === 'link') {
|
|
48
|
+
// Names (a multiple select, a lookup, "Acme, Northwind" in text) become
|
|
49
|
+
// the ids of the linked table's records; a name no record has is dropped.
|
|
50
|
+
const table = getLinkedTable(toProps?.table);
|
|
51
|
+
const ids = table ? resolveLinkIds(table, linkParts(value, from)) : [];
|
|
52
|
+
return toProps?.multiple === false ? ids.slice(0, 1) : ids;
|
|
53
|
+
}
|
|
54
|
+
if (NUMBER_TYPES.has(to)) {
|
|
55
|
+
if (typeof value === 'boolean')
|
|
56
|
+
return value ? 1 : 0;
|
|
57
|
+
if (typeof value === 'number')
|
|
58
|
+
return to === 'rating' ? Math.max(0, Math.min(10, Math.round(value))) : value;
|
|
59
|
+
// A list (a lookup, a multiple select) is a number only when it holds one value: [1, 2] is not 12.
|
|
60
|
+
if (Array.isArray(value))
|
|
61
|
+
return value.length === 1 ? convertValue(value[0], undefined, to) : null;
|
|
62
|
+
// Keep digits, the decimal point, a sign and an exponent: "$1,200" -> 1200.
|
|
63
|
+
const kept = asText(value, from).replace(/[^0-9.eE+-]/g, '');
|
|
64
|
+
// No digit at all ("n/a") is not zero; Number('') would say it is.
|
|
65
|
+
const n = /\d/.test(kept) ? Number(kept) : NaN;
|
|
66
|
+
if (!Number.isFinite(n))
|
|
67
|
+
return null;
|
|
68
|
+
return to === 'rating' ? Math.max(0, Math.min(10, Math.round(n))) : n;
|
|
69
|
+
}
|
|
70
|
+
if (to === 'checkbox') {
|
|
71
|
+
if (typeof value === 'boolean')
|
|
72
|
+
return value;
|
|
73
|
+
if (typeof value === 'number')
|
|
74
|
+
return value !== 0;
|
|
75
|
+
return /^(true|yes|y|1|x|on|done|checked)$/i.test(asText(value, from).trim());
|
|
76
|
+
}
|
|
77
|
+
if (to === 'date') {
|
|
78
|
+
if (value instanceof Date)
|
|
79
|
+
return Number.isNaN(value.getTime()) ? null : localDay(value);
|
|
80
|
+
const t = new Date(asText(value, from));
|
|
81
|
+
return Number.isNaN(t.getTime()) ? null : t.toISOString().slice(0, 10);
|
|
82
|
+
}
|
|
83
|
+
if (to === 'singleSelect') {
|
|
84
|
+
const text = Array.isArray(value) ? asText(value[0]) : asText(value, from);
|
|
85
|
+
return text.trim() || null;
|
|
86
|
+
}
|
|
87
|
+
if (to === 'multiSelect') {
|
|
88
|
+
const parts = Array.isArray(value) ? value.map((x) => asText(x)) : asText(value, from).split(',');
|
|
89
|
+
return [...new Set(parts.map((s) => s.trim()).filter(Boolean))];
|
|
90
|
+
}
|
|
91
|
+
if (to === 'attachment') {
|
|
92
|
+
return from === 'attachment' ? toAttachments(value) : attachmentsFromText(asText(value, from));
|
|
93
|
+
}
|
|
94
|
+
// An app-registered type: the value as text, read by the type's valueParser
|
|
95
|
+
// as a paste would be. Without a parser, a list or an object becomes its
|
|
96
|
+
// text and a plain value stays as it is.
|
|
97
|
+
const parse = parserFor(to, toProps);
|
|
98
|
+
const text = asText(value, from);
|
|
99
|
+
if (!parse)
|
|
100
|
+
return value !== null && typeof value === 'object' ? text || null : value;
|
|
101
|
+
const out = parse(text);
|
|
102
|
+
return out === undefined ? null : out;
|
|
103
|
+
}
|
|
104
|
+
// Per registered type, per its properties (as JSON): the parser its fill gives a column.
|
|
105
|
+
const parsers = new WeakMap();
|
|
106
|
+
function parserFor(to, props) {
|
|
107
|
+
const type = getFieldType(to);
|
|
108
|
+
if (!type)
|
|
109
|
+
return null;
|
|
110
|
+
let byProps = parsers.get(type);
|
|
111
|
+
if (!byProps)
|
|
112
|
+
parsers.set(type, (byProps = new Map()));
|
|
113
|
+
const key = JSON.stringify(props ?? {});
|
|
114
|
+
if (!byProps.has(key)) {
|
|
115
|
+
const fn = fillFieldColumn({ field: '__convert', fieldType: to, fieldProps: props ?? {} }).valueParser;
|
|
116
|
+
byProps.set(key, typeof fn === 'function'
|
|
117
|
+
? (text) => {
|
|
118
|
+
try {
|
|
119
|
+
return fn({ newValue: text, oldValue: null, rawInput: text });
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
: null);
|
|
126
|
+
}
|
|
127
|
+
return byProps.get(key);
|
|
128
|
+
}
|
|
129
|
+
/** The names a value gives a link: a list's items, or text split at commas outside quotes. */
|
|
130
|
+
function linkParts(value, from) {
|
|
131
|
+
return Array.isArray(value) ? value.map((x) => asText(x).trim()).filter(Boolean) : splitNames(asText(value, from));
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Whether converting `value` keeps only part of it: several names to a single
|
|
135
|
+
* select (the first one stays), or names a link cannot find or cannot hold.
|
|
136
|
+
*/
|
|
137
|
+
function losesPart(value, from, to, toProps) {
|
|
138
|
+
if (to === 'singleSelect')
|
|
139
|
+
return Array.isArray(value) && new Set(value.map((x) => asText(x).trim()).filter(Boolean)).size > 1;
|
|
140
|
+
if (to === 'link') {
|
|
141
|
+
const table = getLinkedTable(toProps?.table);
|
|
142
|
+
if (!table)
|
|
143
|
+
return false;
|
|
144
|
+
const ids = linkParts(value, from).map((part) => resolveLinkId(table, part));
|
|
145
|
+
return ids.some((id) => id == null) || (toProps?.multiple === false && new Set(ids).size > 1);
|
|
146
|
+
}
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* How many of `values` are not empty now and would lose their value after
|
|
151
|
+
* converting: cleared, or cut to part of it (several names to a single select).
|
|
152
|
+
*/
|
|
153
|
+
export function countCleared(values, from, to, toProps) {
|
|
154
|
+
if (COMPUTED_TYPES.has(to) || from === to)
|
|
155
|
+
return 0;
|
|
156
|
+
let n = 0;
|
|
157
|
+
for (const v of values) {
|
|
158
|
+
if (isEmptyValue(v))
|
|
159
|
+
continue;
|
|
160
|
+
if (isEmptyValue(convertValue(v, from, to, toProps)) || losesPart(v, from, to, toProps))
|
|
161
|
+
n += 1;
|
|
162
|
+
}
|
|
163
|
+
return n;
|
|
164
|
+
}
|
|
165
|
+
/** Select options for converted values: every distinct value, in first-seen order. */
|
|
166
|
+
export function optionsFromValues(values) {
|
|
167
|
+
const seen = new Set();
|
|
168
|
+
for (const v of values) {
|
|
169
|
+
for (const s of Array.isArray(v) ? v : [v]) {
|
|
170
|
+
const t = s == null ? '' : String(s).trim();
|
|
171
|
+
if (t)
|
|
172
|
+
seen.add(t);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return [...seen].slice(0, 200).map((value) => ({ value }));
|
|
176
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a copy (Ctrl+C) writes for a field of a base: names for people and
|
|
3
|
+
* links (their paste reads names back), the URLs of attachments (a paste of
|
|
4
|
+
* URLs attaches them), a barcode's text. Every other value is copied as
|
|
5
|
+
* stored, so numbers and dates paste back exactly. Names of linked records
|
|
6
|
+
* and of several people are quoted when they hold a comma ("Acme, Inc."),
|
|
7
|
+
* so the paste splits them back the same way.
|
|
8
|
+
*/
|
|
9
|
+
export declare function fieldCopyText(p: {
|
|
10
|
+
value: unknown;
|
|
11
|
+
column: unknown;
|
|
12
|
+
row: unknown;
|
|
13
|
+
}): unknown;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { toAttachments } from './attachments.js';
|
|
2
|
+
import { joinNames } from './name-list.js';
|
|
3
|
+
import { findFieldUser, getLinkedTable, linkedRows } from './sources.svelte.js';
|
|
4
|
+
// Types that store ids or objects: a copy writes what their cell shows.
|
|
5
|
+
const AS_SHOWN = new Set(['user', 'createdBy', 'lastModifiedBy', 'link', 'lookup', 'barcode']);
|
|
6
|
+
/**
|
|
7
|
+
* A stored URL as an absolute one: a paste attaches only http(s) URLs, so a
|
|
8
|
+
* relative one ('/api/files/123') is resolved against the page.
|
|
9
|
+
*/
|
|
10
|
+
function absoluteUrl(url) {
|
|
11
|
+
if (typeof location === 'undefined')
|
|
12
|
+
return url;
|
|
13
|
+
try {
|
|
14
|
+
return new URL(url, location.href).href;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return url;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* What a copy (Ctrl+C) writes for a field of a base: names for people and
|
|
22
|
+
* links (their paste reads names back), the URLs of attachments (a paste of
|
|
23
|
+
* URLs attaches them), a barcode's text. Every other value is copied as
|
|
24
|
+
* stored, so numbers and dates paste back exactly. Names of linked records
|
|
25
|
+
* and of several people are quoted when they hold a comma ("Acme, Inc."),
|
|
26
|
+
* so the paste splits them back the same way.
|
|
27
|
+
*/
|
|
28
|
+
export function fieldCopyText(p) {
|
|
29
|
+
const def = p.column?.columnDef;
|
|
30
|
+
const type = def?.fieldType;
|
|
31
|
+
if (type === 'attachment')
|
|
32
|
+
return toAttachments(p.value).map((a) => a.url).filter(Boolean).map(absoluteUrl).join(' ');
|
|
33
|
+
if (type === 'link') {
|
|
34
|
+
const table = getLinkedTable(def?.fieldProps?.table);
|
|
35
|
+
if (table)
|
|
36
|
+
return joinNames(linkedRows(table, p.value).map((r) => String(r[table.primaryField] ?? '')));
|
|
37
|
+
}
|
|
38
|
+
if (type === 'user' && def?.fieldProps?.multiple) {
|
|
39
|
+
const ids = Array.isArray(p.value) ? p.value : p.value == null || p.value === '' ? [] : [p.value];
|
|
40
|
+
return joinNames(ids.map((x) => findFieldUser(x)?.name ?? String(x)));
|
|
41
|
+
}
|
|
42
|
+
if (!AS_SHOWN.has(type) || typeof def?.formatter !== 'function')
|
|
43
|
+
return p.value;
|
|
44
|
+
return def.formatter({ value: p.value, row: p.row, column: p.column });
|
|
45
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
export type FieldEditorRequest = {
|
|
2
|
+
ctrl: unknown;
|
|
3
|
+
mode: 'add' | 'edit';
|
|
4
|
+
/** The field being edited (mode 'edit'). */
|
|
5
|
+
columnId?: string;
|
|
6
|
+
/** Where a new field goes (mode 'add'). */
|
|
7
|
+
index?: number | 'end';
|
|
8
|
+
};
|
|
9
|
+
export declare const getFieldEditorRequest: () => FieldEditorRequest | null;
|
|
10
|
+
export declare function openFieldEditor(r: FieldEditorRequest): void;
|
|
11
|
+
export declare function closeFieldEditor(): void;
|
|
12
|
+
/**
|
|
13
|
+
* Which grid's record view is open, and on which row. The row header's expand
|
|
14
|
+
* button sets it; FieldsHeaderEnd shows the view when it names its own grid.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* `editPrimary`: a record just made, so its name is the first thing to type.
|
|
18
|
+
* `focus: 'comment'`: the record opens with its comment box focused.
|
|
19
|
+
*/
|
|
20
|
+
export type RecordRequest = {
|
|
21
|
+
ctrl: unknown;
|
|
22
|
+
rowId: string;
|
|
23
|
+
editPrimary?: boolean;
|
|
24
|
+
focus?: 'comment';
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* What `openRecord` takes: the grid as `ctrl`, or as `api`, the object
|
|
28
|
+
* `onApiReady` hands the app (an api passed as `ctrl` works too).
|
|
29
|
+
*/
|
|
30
|
+
export type OpenRecordRequest = {
|
|
31
|
+
ctrl?: unknown;
|
|
32
|
+
api?: unknown;
|
|
33
|
+
rowId: string;
|
|
34
|
+
editPrimary?: boolean;
|
|
35
|
+
focus?: 'comment';
|
|
36
|
+
};
|
|
37
|
+
export declare const getRecordRequest: () => RecordRequest | null;
|
|
38
|
+
export declare function openRecord(r: OpenRecordRequest): void;
|
|
39
|
+
/** Close the open record; given a grid (controller or api), only when it is open on that grid. */
|
|
40
|
+
export declare function closeRecord(grid?: unknown): void;
|
|
41
|
+
/**
|
|
42
|
+
* Records of a linked table opened from a link chip, newest last (a linked
|
|
43
|
+
* record can open another over it), and the chip the pointer rests on. The
|
|
44
|
+
* grid the chip is in shows them (FieldsHeaderEnd).
|
|
45
|
+
*/
|
|
46
|
+
export type LinkedRecordRequest = {
|
|
47
|
+
ctrl: unknown;
|
|
48
|
+
table: string;
|
|
49
|
+
rowId: string;
|
|
50
|
+
};
|
|
51
|
+
export declare const getLinkedRecords: () => LinkedRecordRequest[];
|
|
52
|
+
export declare function openLinkedRecord(r: LinkedRecordRequest): void;
|
|
53
|
+
export declare function closeLinkedRecord(): void;
|
|
54
|
+
/**
|
|
55
|
+
* A field to delete once the user confirms: a link with a field that links
|
|
56
|
+
* back or fields that read it, or any field that formulas read.
|
|
57
|
+
*/
|
|
58
|
+
export type DeleteFieldRequest = {
|
|
59
|
+
ctrl: unknown;
|
|
60
|
+
columnId: string;
|
|
61
|
+
};
|
|
62
|
+
export declare const getDeleteFieldRequest: () => DeleteFieldRequest | null;
|
|
63
|
+
export declare function askDeleteField(r: DeleteFieldRequest | null): void;
|
|
64
|
+
/** Close every linked record (one was opened where it lives). */
|
|
65
|
+
export declare function closeAllLinkedRecords(): void;
|
|
66
|
+
export type LinkPreviewRequest = LinkedRecordRequest & {
|
|
67
|
+
x: number;
|
|
68
|
+
y: number;
|
|
69
|
+
};
|
|
70
|
+
export declare const getLinkPreview: () => LinkPreviewRequest | null;
|
|
71
|
+
export declare function showLinkPreview(r: LinkPreviewRequest | null): void;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which grid's field editor is open, and for what. The column-menu actions
|
|
3
|
+
* (run outside any component) set it; FieldsHeaderEnd, mounted in each grid's
|
|
4
|
+
* header row, shows the editor when the request names its own grid.
|
|
5
|
+
*/
|
|
6
|
+
import { gridOf } from './grid-access.js';
|
|
7
|
+
let request = $state.raw(null);
|
|
8
|
+
export const getFieldEditorRequest = () => request;
|
|
9
|
+
export function openFieldEditor(r) {
|
|
10
|
+
request = r;
|
|
11
|
+
}
|
|
12
|
+
export function closeFieldEditor() {
|
|
13
|
+
request = null;
|
|
14
|
+
}
|
|
15
|
+
let record = $state.raw(null);
|
|
16
|
+
/** The grid behind a controller or an api. */
|
|
17
|
+
const ctrlOf = (grid) => gridOf(grid) ?? grid;
|
|
18
|
+
export const getRecordRequest = () => record;
|
|
19
|
+
export function openRecord(r) {
|
|
20
|
+
const { api, ...rest } = r;
|
|
21
|
+
record = { ...rest, ctrl: ctrlOf(r.ctrl ?? api) };
|
|
22
|
+
}
|
|
23
|
+
/** Close the open record; given a grid (controller or api), only when it is open on that grid. */
|
|
24
|
+
export function closeRecord(grid) {
|
|
25
|
+
if (grid !== undefined && record && record.ctrl !== ctrlOf(grid))
|
|
26
|
+
return;
|
|
27
|
+
record = null;
|
|
28
|
+
}
|
|
29
|
+
let linked = $state.raw([]);
|
|
30
|
+
export const getLinkedRecords = () => linked;
|
|
31
|
+
export function openLinkedRecord(r) {
|
|
32
|
+
linked = [...linked, r];
|
|
33
|
+
linkPreview = null;
|
|
34
|
+
}
|
|
35
|
+
export function closeLinkedRecord() {
|
|
36
|
+
linked = linked.slice(0, -1);
|
|
37
|
+
}
|
|
38
|
+
let deleting = $state.raw(null);
|
|
39
|
+
export const getDeleteFieldRequest = () => deleting;
|
|
40
|
+
export function askDeleteField(r) {
|
|
41
|
+
deleting = r;
|
|
42
|
+
}
|
|
43
|
+
/** Close every linked record (one was opened where it lives). */
|
|
44
|
+
export function closeAllLinkedRecords() {
|
|
45
|
+
linked = [];
|
|
46
|
+
linkPreview = null;
|
|
47
|
+
}
|
|
48
|
+
let linkPreview = $state.raw(null);
|
|
49
|
+
export const getLinkPreview = () => linkPreview;
|
|
50
|
+
export function showLinkPreview(r) {
|
|
51
|
+
linkPreview = r;
|
|
52
|
+
}
|