@manohub/app-kit 0.1.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.
Files changed (103) hide show
  1. package/CONTRACT.md +592 -0
  2. package/README.md +27 -0
  3. package/dist/atoms/app-button.d.ts +90 -0
  4. package/dist/atoms/app-button.js +74 -0
  5. package/dist/atoms/app-checkbox.d.ts +86 -0
  6. package/dist/atoms/app-checkbox.js +78 -0
  7. package/dist/atoms/app-icon-button.d.ts +79 -0
  8. package/dist/atoms/app-icon-button.js +54 -0
  9. package/dist/atoms/app-input.d.ts +118 -0
  10. package/dist/atoms/app-input.js +83 -0
  11. package/dist/atoms/app-layout.d.ts +348 -0
  12. package/dist/atoms/app-layout.js +102 -0
  13. package/dist/atoms/app-notice.d.ts +90 -0
  14. package/dist/atoms/app-notice.js +59 -0
  15. package/dist/atoms/app-query-state.d.ts +191 -0
  16. package/dist/atoms/app-query-state.js +161 -0
  17. package/dist/atoms/app-radio-card.d.ts +80 -0
  18. package/dist/atoms/app-radio-card.js +72 -0
  19. package/dist/atoms/app-search-box.d.ts +80 -0
  20. package/dist/atoms/app-search-box.js +64 -0
  21. package/dist/atoms/app-select.d.ts +106 -0
  22. package/dist/atoms/app-select.js +74 -0
  23. package/dist/atoms/app-switch.d.ts +72 -0
  24. package/dist/atoms/app-switch.js +66 -0
  25. package/dist/atoms/app-textarea.d.ts +89 -0
  26. package/dist/atoms/app-textarea.js +61 -0
  27. package/dist/atoms/app-tooltip.d.ts +70 -0
  28. package/dist/atoms/app-tooltip.js +48 -0
  29. package/dist/atoms/atoms.css +679 -0
  30. package/dist/atoms/state-illustration.d.ts +21 -0
  31. package/dist/atoms/state-illustration.js +101 -0
  32. package/dist/components/app-badge.d.ts +72 -0
  33. package/dist/components/app-badge.js +40 -0
  34. package/dist/components/app-dialog.d.ts +164 -0
  35. package/dist/components/app-dialog.js +121 -0
  36. package/dist/components/app-filter.d.ts +173 -0
  37. package/dist/components/app-filter.js +200 -0
  38. package/dist/components/app-form.d.ts +446 -0
  39. package/dist/components/app-form.js +159 -0
  40. package/dist/components/app-pagination.d.ts +78 -0
  41. package/dist/components/app-pagination.js +57 -0
  42. package/dist/components/app-panel.d.ts +399 -0
  43. package/dist/components/app-panel.js +213 -0
  44. package/dist/components/app-section.d.ts +68 -0
  45. package/dist/components/app-section.js +28 -0
  46. package/dist/components/app-steps.d.ts +115 -0
  47. package/dist/components/app-steps.js +108 -0
  48. package/dist/components/app-table.d.ts +415 -0
  49. package/dist/components/app-table.js +479 -0
  50. package/dist/components/app-tabs.d.ts +58 -0
  51. package/dist/components/app-tabs.js +54 -0
  52. package/dist/components/app-tree.d.ts +273 -0
  53. package/dist/components/app-tree.js +222 -0
  54. package/dist/components/components.css +597 -0
  55. package/dist/composables/use-client-pagination.d.ts +39 -0
  56. package/dist/composables/use-client-pagination.js +29 -0
  57. package/dist/entry/create-query-client.d.ts +8 -0
  58. package/dist/entry/create-query-client.js +14 -0
  59. package/dist/entry/create-sub-app.d.ts +45 -0
  60. package/dist/entry/create-sub-app.js +110 -0
  61. package/dist/entry/index.d.ts +4 -0
  62. package/dist/entry/index.js +12 -0
  63. package/dist/entry/initial-guard.d.ts +12 -0
  64. package/dist/entry/initial-guard.js +23 -0
  65. package/dist/index.d.ts +37 -0
  66. package/dist/index.js +67 -0
  67. package/dist/providers/setup-i18n.d.ts +19 -0
  68. package/dist/providers/setup-i18n.js +24 -0
  69. package/dist/services/app-container.d.ts +15 -0
  70. package/dist/services/app-container.js +7 -0
  71. package/dist/services/app-context.d.ts +4 -0
  72. package/dist/services/app-context.js +17 -0
  73. package/dist/services/index.d.ts +5 -0
  74. package/dist/services/loading.d.ts +16 -0
  75. package/dist/services/loading.js +27 -0
  76. package/dist/services/message-box.d.ts +84 -0
  77. package/dist/services/message-box.js +65 -0
  78. package/dist/services/modal.d.ts +51 -0
  79. package/dist/services/modal.js +51 -0
  80. package/dist/services/notify.d.ts +24 -0
  81. package/dist/services/notify.js +22 -0
  82. package/dist/shell/AppShell.d.ts +139 -0
  83. package/dist/shell/AppShell.js +171 -0
  84. package/dist/shell/shell.css +153 -0
  85. package/dist/styles/farris-bridge.css +129 -0
  86. package/dist/styles/index.css +16 -0
  87. package/dist/styles/markdown.css +85 -0
  88. package/dist/styles/reset.css +74 -0
  89. package/dist/styles/tokens.css +111 -0
  90. package/lint/__tests__/fixtures/clean-src/styles.css +14 -0
  91. package/lint/__tests__/fixtures/clean-src/views/good-page.tsx +15 -0
  92. package/lint/__tests__/fixtures/violations-src/styles.css +18 -0
  93. package/lint/__tests__/fixtures/violations-src/views/bad-page.tsx +38 -0
  94. package/lint/__tests__/fixtures/violations-src/views/no-shell-page.tsx +4 -0
  95. package/lint/__tests__/guardrails.spec.mjs +144 -0
  96. package/lint/component-audit.mjs +131 -0
  97. package/lint/guardrails.config.schema.json +74 -0
  98. package/lint/pre-commit.sample +26 -0
  99. package/lint/run-all.mjs +59 -0
  100. package/lint/shared.mjs +347 -0
  101. package/lint/structure-audit.mjs +254 -0
  102. package/lint/style-audit.mjs +200 -0
  103. package/package.json +78 -0
