@oneflowui/ui 0.4.3 → 0.5.0
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.en.md +1 -1
- package/README.md +70 -0
- package/dist/assets/tableWorker-CTsbCPPP.js +1 -0
- package/dist/components/ContextMenu/index.vue.d.ts +2 -1
- package/dist/components/ContextMenu/index.vue.js +2 -2
- package/dist/components/ContextMenu/index.vue2.js +37 -36
- package/dist/components/base/DropdownMenu.vue.js +2 -2
- package/dist/components/base/DropdownMenu.vue2.js +38 -39
- package/dist/components/base/MonitorItem.vue.d.ts +1 -1
- package/dist/components/base/PersonaCard.vue.d.ts +2 -1
- package/dist/components/base/PersonaCard.vue.js +3 -3
- package/dist/components/base/PersonaCard.vue2.js +51 -39
- package/dist/components/base/RefTag.vue.d.ts +2 -2
- package/dist/components/base/RefTag.vue.js +3 -3
- package/dist/components/base/SearchHighlight.vue.d.ts +6 -0
- package/dist/components/base/SearchHighlight.vue.js +7 -0
- package/dist/components/base/SearchHighlight.vue2.js +21 -0
- package/dist/components/base/ToolbarBtn.vue.d.ts +2 -1
- package/dist/components/base/ToolbarBtn.vue.js +1 -1
- package/dist/components/base/ViewModeGroup.vue.d.ts +2 -1
- package/dist/components/base/ViewModeGroup.vue.js +3 -3
- package/dist/components/base/ViewSwitcher.vue.d.ts +2 -1
- package/dist/components/base/ViewSwitcher.vue.js +2 -2
- package/dist/components/base/index.d.ts +1 -0
- package/dist/components/database/DatabaseView.vue.d.ts +171 -0
- package/dist/components/database/DatabaseView.vue.js +7 -0
- package/dist/components/database/DatabaseView.vue2.js +774 -0
- package/dist/components/database/index.d.ts +2 -0
- package/dist/components/field/FieldAttachment.vue.d.ts +17 -0
- package/dist/components/field/FieldAttachment.vue.js +7 -0
- package/dist/components/field/FieldAttachment.vue2.js +69 -0
- package/dist/components/field/FieldAutoNumber.vue.d.ts +7 -0
- package/dist/components/field/FieldAutoNumber.vue.js +7 -0
- package/dist/components/field/FieldAutoNumber.vue2.js +15 -0
- package/dist/components/field/FieldCreator.vue.d.ts +7 -0
- package/dist/components/field/FieldCreator.vue.js +7 -0
- package/dist/components/field/FieldCreator.vue2.js +24 -0
- package/dist/components/field/FieldCurrency.vue.d.ts +17 -0
- package/dist/components/field/FieldCurrency.vue.js +7 -0
- package/dist/components/field/FieldCurrency.vue2.js +42 -0
- package/dist/components/field/FieldDate.vue.js +2 -2
- package/dist/components/field/FieldDate.vue2.js +13 -10
- package/dist/components/field/FieldDatetime.vue.js +1 -1
- package/dist/components/field/FieldMarkdownPreview.vue.d.ts +13 -0
- package/dist/components/field/FieldMarkdownPreview.vue.js +7 -0
- package/dist/components/field/FieldMarkdownPreview.vue2.js +37 -0
- package/dist/components/field/FieldMultiSelect.vue.js +2 -2
- package/dist/components/field/FieldPhone.vue.d.ts +17 -0
- package/dist/components/field/FieldPhone.vue.js +7 -0
- package/dist/components/field/FieldPhone.vue2.js +34 -0
- package/dist/components/field/FieldProgress.vue.d.ts +15 -0
- package/dist/components/field/FieldProgress.vue.js +7 -0
- package/dist/components/field/FieldProgress.vue2.js +40 -0
- package/dist/components/field/FieldRelation.vue.d.ts +17 -0
- package/dist/components/field/FieldRelation.vue.js +7 -0
- package/dist/components/field/FieldRelation.vue2.js +67 -0
- package/dist/components/field/FieldRichText.vue.d.ts +17 -0
- package/dist/components/field/FieldRichText.vue.js +7 -0
- package/dist/components/field/FieldRichText.vue2.js +65 -0
- package/dist/components/field/FieldSelect.vue.js +1 -1
- package/dist/components/field/FieldSelect.vue2.js +43 -42
- package/dist/components/form/FormDesigner.vue.js +2 -2
- package/dist/components/form/FormDesigner.vue2.js +62 -52
- package/dist/components/gallery/GalleryCard.vue.js +2 -2
- package/dist/components/gallery/GalleryView.vue.d.ts +6 -2
- package/dist/components/gallery/GalleryView.vue.js +2 -2
- package/dist/components/gallery/GalleryView.vue2.js +30 -20
- package/dist/components/kanban/KanbanBoard.vue.d.ts +5 -1
- package/dist/components/kanban/KanbanBoard.vue.js +4 -4
- package/dist/components/kanban/KanbanBoard.vue2.js +81 -48
- package/dist/components/layout/AppLayout.vue.js +2 -2
- package/dist/components/layout/AppLayout.vue2.js +46 -62
- package/dist/components/overlay/Drawer.vue.js +1 -1
- package/dist/components/overlay/Drawer.vue2.js +52 -68
- package/dist/components/overlay/Modal.vue.js +1 -1
- package/dist/components/overlay/Modal.vue2.js +52 -68
- package/dist/components/overlay/SidePanel.vue.js +2 -2
- package/dist/components/overlay/SidePanel.vue2.js +64 -80
- package/dist/components/table/ColumnHeaderMenu.vue.d.ts +33 -0
- package/dist/components/table/ColumnHeaderMenu.vue.js +7 -0
- package/dist/components/table/ColumnHeaderMenu.vue2.js +153 -0
- package/dist/components/table/DataTable.vue.d.ts +116 -25
- package/dist/components/table/DataTable.vue.js +4 -4
- package/dist/components/table/DataTable.vue2.js +775 -188
- package/dist/components/table/DetailSheet.vue.d.ts +43 -0
- package/dist/components/table/DetailSheet.vue.js +7 -0
- package/dist/components/table/DetailSheet.vue2.js +140 -0
- package/dist/components/table/FieldCell.vue.d.ts +1 -1
- package/dist/components/table/FieldCell.vue.js +1 -1
- package/dist/components/table/FieldCell.vue2.js +59 -44
- package/dist/components/table/FieldTypePicker.vue.d.ts +15 -0
- package/dist/components/table/FieldTypePicker.vue.js +7 -0
- package/dist/components/table/FieldTypePicker.vue2.js +92 -0
- package/dist/components/table/MobileListView.vue.d.ts +24 -0
- package/dist/components/table/MobileListView.vue.js +7 -0
- package/dist/components/table/MobileListView.vue2.js +90 -0
- package/dist/components/table/TableGroupRow.vue.d.ts +5 -0
- package/dist/components/table/TableGroupRow.vue.js +2 -2
- package/dist/components/table/TableGroupRow.vue2.js +33 -23
- package/dist/components/table/TableHeaderRow.vue.d.ts +16 -0
- package/dist/components/table/TableHeaderRow.vue.js +2 -2
- package/dist/components/table/TableHeaderRow.vue2.js +54 -33
- package/dist/components/table/TableToolbar.vue.d.ts +118 -0
- package/dist/components/table/TableToolbar.vue.js +7 -0
- package/dist/components/table/TableToolbar.vue2.js +273 -0
- package/dist/components/table/index.d.ts +5 -0
- package/dist/components/timeline/GanttTimeline.vue.js +1 -1
- package/dist/components/timeline/GanttTimeline.vue2.js +128 -127
- package/dist/components/toast/ToastItem.vue.js +3 -3
- package/dist/composables/index.d.ts +21 -0
- package/dist/composables/useBreakpoint.d.ts +2 -1
- package/dist/composables/useBreakpoint.js +14 -12
- package/dist/composables/useColumnResize.d.ts +19 -0
- package/dist/composables/useColumnResize.js +58 -0
- package/dist/composables/useDatabaseView.d.ts +138 -0
- package/dist/composables/useDatabaseView.js +388 -0
- package/dist/composables/useDraftRows.d.ts +33 -0
- package/dist/composables/useDraftRows.js +103 -0
- package/dist/composables/useFixedColumns.d.ts +25 -0
- package/dist/composables/useFixedColumns.js +61 -0
- package/dist/composables/useFocusTrap.d.ts +10 -0
- package/dist/composables/useFocusTrap.js +37 -0
- package/dist/composables/useInlineEdit.js +3 -3
- package/dist/composables/useKeyboardNavigation.d.ts +45 -0
- package/dist/composables/useKeyboardNavigation.js +140 -0
- package/dist/composables/useRowDrag.d.ts +32 -0
- package/dist/composables/useRowDrag.js +85 -0
- package/dist/composables/useSchemaEngine.d.ts +31 -0
- package/dist/composables/useSchemaEngine.js +129 -0
- package/dist/composables/useSearch.d.ts +30 -0
- package/dist/composables/useSearch.js +59 -0
- package/dist/composables/useSupabaseProvider.d.ts +70 -0
- package/dist/composables/useSupabaseProvider.js +126 -0
- package/dist/composables/useTable.d.ts +3 -0
- package/dist/composables/useTable.js +103 -83
- package/dist/composables/useTableGroup.d.ts +14 -1
- package/dist/composables/useTableGroup.js +57 -33
- package/dist/composables/useViewPersistence.d.ts +98 -0
- package/dist/composables/useViewPersistence.js +141 -0
- package/dist/composables/useVirtualList.d.ts +4 -1
- package/dist/composables/useVirtualList.js +108 -85
- package/dist/composables/useWorkerSort.d.ts +14 -0
- package/dist/composables/useWorkerSort.js +61 -0
- package/dist/index.d.ts +32 -4
- package/dist/index.js +274 -221
- package/dist/style.css +1 -1
- package/dist/tests/database-view.integration.spec.d.ts +1 -0
- package/dist/types/index.d.ts +63 -2
- package/dist/types/index.js +23 -6
- package/dist/types/table-internal.d.ts +64 -0
- package/dist/utils/aggregation.d.ts +5 -0
- package/dist/utils/aggregation.js +38 -0
- package/dist/utils/supabaseAdapter.d.ts +48 -0
- package/dist/utils/supabaseAdapter.js +76 -0
- package/dist/utils/supabaseSchema.d.ts +81 -0
- package/dist/utils/supabaseSchema.js +202 -0
- package/dist/workers/tableWorker.d.ts +31 -0
- package/package.json +17 -17
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/types/index.d.ts
CHANGED
|
@@ -120,7 +120,7 @@ export interface SidebarItem {
|
|
|
120
120
|
badge?: string | number;
|
|
121
121
|
children?: SidebarItem[];
|
|
122
122
|
}
|
|
123
|
-
export type FieldType = "text" | "number" | "select" | "multi_select" | "date" | "datetime" | "checkbox" | "url" | "email" | "phone" | "rating" | "user" | "attachment" | "relation" | "formula";
|
|
123
|
+
export type FieldType = "text" | "number" | "select" | "multi_select" | "date" | "datetime" | "checkbox" | "url" | "email" | "phone" | "rating" | "user" | "attachment" | "relation" | "formula" | "currency" | "richtext" | "auto_number" | "creator" | "progress";
|
|
124
124
|
export interface SelectOption {
|
|
125
125
|
value: string;
|
|
126
126
|
label: string;
|
|
@@ -167,15 +167,66 @@ export interface AttachmentFieldDef extends FieldDefBase {
|
|
|
167
167
|
export interface RelationFieldDef extends FieldDefBase {
|
|
168
168
|
type: "relation";
|
|
169
169
|
targetTableId?: string;
|
|
170
|
+
/** Field ID to display from the target table (e.g. "title") */
|
|
171
|
+
displayFieldId?: string;
|
|
172
|
+
/** Filter conditions applied when selecting related records */
|
|
173
|
+
filterConfig?: FilterCondition[];
|
|
174
|
+
/** Relation cardinality */
|
|
175
|
+
relationMode?: "one_to_many" | "many_to_many";
|
|
176
|
+
/** Schema of the target table for inline rendering */
|
|
177
|
+
targetSchema?: TableSchema;
|
|
170
178
|
}
|
|
171
179
|
export interface FormulaFieldDef extends FieldDefBase {
|
|
172
180
|
type: "formula";
|
|
173
181
|
formula: string;
|
|
182
|
+
/** Type of the computed result */
|
|
183
|
+
resultType?: "string" | "number" | "date" | "boolean";
|
|
184
|
+
/** Field IDs this formula depends on (for recalculation) */
|
|
185
|
+
dependencies?: string[];
|
|
174
186
|
}
|
|
175
|
-
export
|
|
187
|
+
export interface CurrencyFieldDef extends FieldDefBase {
|
|
188
|
+
type: "currency";
|
|
189
|
+
/** ISO 4217 currency code, e.g. 'USD', 'CNY' */
|
|
190
|
+
currencyCode?: string;
|
|
191
|
+
/** Decimal precision, default 2 */
|
|
192
|
+
precision?: number;
|
|
193
|
+
}
|
|
194
|
+
export interface RichTextFieldDef extends FieldDefBase {
|
|
195
|
+
type: "richtext";
|
|
196
|
+
}
|
|
197
|
+
export interface AutoNumberFieldDef extends FieldDefBase {
|
|
198
|
+
type: "auto_number";
|
|
199
|
+
/** Prefix for the auto number, e.g. 'TASK-' */
|
|
200
|
+
prefix?: string;
|
|
201
|
+
}
|
|
202
|
+
export interface CreatorFieldDef extends FieldDefBase {
|
|
203
|
+
type: "creator";
|
|
204
|
+
}
|
|
205
|
+
export interface ProgressFieldDef extends FieldDefBase {
|
|
206
|
+
type: "progress";
|
|
207
|
+
min?: number;
|
|
208
|
+
max?: number;
|
|
209
|
+
}
|
|
210
|
+
export type FieldDef = TextFieldDef | NumberFieldDef | SelectFieldDef | DateFieldDef | CheckboxFieldDef | ContactFieldDef | RatingFieldDef | UserFieldDef | AttachmentFieldDef | RelationFieldDef | FormulaFieldDef | CurrencyFieldDef | RichTextFieldDef | AutoNumberFieldDef | CreatorFieldDef | ProgressFieldDef;
|
|
176
211
|
export declare function isSelectField(field: FieldDef): field is SelectFieldDef;
|
|
177
212
|
export declare function isFormulaField(field: FieldDef): field is FormulaFieldDef;
|
|
178
213
|
export type CellValue = string | number | boolean | string[] | null;
|
|
214
|
+
export interface ActiveCell {
|
|
215
|
+
rowId: string;
|
|
216
|
+
colKey: string;
|
|
217
|
+
}
|
|
218
|
+
export type AggregationFn = "sum" | "avg" | "count" | "min" | "max";
|
|
219
|
+
export interface AggregationConfig {
|
|
220
|
+
fieldId: string;
|
|
221
|
+
fn: AggregationFn;
|
|
222
|
+
}
|
|
223
|
+
export interface DraftRowState {
|
|
224
|
+
draftId: string;
|
|
225
|
+
fields: Record<string, CellValue>;
|
|
226
|
+
dirtyFields: Set<string>;
|
|
227
|
+
validationErrors: Map<string, string>;
|
|
228
|
+
groupFieldValue?: CellValue;
|
|
229
|
+
}
|
|
179
230
|
export interface DataRecord {
|
|
180
231
|
id: string;
|
|
181
232
|
fields: Record<string, CellValue>;
|
|
@@ -205,6 +256,10 @@ export interface ViewConfig {
|
|
|
205
256
|
kanbanFieldId?: string;
|
|
206
257
|
galleryCoverFieldId?: string;
|
|
207
258
|
galleryCardFields?: string[];
|
|
259
|
+
/** Aggregation configs shown in group headers/footers */
|
|
260
|
+
aggregations?: AggregationConfig[];
|
|
261
|
+
/** Column keys to pin on the left side */
|
|
262
|
+
fixedColumns?: string[];
|
|
208
263
|
}
|
|
209
264
|
export interface TableSchema {
|
|
210
265
|
tableId: string;
|
|
@@ -223,4 +278,10 @@ export declare function buildGalleryItems(records: DataRecord[], options?: {
|
|
|
223
278
|
coverFieldId?: string;
|
|
224
279
|
cardFieldIds?: string[];
|
|
225
280
|
}): GalleryItem[];
|
|
281
|
+
export declare function buildGanttItems(records: DataRecord[], options?: {
|
|
282
|
+
startFieldId?: string;
|
|
283
|
+
endFieldId?: string;
|
|
284
|
+
labelFieldId?: string;
|
|
285
|
+
barColorFieldId?: string;
|
|
286
|
+
}): GanttItem[];
|
|
226
287
|
export declare const DEFAULT_TABLE_SCHEMA: TableSchema;
|
package/dist/types/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
function f(t) {
|
|
2
2
|
return t.type === "select" || t.type === "multi_select";
|
|
3
3
|
}
|
|
4
|
-
function
|
|
4
|
+
function g(t) {
|
|
5
5
|
return t.type === "formula";
|
|
6
6
|
}
|
|
7
|
-
function
|
|
7
|
+
function p(t) {
|
|
8
8
|
return {
|
|
9
9
|
id: t.id,
|
|
10
10
|
fields: {
|
|
@@ -52,7 +52,7 @@ function y(t, s = {}) {
|
|
|
52
52
|
};
|
|
53
53
|
});
|
|
54
54
|
}
|
|
55
|
-
function
|
|
55
|
+
function D(t, s = {}) {
|
|
56
56
|
const r = s.coverFieldId, a = s.cardFieldIds ?? [];
|
|
57
57
|
return t.map((o) => {
|
|
58
58
|
const n = c(o), e = r ? o.fields[r] : null, i = typeof e == "string" && e ? e : void 0, l = a.map((d) => {
|
|
@@ -67,11 +67,28 @@ function v(t, s = {}) {
|
|
|
67
67
|
};
|
|
68
68
|
});
|
|
69
69
|
}
|
|
70
|
+
function F(t, s = {}) {
|
|
71
|
+
const r = s.startFieldId ?? "startDate", a = s.endFieldId ?? "endDate", o = s.labelFieldId ?? "title", n = [];
|
|
72
|
+
for (const e of t) {
|
|
73
|
+
const i = c(e), l = e.fields[r], d = e.fields[a];
|
|
74
|
+
if (!l || !d) continue;
|
|
75
|
+
const u = e.fields[o];
|
|
76
|
+
n.push({
|
|
77
|
+
...i,
|
|
78
|
+
title: typeof u == "string" && u ? u : i.title,
|
|
79
|
+
startDate: String(l),
|
|
80
|
+
endDate: String(d),
|
|
81
|
+
barColor: s.barColorFieldId ? String(e.fields[s.barColorFieldId] ?? "") : void 0
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return n;
|
|
85
|
+
}
|
|
70
86
|
export {
|
|
71
|
-
|
|
87
|
+
D as buildGalleryItems,
|
|
88
|
+
F as buildGanttItems,
|
|
72
89
|
y as buildKanbanColumns,
|
|
73
90
|
c as dataRecordToTask,
|
|
74
|
-
|
|
91
|
+
g as isFormulaField,
|
|
75
92
|
f as isSelectField,
|
|
76
|
-
|
|
93
|
+
p as taskToDataRecord
|
|
77
94
|
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { CellValue, AggregationFn } from './index';
|
|
2
|
+
export interface ColumnResizeState {
|
|
3
|
+
colKey: string;
|
|
4
|
+
startX: number;
|
|
5
|
+
initialWidth: number;
|
|
6
|
+
}
|
|
7
|
+
export interface FixedColumnLayout {
|
|
8
|
+
/** Total width of all fixed columns including selector checkbox (40px) */
|
|
9
|
+
totalWidth: number;
|
|
10
|
+
/** Left offset for each fixed column, keyed by column key */
|
|
11
|
+
offsets: Map<string, number>;
|
|
12
|
+
}
|
|
13
|
+
export interface GroupAggregationResult {
|
|
14
|
+
/** Aggregation function used */
|
|
15
|
+
fn: AggregationFn;
|
|
16
|
+
/** Field ID the aggregation is computed on */
|
|
17
|
+
fieldId: string;
|
|
18
|
+
/** Computed result value */
|
|
19
|
+
value: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Replaces the previous __type string marker pattern.
|
|
23
|
+
* Used in virtual scroll and rendering pipeline.
|
|
24
|
+
*/
|
|
25
|
+
export type FlattenedRow<T> = FlattenedDataRow<T> | FlattenedGroupHeader | FlattenedDraftRow;
|
|
26
|
+
export interface FlattenedDataRow<T> {
|
|
27
|
+
readonly __type: "data-row";
|
|
28
|
+
readonly id: string;
|
|
29
|
+
readonly data: T;
|
|
30
|
+
}
|
|
31
|
+
export interface FlattenedGroupHeader {
|
|
32
|
+
readonly __type: "group-header";
|
|
33
|
+
readonly id: string;
|
|
34
|
+
readonly groupKey: string;
|
|
35
|
+
/** Full path for nested groups, e.g. ['status:done', 'priority:P0'] */
|
|
36
|
+
readonly groupPath: string[];
|
|
37
|
+
readonly groupLevel: number;
|
|
38
|
+
readonly groupCount: number;
|
|
39
|
+
readonly aggregations?: GroupAggregationResult[];
|
|
40
|
+
}
|
|
41
|
+
export interface FlattenedDraftRow {
|
|
42
|
+
readonly __type: "draft-row";
|
|
43
|
+
readonly id: string;
|
|
44
|
+
readonly draftId: string;
|
|
45
|
+
readonly fields: Record<string, CellValue>;
|
|
46
|
+
readonly dirtyFields: ReadonlySet<string>;
|
|
47
|
+
readonly validationErrors: ReadonlyMap<string, string>;
|
|
48
|
+
}
|
|
49
|
+
export declare function isDataRow<T>(row: FlattenedRow<T>): row is FlattenedDataRow<T>;
|
|
50
|
+
export declare function isGroupHeader<T>(row: FlattenedRow<T>): row is FlattenedGroupHeader;
|
|
51
|
+
export declare function isDraftRow<T>(row: FlattenedRow<T>): row is FlattenedDraftRow;
|
|
52
|
+
export interface RowHeightConfig {
|
|
53
|
+
dataRow: number;
|
|
54
|
+
groupHeader: number;
|
|
55
|
+
draftRow: number;
|
|
56
|
+
groupSpacing: number;
|
|
57
|
+
}
|
|
58
|
+
export interface CellCoordinate {
|
|
59
|
+
rowIndex: number;
|
|
60
|
+
colIndex: number;
|
|
61
|
+
}
|
|
62
|
+
export interface HoverState {
|
|
63
|
+
rowId: string | null;
|
|
64
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
function f(u, s) {
|
|
2
|
+
const r = {};
|
|
3
|
+
for (const e of s) {
|
|
4
|
+
const t = `${e.fieldId}:${e.fn}`;
|
|
5
|
+
r[t] = c(u, e.fieldId, e.fn);
|
|
6
|
+
}
|
|
7
|
+
return r;
|
|
8
|
+
}
|
|
9
|
+
function c(u, s, r) {
|
|
10
|
+
if (r === "count")
|
|
11
|
+
return u.length;
|
|
12
|
+
const e = [];
|
|
13
|
+
for (const t of u) {
|
|
14
|
+
const n = t[s];
|
|
15
|
+
if (n != null && typeof n == "number" && !Number.isNaN(n))
|
|
16
|
+
e.push(n);
|
|
17
|
+
else if (n != null && typeof n == "string") {
|
|
18
|
+
const o = Number(n);
|
|
19
|
+
Number.isNaN(o) || e.push(o);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (e.length === 0) return 0;
|
|
23
|
+
switch (r) {
|
|
24
|
+
case "sum":
|
|
25
|
+
return e.reduce((t, n) => t + n, 0);
|
|
26
|
+
case "avg":
|
|
27
|
+
return e.reduce((t, n) => t + n, 0) / e.length;
|
|
28
|
+
case "min":
|
|
29
|
+
return Math.min(...e);
|
|
30
|
+
case "max":
|
|
31
|
+
return Math.max(...e);
|
|
32
|
+
default:
|
|
33
|
+
return 0;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export {
|
|
37
|
+
f as computeAggregations
|
|
38
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Ref } from 'vue';
|
|
2
|
+
import { DataRecord, CellValue, TableSchema } from '../types';
|
|
3
|
+
export interface RowAdapterOptions {
|
|
4
|
+
/** 主键列名,默认 'id' */
|
|
5
|
+
idColumn?: string;
|
|
6
|
+
/** created_at 列名 */
|
|
7
|
+
createdAtColumn?: string;
|
|
8
|
+
/** updated_at 列名 */
|
|
9
|
+
updatedAtColumn?: string;
|
|
10
|
+
/** 需要从 fields 中排除的列(如主键、时间戳),自动包含 id/created_at/updated_at */
|
|
11
|
+
excludeFromFields?: string[];
|
|
12
|
+
/** Schema 定义(如有),用于类型转换 */
|
|
13
|
+
schema?: TableSchema;
|
|
14
|
+
}
|
|
15
|
+
export interface UseSupabaseAdapterOptions extends RowAdapterOptions {
|
|
16
|
+
/** 响应式的原始 Supabase 行数据 */
|
|
17
|
+
rawRows?: Ref<Record<string, unknown>[]>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* 将 Supabase 的扁平行转换为 DataRecord
|
|
21
|
+
*/
|
|
22
|
+
export declare function rowToDataRecord(row: Record<string, unknown>, options?: RowAdapterOptions): DataRecord;
|
|
23
|
+
/**
|
|
24
|
+
* 批量转换
|
|
25
|
+
*/
|
|
26
|
+
export declare function rowsToDataRecords(rows: Record<string, unknown>[], options?: RowAdapterOptions): DataRecord[];
|
|
27
|
+
/**
|
|
28
|
+
* 将 DataRecord 转换回 Supabase 扁平行格式(用于 insert/update)
|
|
29
|
+
*/
|
|
30
|
+
export declare function dataRecordToRow(record: DataRecord, options?: RowAdapterOptions): Record<string, unknown>;
|
|
31
|
+
/**
|
|
32
|
+
* 部分更新:只提取变更的字段,转换为 Supabase 扁平行
|
|
33
|
+
*/
|
|
34
|
+
export declare function fieldsToRow(fields: Partial<Record<string, CellValue>>): Record<string, unknown>;
|
|
35
|
+
/**
|
|
36
|
+
* 响应式适配器:自动将 Supabase 行数据转换为 DataRecord[]
|
|
37
|
+
*/
|
|
38
|
+
export declare function useSupabaseAdapter(options?: UseSupabaseAdapterOptions): {
|
|
39
|
+
records: import('vue').ComputedRef<DataRecord[]>;
|
|
40
|
+
/** 单行转换 */
|
|
41
|
+
toRecord: (row: Record<string, unknown>) => DataRecord;
|
|
42
|
+
/** 批量转换 */
|
|
43
|
+
toRecords: (rows: Record<string, unknown>[]) => DataRecord[];
|
|
44
|
+
/** 回写转换 */
|
|
45
|
+
toRow: (record: DataRecord) => Record<string, unknown>;
|
|
46
|
+
/** 部分字段回写 */
|
|
47
|
+
fieldsToRow: typeof fieldsToRow;
|
|
48
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { computed as p } from "vue";
|
|
2
|
+
function s(t, n = {}) {
|
|
3
|
+
var f;
|
|
4
|
+
const {
|
|
5
|
+
idColumn: e = "id",
|
|
6
|
+
createdAtColumn: r = "created_at",
|
|
7
|
+
updatedAtColumn: d = "updated_at",
|
|
8
|
+
excludeFromFields: o = []
|
|
9
|
+
} = n, c = /* @__PURE__ */ new Set([e, r, d, ...o]), i = {};
|
|
10
|
+
for (const [u, l] of Object.entries(t))
|
|
11
|
+
c.has(u) || (i[u] = R(
|
|
12
|
+
l,
|
|
13
|
+
(f = n.schema) == null ? void 0 : f.fields.find((m) => m.id === u)
|
|
14
|
+
));
|
|
15
|
+
return {
|
|
16
|
+
id: String(t[e] ?? ""),
|
|
17
|
+
fields: i,
|
|
18
|
+
createdAt: t[r] != null ? String(t[r]) : void 0,
|
|
19
|
+
updatedAt: t[d] != null ? String(t[d]) : void 0
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function a(t, n = {}) {
|
|
23
|
+
return t.map((e) => s(e, n));
|
|
24
|
+
}
|
|
25
|
+
function A(t, n = {}) {
|
|
26
|
+
const {
|
|
27
|
+
idColumn: e = "id",
|
|
28
|
+
createdAtColumn: r = "created_at",
|
|
29
|
+
updatedAtColumn: d = "updated_at"
|
|
30
|
+
} = n, o = {
|
|
31
|
+
[e]: t.id
|
|
32
|
+
};
|
|
33
|
+
for (const [c, i] of Object.entries(t.fields))
|
|
34
|
+
o[c] = i;
|
|
35
|
+
return t.createdAt && (o[r] = t.createdAt), t.updatedAt && (o[d] = t.updatedAt), o;
|
|
36
|
+
}
|
|
37
|
+
function y(t) {
|
|
38
|
+
const n = {};
|
|
39
|
+
for (const [e, r] of Object.entries(t))
|
|
40
|
+
n[e] = r;
|
|
41
|
+
return n;
|
|
42
|
+
}
|
|
43
|
+
function b(t = {}) {
|
|
44
|
+
return {
|
|
45
|
+
records: p(() => {
|
|
46
|
+
var e;
|
|
47
|
+
return (e = t.rawRows) != null && e.value ? a(t.rawRows.value, t) : [];
|
|
48
|
+
}),
|
|
49
|
+
/** 单行转换 */
|
|
50
|
+
toRecord: (e) => s(e, t),
|
|
51
|
+
/** 批量转换 */
|
|
52
|
+
toRecords: (e) => a(e, t),
|
|
53
|
+
/** 回写转换 */
|
|
54
|
+
toRow: (e) => A(e, t),
|
|
55
|
+
/** 部分字段回写 */
|
|
56
|
+
fieldsToRow: y
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function R(t, n) {
|
|
60
|
+
if (t == null) return null;
|
|
61
|
+
if (Array.isArray(t))
|
|
62
|
+
return t.map(String);
|
|
63
|
+
if (typeof t == "boolean" || typeof t == "number" || typeof t == "string") return t;
|
|
64
|
+
if (typeof t == "object") {
|
|
65
|
+
const e = t;
|
|
66
|
+
return "content" in e && typeof e.content == "string" ? e.content : JSON.stringify(t);
|
|
67
|
+
}
|
|
68
|
+
return String(t);
|
|
69
|
+
}
|
|
70
|
+
export {
|
|
71
|
+
A as dataRecordToRow,
|
|
72
|
+
y as fieldsToRow,
|
|
73
|
+
s as rowToDataRecord,
|
|
74
|
+
a as rowsToDataRecords,
|
|
75
|
+
b as useSupabaseAdapter
|
|
76
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { FieldDef, TableSchema, SelectOption } from '../types';
|
|
2
|
+
/** Supabase / PostgreSQL column metadata (from information_schema.columns) */
|
|
3
|
+
export interface PgColumnInfo {
|
|
4
|
+
column_name: string;
|
|
5
|
+
data_type: string;
|
|
6
|
+
udt_name: string;
|
|
7
|
+
is_nullable: "YES" | "NO";
|
|
8
|
+
column_default: string | null;
|
|
9
|
+
character_maximum_length: number | null;
|
|
10
|
+
is_identity: "YES" | "NO";
|
|
11
|
+
identity_generation: string | null;
|
|
12
|
+
}
|
|
13
|
+
export interface InferSchemaOptions {
|
|
14
|
+
/** Table name */
|
|
15
|
+
tableName: string;
|
|
16
|
+
/** Column info rows from information_schema.columns */
|
|
17
|
+
columns: PgColumnInfo[];
|
|
18
|
+
/** Manual per-column overrides (merged last) */
|
|
19
|
+
overrides?: Record<string, Partial<FieldDef>>;
|
|
20
|
+
/** Column names to skip */
|
|
21
|
+
skipColumns?: string[];
|
|
22
|
+
/** column_name → display label mapping; use key '__table__' for the table display name */
|
|
23
|
+
labelMap?: Record<string, string>;
|
|
24
|
+
/** Include the id column (skipped by default) */
|
|
25
|
+
includeIdColumn?: boolean;
|
|
26
|
+
/** Use column-name heuristics for content fields (default true) */
|
|
27
|
+
inferContentFields?: boolean;
|
|
28
|
+
/** Pre-defined select options keyed by column name */
|
|
29
|
+
selectOptions?: Record<string, SelectOption[]>;
|
|
30
|
+
}
|
|
31
|
+
export declare function inferSchema(options: InferSchemaOptions): TableSchema;
|
|
32
|
+
/** Minimal Supabase client interface (avoids hard dependency on @supabase/supabase-js) */
|
|
33
|
+
interface SupabaseClientLike {
|
|
34
|
+
from(table: string): {
|
|
35
|
+
select(columns: string): {
|
|
36
|
+
eq(column: string, value: string): {
|
|
37
|
+
eq(column: string, value: string): Promise<{
|
|
38
|
+
data: PgColumnInfo[] | null;
|
|
39
|
+
error: {
|
|
40
|
+
message: string;
|
|
41
|
+
} | null;
|
|
42
|
+
}>;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
rpc?(fn: string, params?: Record<string, unknown>): Promise<{
|
|
47
|
+
data: unknown;
|
|
48
|
+
error: {
|
|
49
|
+
message: string;
|
|
50
|
+
} | null;
|
|
51
|
+
}>;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Query table schema from Supabase and produce a TableSchema.
|
|
55
|
+
*
|
|
56
|
+
* Strategy (in order of preference):
|
|
57
|
+
* 1. RPC `get_table_columns(p_table_name)` — requires a custom Postgres function
|
|
58
|
+
* 2. Direct `information_schema.columns` query — works only if PostgREST exposes it
|
|
59
|
+
* 3. Falls back to inferring from a sample row's keys (limited type info)
|
|
60
|
+
*
|
|
61
|
+
* To use strategy 1, create this function in your Supabase SQL editor:
|
|
62
|
+
* ```sql
|
|
63
|
+
* CREATE OR REPLACE FUNCTION get_table_columns(p_table_name text)
|
|
64
|
+
* RETURNS TABLE(
|
|
65
|
+
* column_name text, data_type text, udt_name text,
|
|
66
|
+
* is_nullable text, column_default text,
|
|
67
|
+
* character_maximum_length int, is_identity text, identity_generation text
|
|
68
|
+
* ) LANGUAGE sql SECURITY DEFINER AS $$
|
|
69
|
+
* SELECT column_name::text, data_type::text, udt_name::text,
|
|
70
|
+
* is_nullable::text, column_default::text,
|
|
71
|
+
* character_maximum_length::int, is_identity::text, identity_generation::text
|
|
72
|
+
* FROM information_schema.columns
|
|
73
|
+
* WHERE table_schema = 'public' AND table_name = p_table_name
|
|
74
|
+
* ORDER BY ordinal_position;
|
|
75
|
+
* $$;
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export declare function fetchSchemaFromSupabase(client: SupabaseClientLike, tableName: string, options?: Omit<InferSchemaOptions, "tableName" | "columns">): Promise<TableSchema>;
|
|
79
|
+
/** snake_case → Human Readable Name */
|
|
80
|
+
export declare function humanizeColumnName(name: string): string;
|
|
81
|
+
export {};
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
const l = {
|
|
2
|
+
// Text
|
|
3
|
+
text: "text",
|
|
4
|
+
varchar: "text",
|
|
5
|
+
char: "text",
|
|
6
|
+
"character varying": "text",
|
|
7
|
+
character: "text",
|
|
8
|
+
name: "text",
|
|
9
|
+
citext: "text",
|
|
10
|
+
// Numeric
|
|
11
|
+
int2: "number",
|
|
12
|
+
int4: "number",
|
|
13
|
+
int8: "number",
|
|
14
|
+
float4: "number",
|
|
15
|
+
float8: "number",
|
|
16
|
+
numeric: "number",
|
|
17
|
+
decimal: "number",
|
|
18
|
+
real: "number",
|
|
19
|
+
"double precision": "number",
|
|
20
|
+
integer: "number",
|
|
21
|
+
bigint: "number",
|
|
22
|
+
smallint: "number",
|
|
23
|
+
// Boolean
|
|
24
|
+
bool: "checkbox",
|
|
25
|
+
boolean: "checkbox",
|
|
26
|
+
// Date / Time
|
|
27
|
+
date: "date",
|
|
28
|
+
timestamp: "datetime",
|
|
29
|
+
timestamptz: "datetime",
|
|
30
|
+
"timestamp with time zone": "datetime",
|
|
31
|
+
"timestamp without time zone": "datetime",
|
|
32
|
+
// JSON → richtext (markdown / structured content)
|
|
33
|
+
json: "richtext",
|
|
34
|
+
jsonb: "richtext",
|
|
35
|
+
// UUID
|
|
36
|
+
uuid: "text",
|
|
37
|
+
// Array types
|
|
38
|
+
_text: "multi_select",
|
|
39
|
+
// text[]
|
|
40
|
+
_varchar: "multi_select",
|
|
41
|
+
// varchar[]
|
|
42
|
+
_int4: "multi_select",
|
|
43
|
+
// int[]
|
|
44
|
+
_uuid: "relation"
|
|
45
|
+
// uuid[] → relation
|
|
46
|
+
};
|
|
47
|
+
function x(n, r) {
|
|
48
|
+
const e = n.toLowerCase();
|
|
49
|
+
return e === "url" || e === "link" || e === "website" || e.endsWith("_url") || e.endsWith("_link") ? "url" : e === "email" || e.endsWith("_email") ? "email" : e === "phone" || e === "tel" || e === "mobile" || e.endsWith("_phone") ? "phone" : e === "progress" || e === "completion" || e === "percent" ? "progress" : e === "rating" || e === "score" || e === "stars" ? "rating" : e === "created_by" || e === "creator" || e === "author" || e === "updated_by" || e === "modifier" ? "creator" : (e === "price" || e === "amount" || e === "cost" || e === "total" || e.endsWith("_amount") || e.endsWith("_price")) && (r === "numeric" || r === "decimal" || r === "float8" || r === "money") ? "currency" : (e === "content" || e === "body" || e === "description" || e === "summary" || e === "notes") && (r === "text" || r === "jsonb") ? "richtext" : null;
|
|
50
|
+
}
|
|
51
|
+
function p(n) {
|
|
52
|
+
const {
|
|
53
|
+
tableName: r,
|
|
54
|
+
columns: e,
|
|
55
|
+
overrides: t = {},
|
|
56
|
+
skipColumns: i = [],
|
|
57
|
+
labelMap: u = {},
|
|
58
|
+
includeIdColumn: d = !1,
|
|
59
|
+
inferContentFields: _ = !0,
|
|
60
|
+
selectOptions: c = {}
|
|
61
|
+
} = n, h = /* @__PURE__ */ new Set(["id", "created_at", "updated_at", ...i]);
|
|
62
|
+
d && h.delete("id");
|
|
63
|
+
const m = [];
|
|
64
|
+
for (const a of e) {
|
|
65
|
+
if (h.has(a.column_name)) continue;
|
|
66
|
+
if (a.is_identity === "YES" || a.column_default && a.column_default.includes("nextval")) {
|
|
67
|
+
m.push({
|
|
68
|
+
id: a.column_name,
|
|
69
|
+
name: u[a.column_name] ?? s(a.column_name),
|
|
70
|
+
type: "auto_number",
|
|
71
|
+
prefix: ""
|
|
72
|
+
});
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (c[a.column_name]) {
|
|
76
|
+
m.push({
|
|
77
|
+
id: a.column_name,
|
|
78
|
+
name: u[a.column_name] ?? s(a.column_name),
|
|
79
|
+
type: "select",
|
|
80
|
+
options: c[a.column_name],
|
|
81
|
+
required: a.is_nullable === "NO"
|
|
82
|
+
});
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
let o = "text";
|
|
86
|
+
if (_) {
|
|
87
|
+
const b = x(a.column_name, a.udt_name);
|
|
88
|
+
b ? o = b : o = l[a.udt_name] ?? l[a.data_type] ?? "text";
|
|
89
|
+
} else
|
|
90
|
+
o = l[a.udt_name] ?? l[a.data_type] ?? "text";
|
|
91
|
+
let f = g(a, o, u);
|
|
92
|
+
t[a.column_name] && (f = { ...f, ...t[a.column_name] }), m.push(f);
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
tableId: r,
|
|
96
|
+
name: u.__table__ ?? s(r),
|
|
97
|
+
fields: m,
|
|
98
|
+
views: []
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function g(n, r, e) {
|
|
102
|
+
const t = {
|
|
103
|
+
id: n.column_name,
|
|
104
|
+
name: e[n.column_name] ?? s(n.column_name),
|
|
105
|
+
required: n.is_nullable === "NO"
|
|
106
|
+
};
|
|
107
|
+
switch (r) {
|
|
108
|
+
case "number":
|
|
109
|
+
return { ...t, type: "number" };
|
|
110
|
+
case "currency":
|
|
111
|
+
return { ...t, type: "currency", currencyCode: "CNY", precision: 2 };
|
|
112
|
+
case "progress":
|
|
113
|
+
return { ...t, type: "progress", min: 0, max: 100 };
|
|
114
|
+
case "rating":
|
|
115
|
+
return { ...t, type: "rating", max: 5 };
|
|
116
|
+
case "checkbox":
|
|
117
|
+
return { ...t, type: "checkbox" };
|
|
118
|
+
case "date":
|
|
119
|
+
return { ...t, type: "date" };
|
|
120
|
+
case "datetime":
|
|
121
|
+
return { ...t, type: "datetime" };
|
|
122
|
+
case "richtext":
|
|
123
|
+
return { ...t, type: "richtext" };
|
|
124
|
+
case "url":
|
|
125
|
+
return { ...t, type: "url" };
|
|
126
|
+
case "email":
|
|
127
|
+
return { ...t, type: "email" };
|
|
128
|
+
case "phone":
|
|
129
|
+
return { ...t, type: "phone" };
|
|
130
|
+
case "creator":
|
|
131
|
+
return { ...t, type: "creator" };
|
|
132
|
+
case "auto_number":
|
|
133
|
+
return { ...t, type: "auto_number", prefix: "" };
|
|
134
|
+
case "select":
|
|
135
|
+
case "multi_select":
|
|
136
|
+
return { ...t, type: r, options: [] };
|
|
137
|
+
case "relation":
|
|
138
|
+
return { ...t, type: "relation" };
|
|
139
|
+
case "attachment":
|
|
140
|
+
return { ...t, type: "attachment" };
|
|
141
|
+
default:
|
|
142
|
+
return { ...t, type: "text" };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
async function w(n, r, e) {
|
|
146
|
+
if (n.rpc)
|
|
147
|
+
try {
|
|
148
|
+
const { data: t, error: i } = await n.rpc("get_table_columns", {
|
|
149
|
+
p_table_name: r
|
|
150
|
+
});
|
|
151
|
+
if (!i && Array.isArray(t) && t.length > 0)
|
|
152
|
+
return p({
|
|
153
|
+
tableName: r,
|
|
154
|
+
columns: t,
|
|
155
|
+
...e
|
|
156
|
+
});
|
|
157
|
+
} catch {
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
const { data: t, error: i } = await n.from("information_schema.columns").select(
|
|
161
|
+
"column_name, data_type, udt_name, is_nullable, column_default, character_maximum_length, is_identity, identity_generation"
|
|
162
|
+
).eq("table_schema", "public").eq("table_name", r);
|
|
163
|
+
if (!i && t && t.length > 0)
|
|
164
|
+
return p({
|
|
165
|
+
tableName: r,
|
|
166
|
+
columns: t,
|
|
167
|
+
...e
|
|
168
|
+
});
|
|
169
|
+
} catch {
|
|
170
|
+
}
|
|
171
|
+
try {
|
|
172
|
+
const { data: t, error: i } = await n.from(r).select("*").limit(1);
|
|
173
|
+
if (!i && Array.isArray(t) && t.length > 0) {
|
|
174
|
+
const u = t[0], d = Object.entries(u).map(([_, c]) => ({
|
|
175
|
+
column_name: _,
|
|
176
|
+
data_type: y(c),
|
|
177
|
+
udt_name: y(c),
|
|
178
|
+
is_nullable: "YES",
|
|
179
|
+
column_default: null,
|
|
180
|
+
character_maximum_length: null,
|
|
181
|
+
is_identity: "NO",
|
|
182
|
+
identity_generation: null
|
|
183
|
+
}));
|
|
184
|
+
return p({ tableName: r, columns: d, ...e });
|
|
185
|
+
}
|
|
186
|
+
} catch {
|
|
187
|
+
}
|
|
188
|
+
throw new Error(
|
|
189
|
+
`Failed to fetch schema for "${r}". Create the get_table_columns() RPC function or provide schema manually via inferSchema().`
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
function y(n) {
|
|
193
|
+
return n == null ? "text" : typeof n == "boolean" ? "bool" : typeof n == "number" ? Number.isInteger(n) ? "int4" : "float8" : typeof n == "string" ? /^\d{4}-\d{2}-\d{2}T/.test(n) ? "timestamptz" : /^\d{4}-\d{2}-\d{2}$/.test(n) ? "date" : "text" : Array.isArray(n) ? "_text" : typeof n == "object" ? "jsonb" : "text";
|
|
194
|
+
}
|
|
195
|
+
function s(n) {
|
|
196
|
+
return n.replace(/_/g, " ").replace(/\b\w/g, (r) => r.toUpperCase()).trim();
|
|
197
|
+
}
|
|
198
|
+
export {
|
|
199
|
+
w as fetchSchemaFromSupabase,
|
|
200
|
+
s as humanizeColumnName,
|
|
201
|
+
p as inferSchema
|
|
202
|
+
};
|