@@ -0,0 +1,479 @@
1
+ import { defineComponent, ref, computed, watch, nextTick, onMounted, onBeforeUnmount, createVNode, mergeProps } from "vue";
2
+ import { FDataGrid } from "@farris/ui-vue";
3
+ import { AppQueryState } from "../atoms/app-query-state.js";
4
+ const RESIZE_DEBOUNCE_MS = 150;
5
+ function unwrapRow(row) {
6
+ const wrapper = row ?? {};
7
+ const item = "raw" in wrapper ? wrapper.raw : wrapper;
8
+ const index = typeof item?.__fv_data_index__ === "number" ? item.__fv_data_index__ : 0;
9
+ return {
10
+ item,
11
+ index
12
+ };
13
+ }
14
+ const SOURCE_ROW_KEY = "__ak_source_row__";
15
+ function toBusinessRow(item) {
16
+ const record = item;
17
+ return record && typeof record === "object" && SOURCE_ROW_KEY in record ? record[SOURCE_ROW_KEY] : item;
18
+ }
19
+ const AppTable = /* @__PURE__ */ defineComponent({
20
+ name: "AppTable",
21
+ inheritAttrs: false,
22
+ props: {
23
+ rows: {
24
+ type: Array,
25
+ required: true
26
+ },
27
+ columns: {
28
+ type: Array,
29
+ required: true
30
+ },
31
+ rowKey: {
32
+ type: String,
33
+ required: true
34
+ },
35
+ loading: {
36
+ type: Boolean,
37
+ default: false
38
+ },
39
+ /*
40
+ * truthy 即触发错误态;type 用构造器数组承载 unknown(`type: null` 在 vue-tsc 下被推断为 undefined)。
41
+ *
42
+ * **顺序有硬约束:`String` 必须排在 `Boolean` 前面。** Vue 的 prop 布尔转换规则是
43
+ * 「Boolean 在类型数组里比 String 靠前时,空字符串会被转成 true」—— 顺序写反时
44
+ * `error={loadError.value}` 在**成功态**(值为 `''`)会被判成 `true`,表格永远显示「加载失败」,
45
+ * 数据行根本渲染不出来(真实踩过:接口返回 200 + `[]` 仍报加载失败)。
46
+ */
47
+ error: {
48
+ type: [String, Boolean, Object, Number, Array],
49
+ default: void 0
50
+ },
51
+ empty: {
52
+ type: String,
53
+ default: "暂无数据"
54
+ },
55
+ emptyDescription: {
56
+ type: String,
57
+ default: void 0
58
+ },
59
+ /** 错误态标题(默认「加载失败」);错误原文会自动作为副文案显示 */
60
+ errorTitle: {
61
+ type: String,
62
+ default: "加载失败"
63
+ },
64
+ /** 空态操作按钮(如「新建」);与 `onEmptyAction` 同时传才渲染 */
65
+ emptyActionText: {
66
+ type: String,
67
+ default: void 0
68
+ },
69
+ onEmptyAction: {
70
+ type: Function,
71
+ default: void 0
72
+ },
73
+ /** 错误态操作按钮(如「重试」);与 `onErrorAction` 同时传才渲染 */
74
+ errorActionText: {
75
+ type: String,
76
+ default: void 0
77
+ },
78
+ onErrorAction: {
79
+ type: Function,
80
+ default: void 0
81
+ },
82
+ skeletonRows: {
83
+ type: Number,
84
+ default: 6
85
+ },
86
+ rowNumber: {
87
+ type: [Boolean, Object],
88
+ default: false
89
+ },
90
+ rowHeight: {
91
+ type: Number,
92
+ default: void 0
93
+ },
94
+ headerHeight: {
95
+ type: Number,
96
+ default: void 0
97
+ },
98
+ stripe: {
99
+ type: Boolean,
100
+ default: void 0
101
+ },
102
+ framed: {
103
+ type: Boolean,
104
+ default: false
105
+ },
106
+ // 任意类型载体:type 用构造器数组(`PropType<unknown>` 在本仓 vue-tsc 下退化为 undefined);
107
+ // `String` 同样必须排在 `Boolean` 前(理由见上面 `error` 的说明)
108
+ refreshKey: {
109
+ type: [String, Boolean, Object, Number, Array],
110
+ default: void 0
111
+ },
112
+ selection: {
113
+ type: [Boolean, Object],
114
+ default: false
115
+ },
116
+ rowHighlightKey: {
117
+ type: String,
118
+ default: void 0
119
+ },
120
+ rowClass: {
121
+ type: Function,
122
+ default: void 0
123
+ },
124
+ onRowClick: {
125
+ type: Function,
126
+ default: void 0
127
+ },
128
+ onRowDoubleClick: {
129
+ type: Function,
130
+ default: void 0
131
+ },
132
+ tableKey: {
133
+ type: [String, Number],
134
+ default: void 0
135
+ },
136
+ onContainerResize: {
137
+ type: Function,
138
+ default: void 0
139
+ },
140
+ resizePollInterval: {
141
+ type: Number,
142
+ default: void 0
143
+ }
144
+ },
145
+ setup(props, {
146
+ attrs
147
+ }) {
148
+ const gridRef = ref(null);
149
+ const flatRows = computed(() => props.rows.map((row) => {
150
+ const copy = {
151
+ ...row
152
+ };
153
+ Object.defineProperty(copy, SOURCE_ROW_KEY, {
154
+ value: row,
155
+ enumerable: false,
156
+ configurable: true
157
+ });
158
+ return copy;
159
+ }));
160
+ watch(flatRows, (items) => {
161
+ void nextTick(() => {
162
+ gridRef.value?.updateDataSource?.(items);
163
+ });
164
+ });
165
+ const selectionConfig = computed(() => props.selection ? {
166
+ mode: "multiple",
167
+ ...props.selection
168
+ } : null);
169
+ const isControlled = computed(() => selectionConfig.value?.selected !== void 0);
170
+ const internalSelected = ref(/* @__PURE__ */ new Set());
171
+ const rowId = (row) => String(row?.[props.rowKey] ?? "");
172
+ const canSelect = (row) => selectionConfig.value?.selectable ? selectionConfig.value.selectable(row) : true;
173
+ const selectedIds = computed(() => isControlled.value ? new Set(selectionConfig.value?.selected ?? []) : internalSelected.value);
174
+ watch(() => props.rows, (rows) => {
175
+ if (isControlled.value) return;
176
+ const existing = new Set(rows.map((row) => rowId(row)));
177
+ const kept = [...internalSelected.value].filter((id) => existing.has(id));
178
+ if (kept.length !== internalSelected.value.size) internalSelected.value = new Set(kept);
179
+ });
180
+ watch(() => [...selectedIds.value].sort().join(""), () => {
181
+ void nextTick(() => {
182
+ gridRef.value?.updateDataSource?.(flatRows.value);
183
+ });
184
+ });
185
+ watch(() => props.refreshKey, () => {
186
+ void nextTick(() => {
187
+ gridRef.value?.updateDataSource?.(flatRows.value);
188
+ });
189
+ }, {
190
+ deep: true
191
+ });
192
+ const pageSelectableRows = computed(() => {
193
+ const cfg = selectionConfig.value;
194
+ if (!cfg || cfg.mode === "single") return [];
195
+ return props.rows.filter((row) => canSelect(row));
196
+ });
197
+ const allPageSelected = computed(() => pageSelectableRows.value.length > 0 && pageSelectableRows.value.every((row) => selectedIds.value.has(rowId(row))));
198
+ const somePageSelected = computed(() => pageSelectableRows.value.some((row) => selectedIds.value.has(rowId(row))));
199
+ const toggleIds = (ids) => {
200
+ const cfg = selectionConfig.value;
201
+ if (!cfg || ids.length === 0) return;
202
+ if (isControlled.value) {
203
+ cfg.onSelectedChange?.(ids);
204
+ return;
205
+ }
206
+ const next = new Set(internalSelected.value);
207
+ for (const id of ids) {
208
+ if (next.has(id)) next.delete(id);
209
+ else next.add(id);
210
+ }
211
+ internalSelected.value = next;
212
+ cfg.onSelectedChange?.([...next]);
213
+ };
214
+ const handleToggleRow = (row) => {
215
+ const cfg = selectionConfig.value;
216
+ if (!cfg || !canSelect(row)) return;
217
+ const id = rowId(row);
218
+ if (cfg.mode !== "single") {
219
+ toggleIds([id]);
220
+ return;
221
+ }
222
+ const current = [...selectedIds.value];
223
+ if (current.length === 1 && current[0] === id) return;
224
+ if (isControlled.value) {
225
+ cfg.onSelectedChange?.([...current.filter((it) => it !== id), id]);
226
+ return;
227
+ }
228
+ internalSelected.value = /* @__PURE__ */ new Set([id]);
229
+ cfg.onSelectedChange?.([id]);
230
+ };
231
+ const renderSelectCell = (_cell, row) => {
232
+ const cfg = selectionConfig.value;
233
+ if (!cfg) return null;
234
+ const item = toBusinessRow(unwrapRow(row).item);
235
+ const selectable = canSelect(item);
236
+ return createVNode("label", {
237
+ "class": selectable ? "ak-table-check" : "ak-table-check is-disabled"
238
+ }, [createVNode("input", {
239
+ "type": cfg.mode === "single" ? "radio" : "checkbox",
240
+ "checked": selectedIds.value.has(rowId(item)),
241
+ "disabled": !selectable,
242
+ "onChange": () => handleToggleRow(item)
243
+ }, null)]);
244
+ };
245
+ const renderSelectAllHeader = () => {
246
+ if (selectionConfig.value?.mode === "single") return null;
247
+ const selectable = pageSelectableRows.value.length > 0;
248
+ return createVNode("label", {
249
+ "class": selectable ? "ak-table-check" : "ak-table-check is-disabled"
250
+ }, [createVNode("input", {
251
+ "type": "checkbox",
252
+ "checked": allPageSelected.value,
253
+ "disabled": !selectable,
254
+ "ref": (el) => {
255
+ const input = el;
256
+ if (input) input.indeterminate = somePageSelected.value && !allPageSelected.value;
257
+ },
258
+ "onChange": () => {
259
+ const cfg = selectionConfig.value;
260
+ if (!cfg) return;
261
+ if (cfg.onSelectAll) {
262
+ cfg.onSelectAll(pageSelectableRows.value);
263
+ return;
264
+ }
265
+ const targetSelected = !allPageSelected.value;
266
+ const ids = pageSelectableRows.value.map((row) => rowId(row)).filter((id) => selectedIds.value.has(id) !== targetSelected);
267
+ toggleIds(ids);
268
+ }
269
+ }, null)]);
270
+ };
271
+ const farrisColumns = computed(() => {
272
+ const columns = props.columns.map((col) => {
273
+ const column = {
274
+ field: col.key,
275
+ dataType: "string",
276
+ width: col.width ?? 120,
277
+ resizable: true
278
+ };
279
+ if (typeof col.title === "function") {
280
+ column.title = "";
281
+ column.headerFormatter = col.title;
282
+ } else {
283
+ column.title = col.title;
284
+ }
285
+ if (col.align) {
286
+ column.align = {
287
+ left: "left",
288
+ center: "center",
289
+ right: "right"
290
+ }[col.align];
291
+ }
292
+ if (col.render) {
293
+ column.columnTemplate = (_cell, row) => {
294
+ const {
295
+ item,
296
+ index
297
+ } = unwrapRow(row);
298
+ return col.render(toBusinessRow(item), index);
299
+ };
300
+ }
301
+ return column;
302
+ });
303
+ if (selectionConfig.value) {
304
+ columns.unshift({
305
+ field: "__select",
306
+ title: "",
307
+ dataType: "string",
308
+ width: 44,
309
+ resizable: false,
310
+ columnTemplate: renderSelectCell,
311
+ headerFormatter: renderSelectAllHeader
312
+ });
313
+ }
314
+ return columns;
315
+ });
316
+ const passThroughAttrs = computed(() => {
317
+ const out = {
318
+ customClass: [
319
+ attrs.class,
320
+ "ak-table",
321
+ props.framed ? "ak-table--framed" : "",
322
+ /* 表头高度按需开启:只有传了 headerHeight 才挂类 + 下发变量,
323
+ 不传的消费方与以前完全一致(bridge 规则不生效) */
324
+ props.headerHeight ? "ak-table--custom-header" : ""
325
+ ].filter(Boolean).join(" ")
326
+ };
327
+ const style = {
328
+ ...attrs.style ?? {}
329
+ };
330
+ if (props.headerHeight) style["--ak-table-header-height"] = `${props.headerHeight}px`;
331
+ if (Object.keys(style).length > 0) out.style = style;
332
+ return out;
333
+ });
334
+ const rowNumberOption = computed(() => {
335
+ if (props.rowNumber === false) return void 0;
336
+ if (props.rowNumber === true) return {
337
+ enable: true,
338
+ width: 60
339
+ };
340
+ return {
341
+ enable: true,
342
+ heading: props.rowNumber.heading,
343
+ width: props.rowNumber.width ?? 60
344
+ };
345
+ });
346
+ const hostRef = ref(null);
347
+ const needsHost = computed(() => !!props.onContainerResize || !!props.resizePollInterval);
348
+ let observer = null;
349
+ let pollTimer = null;
350
+ let debounceTimer = null;
351
+ let lastWidth = 0;
352
+ const flushResize = () => {
353
+ if (debounceTimer) return;
354
+ debounceTimer = setTimeout(() => {
355
+ debounceTimer = null;
356
+ const width = hostRef.value?.clientWidth ?? 0;
357
+ if (width > 0 && width !== lastWidth) {
358
+ lastWidth = width;
359
+ props.onContainerResize?.(width);
360
+ }
361
+ }, RESIZE_DEBOUNCE_MS);
362
+ };
363
+ onMounted(() => {
364
+ if (props.resizePollInterval && props.resizePollInterval > 0) {
365
+ pollTimer = setInterval(flushResize, props.resizePollInterval);
366
+ }
367
+ });
368
+ watch(hostRef, (el) => {
369
+ observer?.disconnect();
370
+ observer = null;
371
+ if (!el) return;
372
+ lastWidth = el.clientWidth;
373
+ if (typeof ResizeObserver !== "undefined") {
374
+ observer = new ResizeObserver(flushResize);
375
+ observer.observe(el);
376
+ }
377
+ });
378
+ watch([() => props.rowHighlightKey, flatRows], async () => {
379
+ if (!props.rowHighlightKey || !gridRef.value?.selectItemByIds) return;
380
+ await nextTick();
381
+ gridRef.value.selectItemByIds([props.rowHighlightKey]);
382
+ }, {
383
+ flush: "post"
384
+ });
385
+ onBeforeUnmount(() => {
386
+ observer?.disconnect();
387
+ observer = null;
388
+ if (pollTimer) {
389
+ clearInterval(pollTimer);
390
+ pollTimer = null;
391
+ }
392
+ if (debounceTimer) {
393
+ clearTimeout(debounceTimer);
394
+ debounceTimer = null;
395
+ }
396
+ });
397
+ return () => {
398
+ if (props.loading) {
399
+ return createVNode(AppQueryState, {
400
+ "loading": true,
401
+ "skeletonRows": props.skeletonRows,
402
+ "skeletonRowHeight": 40
403
+ }, null);
404
+ }
405
+ if (props.error) {
406
+ return createVNode(AppQueryState, {
407
+ "error": props.error,
408
+ "errorTitle": props.errorTitle,
409
+ "errorDescription": typeof props.error === "string" ? props.error : void 0,
410
+ "errorActionText": props.errorActionText,
411
+ "onErrorAction": props.onErrorAction
412
+ }, null);
413
+ }
414
+ if (props.rows.length === 0) {
415
+ return createVNode(AppQueryState, {
416
+ "empty": props.empty ?? "暂无数据",
417
+ "emptyDescription": props.emptyDescription,
418
+ "emptyActionText": props.emptyActionText,
419
+ "onEmptyAction": props.onEmptyAction
420
+ }, null);
421
+ }
422
+ const grid = createVNode(FDataGrid, mergeProps({
423
+ "key": props.tableKey,
424
+ "ref": gridRef,
425
+ "data": flatRows.value,
426
+ "columns": farrisColumns.value,
427
+ "idField": props.rowKey,
428
+ "showHeader": true,
429
+ "fit": true,
430
+ "rowNumber": rowNumberOption.value,
431
+ "showStripe": props.stripe,
432
+ "rowOption": props.rowHeight || props.rowClass ? {
433
+ height: props.rowHeight,
434
+ showHovering: true,
435
+ wrapContent: false,
436
+ customRowStyle: props.rowClass ? (raw) => {
437
+ const cls = props.rowClass(toBusinessRow(unwrapRow(raw).item));
438
+ if (cls === void 0) return void 0;
439
+ return {
440
+ class: typeof cls === "string" ? {
441
+ [cls]: true
442
+ } : cls
443
+ };
444
+ } : void 0
445
+ } : void 0,
446
+ "columnOption": {
447
+ fitColumns: true,
448
+ resizeColumn: true,
449
+ fitMode: "percentage"
450
+ },
451
+ "selection": {
452
+ // 行高亮(rowHighlightKey)需要上游的「行选中」开关为真才染色,
453
+ // 但所有「列」的显示开关保持关闭 —— 得到的是染行、不加列。
454
+ enabelSelectRow: !!props.rowHighlightKey,
455
+ enableSelectRow: !!props.rowHighlightKey,
456
+ multiSelect: false,
457
+ showCheckbox: false,
458
+ showSelectAll: false,
459
+ showSelection: false
460
+ },
461
+ "selectionValues": props.rowHighlightKey ? [props.rowHighlightKey] : void 0
462
+ }, passThroughAttrs.value, {
463
+ "onClickRow": (_index, raw) => {
464
+ props.onRowClick?.(toBusinessRow(unwrapRow(raw).item));
465
+ },
466
+ "onDoubleClickRow": (_index, raw) => {
467
+ props.onRowDoubleClick?.(toBusinessRow(unwrapRow(raw).item));
468
+ }
469
+ }), null);
470
+ return needsHost.value ? createVNode("div", {
471
+ "ref": hostRef,
472
+ "class": "ak-table-host"
473
+ }, [grid]) : grid;
474
+ };
475
+ }
476
+ });
477
+ export {
478
+ AppTable
479
+ };
@@ -0,0 +1,58 @@
1
+ import { type PropType, type SlotsType, type VNodeChild } from 'vue';
2
+ export interface AppTabItem {
3
+ /** 页签标识,同时用作插槽名(`v-slots={{ [key]: () => ... }}`) */
4
+ key: string;
5
+ label: string;
6
+ }
7
+ /**
8
+ * 页签(封装 farris FTabs + FTabPage)。
9
+ *
10
+ * 对外只给 `items` + 受控 `modelValue`(当前 key),页签内容按 `item.key` 取同名插槽;
11
+ * farris 的 `activeId` / `onUpdate:activeId` / `fill` 等写法在内部翻译。
12
+ *
13
+ * @example
14
+ * <AppTabs modelValue={activeTab} items={[{ key: 'overview', label: '概览' }, { key: 'tools', label: '工具' }]}
15
+ * v-slots={{ overview: () => <Overview />, tools: () => <Tools /> }}
16
+ * onChange={(key) => (activeTab = key)} />
17
+ */
18
+ export declare const AppTabs: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
19
+ modelValue: {
20
+ type: StringConstructor;
21
+ required: true;
22
+ };
23
+ items: {
24
+ type: PropType<AppTabItem[]>;
25
+ default: () => never[];
26
+ };
27
+ /** 撑满容器高度(farris fill,抽屉/面板内需要) */
28
+ fill: {
29
+ type: BooleanConstructor;
30
+ default: boolean;
31
+ };
32
+ onChange: {
33
+ type: PropType<(key: string) => void>;
34
+ default: undefined;
35
+ };
36
+ }>, () => import("vue/jsx-runtime").JSX.Element, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
37
+ modelValue: {
38
+ type: StringConstructor;
39
+ required: true;
40
+ };
41
+ items: {
42
+ type: PropType<AppTabItem[]>;
43
+ default: () => never[];
44
+ };
45
+ /** 撑满容器高度(farris fill,抽屉/面板内需要) */
46
+ fill: {
47
+ type: BooleanConstructor;
48
+ default: boolean;
49
+ };
50
+ onChange: {
51
+ type: PropType<(key: string) => void>;
52
+ default: undefined;
53
+ };
54
+ }>> & Readonly<{}>, {
55
+ fill: boolean;
56
+ onChange: (key: string) => void;
57
+ items: AppTabItem[];
58
+ }, SlotsType<Record<string, (() => VNodeChild) | undefined>>, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@@ -0,0 +1,54 @@
1
+ import { defineComponent, createVNode, isVNode } from "vue";
2
+ import { FTabs, FTabPage } from "@farris/ui-vue";
3
+ function _isSlot(s) {
4
+ return typeof s === "function" || Object.prototype.toString.call(s) === "[object Object]" && !isVNode(s);
5
+ }
6
+ const AppTabs = /* @__PURE__ */ defineComponent({
7
+ name: "AppTabs",
8
+ inheritAttrs: false,
9
+ props: {
10
+ modelValue: {
11
+ type: String,
12
+ required: true
13
+ },
14
+ items: {
15
+ type: Array,
16
+ default: () => []
17
+ },
18
+ /** 撑满容器高度(farris fill,抽屉/面板内需要) */
19
+ fill: {
20
+ type: Boolean,
21
+ default: true
22
+ },
23
+ onChange: {
24
+ type: Function,
25
+ default: void 0
26
+ }
27
+ },
28
+ slots: Object,
29
+ setup(props, {
30
+ slots,
31
+ attrs
32
+ }) {
33
+ return () => {
34
+ let _slot;
35
+ return createVNode(FTabs, {
36
+ "activeId": props.modelValue,
37
+ "fill": props.fill,
38
+ "customClass": attrs.class,
39
+ "onUpdate:activeId": (key) => props.onChange?.(key)
40
+ }, _isSlot(_slot = props.items.map((item) => createVNode(FTabPage, {
41
+ "id": item.key,
42
+ "title": item.label,
43
+ "key": item.key
44
+ }, {
45
+ default: () => [slots[item.key]?.()]
46
+ }))) ? _slot : {
47
+ default: () => [_slot]
48
+ });
49
+ };
50
+ }
51
+ });
52
+ export {
53
+ AppTabs
54
+ };