@dou-ui/dashboard 0.0.0-stage → 0.1.0-alpha.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 (188) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -2
  3. package/dist/chart/chart-settings.js +6 -0
  4. package/dist/chart/chart-settings.js.map +1 -0
  5. package/dist/chart/chart-settings.vue_vue_type_script_setup_true_lang.js +122 -0
  6. package/dist/chart/chart-settings.vue_vue_type_script_setup_true_lang.js.map +1 -0
  7. package/dist/chart/chart.js +6 -0
  8. package/dist/chart/chart.js.map +1 -0
  9. package/dist/chart/chart.vue_vue_type_script_setup_true_lang.js +99 -0
  10. package/dist/chart/chart.vue_vue_type_script_setup_true_lang.js.map +1 -0
  11. package/dist/chart/index.js +33 -0
  12. package/dist/chart/index.js.map +1 -0
  13. package/dist/collaboration/index.js +281 -0
  14. package/dist/collaboration/index.js.map +1 -0
  15. package/dist/collaboration/order.js +88 -0
  16. package/dist/collaboration/order.js.map +1 -0
  17. package/dist/core/document.js +128 -0
  18. package/dist/core/document.js.map +1 -0
  19. package/dist/core/history.js +66 -0
  20. package/dist/core/history.js.map +1 -0
  21. package/dist/core/transaction.js +218 -0
  22. package/dist/core/transaction.js.map +1 -0
  23. package/dist/dashboard/dashboard.js +6 -0
  24. package/dist/dashboard/dashboard.js.map +1 -0
  25. package/dist/dashboard/dashboard.vue_vue_type_script_setup_true_lang.js +1072 -0
  26. package/dist/dashboard/dashboard.vue_vue_type_script_setup_true_lang.js.map +1 -0
  27. package/dist/dashboard/export.js +77 -0
  28. package/dist/dashboard/export.js.map +1 -0
  29. package/dist/dashboard/filter-bar.js +6 -0
  30. package/dist/dashboard/filter-bar.js.map +1 -0
  31. package/dist/dashboard/filter-bar.vue_vue_type_script_setup_true_lang.js +150 -0
  32. package/dist/dashboard/filter-bar.vue_vue_type_script_setup_true_lang.js.map +1 -0
  33. package/dist/dashboard/frame-context.js +5 -0
  34. package/dist/dashboard/frame-context.js.map +1 -0
  35. package/dist/dashboard/observers.js +48 -0
  36. package/dist/dashboard/observers.js.map +1 -0
  37. package/dist/dashboard/palette.js +6 -0
  38. package/dist/dashboard/palette.js.map +1 -0
  39. package/dist/dashboard/palette.vue_vue_type_script_setup_true_lang.js +54 -0
  40. package/dist/dashboard/palette.vue_vue_type_script_setup_true_lang.js.map +1 -0
  41. package/dist/dashboard/widget-body.js +6 -0
  42. package/dist/dashboard/widget-body.js.map +1 -0
  43. package/dist/dashboard/widget-body.vue_vue_type_script_setup_true_lang.js +96 -0
  44. package/dist/dashboard/widget-body.vue_vue_type_script_setup_true_lang.js.map +1 -0
  45. package/dist/dashboard/widget-frame.js +6 -0
  46. package/dist/dashboard/widget-frame.js.map +1 -0
  47. package/dist/dashboard/widget-frame.vue_vue_type_script_setup_true_lang.js +300 -0
  48. package/dist/dashboard/widget-frame.vue_vue_type_script_setup_true_lang.js.map +1 -0
  49. package/dist/dashboard/widget-settings.js +6 -0
  50. package/dist/dashboard/widget-settings.js.map +1 -0
  51. package/dist/dashboard/widget-settings.vue_vue_type_script_setup_true_lang.js +36 -0
  52. package/dist/dashboard/widget-settings.vue_vue_type_script_setup_true_lang.js.map +1 -0
  53. package/dist/data/context.js +5 -0
  54. package/dist/data/context.js.map +1 -0
  55. package/dist/data/filters.js +31 -0
  56. package/dist/data/filters.js.map +1 -0
  57. package/dist/data/store.js +214 -0
  58. package/dist/data/store.js.map +1 -0
  59. package/dist/grid/grid-handle.js +6 -0
  60. package/dist/grid/grid-handle.js.map +1 -0
  61. package/dist/grid/grid-handle.vue_vue_type_script_setup_true_lang.js +21 -0
  62. package/dist/grid/grid-handle.vue_vue_type_script_setup_true_lang.js.map +1 -0
  63. package/dist/grid/grid-item.js +80 -0
  64. package/dist/grid/grid-item.js.map +1 -0
  65. package/dist/grid/grid-layout.js +6 -0
  66. package/dist/grid/grid-layout.js.map +1 -0
  67. package/dist/grid/grid-layout.vue_vue_type_script_setup_true_lang.js +767 -0
  68. package/dist/grid/grid-layout.vue_vue_type_script_setup_true_lang.js.map +1 -0
  69. package/dist/grid/tiers.js +31 -0
  70. package/dist/grid/tiers.js.map +1 -0
  71. package/dist/index.js +23 -0
  72. package/dist/index.js.map +1 -0
  73. package/dist/layout/engine.js +284 -0
  74. package/dist/layout/engine.js.map +1 -0
  75. package/dist/layout/index.js +1 -0
  76. package/dist/locale.js +245 -0
  77. package/dist/locale.js.map +1 -0
  78. package/dist/pivot/index.js +47 -0
  79. package/dist/pivot/index.js.map +1 -0
  80. package/dist/pivot/pivot-settings.js +6 -0
  81. package/dist/pivot/pivot-settings.js.map +1 -0
  82. package/dist/pivot/pivot-settings.vue_vue_type_script_setup_true_lang.js +64 -0
  83. package/dist/pivot/pivot-settings.vue_vue_type_script_setup_true_lang.js.map +1 -0
  84. package/dist/pivot/pivot.js +6 -0
  85. package/dist/pivot/pivot.js.map +1 -0
  86. package/dist/pivot/pivot.vue_vue_type_script_setup_true_lang.js +76 -0
  87. package/dist/pivot/pivot.vue_vue_type_script_setup_true_lang.js.map +1 -0
  88. package/dist/style.css +1 -0
  89. package/dist/style.d.ts +1 -0
  90. package/dist/style.js +12 -0
  91. package/dist/types/chart/chart-settings.vue.d.ts +11 -0
  92. package/dist/types/chart/chart.vue.d.ts +3 -0
  93. package/dist/types/chart/index.d.ts +9 -0
  94. package/dist/types/chart/types.d.ts +23 -0
  95. package/dist/types/collaboration/index.d.ts +53 -0
  96. package/dist/types/collaboration/order.d.ts +20 -0
  97. package/dist/types/core/collaboration.d.ts +51 -0
  98. package/dist/types/core/document.d.ts +35 -0
  99. package/dist/types/core/history.d.ts +15 -0
  100. package/dist/types/core/transaction.d.ts +86 -0
  101. package/dist/types/core/types.d.ts +27 -0
  102. package/dist/types/dashboard/dashboard.vue.d.ts +33 -0
  103. package/dist/types/dashboard/export.d.ts +11 -0
  104. package/dist/types/dashboard/filter-bar.vue.d.ts +19 -0
  105. package/dist/types/dashboard/frame-context.d.ts +54 -0
  106. package/dist/types/dashboard/observers.d.ts +15 -0
  107. package/dist/types/dashboard/palette.vue.d.ts +20 -0
  108. package/dist/types/dashboard/types.d.ts +190 -0
  109. package/dist/types/dashboard/widget-body.vue.d.ts +19 -0
  110. package/dist/types/dashboard/widget-frame.vue.d.ts +6 -0
  111. package/dist/types/dashboard/widget-settings.vue.d.ts +16 -0
  112. package/dist/types/data/context.d.ts +18 -0
  113. package/dist/types/data/filters.d.ts +9 -0
  114. package/dist/types/data/store.d.ts +48 -0
  115. package/dist/types/data/types.d.ts +67 -0
  116. package/dist/types/grid/grid-handle.vue.d.ts +18 -0
  117. package/dist/types/grid/grid-item.d.ts +78 -0
  118. package/dist/types/grid/grid-layout.vue.d.ts +46 -0
  119. package/dist/types/grid/tiers.d.ts +12 -0
  120. package/dist/types/grid/types.d.ts +113 -0
  121. package/dist/types/index.d.ts +295 -0
  122. package/dist/types/layout/engine.d.ts +50 -0
  123. package/dist/types/layout/index.d.ts +2 -0
  124. package/dist/types/layout/types.d.ts +39 -0
  125. package/dist/types/locale.d.ts +117 -0
  126. package/dist/types/pivot/index.d.ts +14 -0
  127. package/dist/types/pivot/pivot-settings.vue.d.ts +16 -0
  128. package/dist/types/pivot/pivot.vue.d.ts +7 -0
  129. package/dist/types/pivot/types.d.ts +11 -0
  130. package/dist/types/widget/context.d.ts +91 -0
  131. package/dist/types/widget/define.d.ts +50 -0
  132. package/dist/types/widgets/field-select.vue.d.ts +18 -0
  133. package/dist/types/widgets/index.d.ts +15 -0
  134. package/dist/types/widgets/settings-field.vue.d.ts +17 -0
  135. package/dist/types/widgets/shared.d.ts +20 -0
  136. package/dist/types/widgets/source-field.vue.d.ts +15 -0
  137. package/dist/types/widgets/stat-settings.vue.d.ts +11 -0
  138. package/dist/types/widgets/stat.vue.d.ts +3 -0
  139. package/dist/types/widgets/table-settings.vue.d.ts +11 -0
  140. package/dist/types/widgets/table.vue.d.ts +3 -0
  141. package/dist/types/widgets/text-settings.vue.d.ts +11 -0
  142. package/dist/types/widgets/text.vue.d.ts +3 -0
  143. package/dist/types/widgets/types.d.ts +38 -0
  144. package/dist/widget/context.js +88 -0
  145. package/dist/widget/context.js.map +1 -0
  146. package/dist/widget/define.js +32 -0
  147. package/dist/widget/define.js.map +1 -0
  148. package/dist/widgets/field-select.js +6 -0
  149. package/dist/widgets/field-select.js.map +1 -0
  150. package/dist/widgets/field-select.vue_vue_type_script_setup_true_lang.js +54 -0
  151. package/dist/widgets/field-select.vue_vue_type_script_setup_true_lang.js.map +1 -0
  152. package/dist/widgets/index.js +74 -0
  153. package/dist/widgets/index.js.map +1 -0
  154. package/dist/widgets/settings-field.js +6 -0
  155. package/dist/widgets/settings-field.js.map +1 -0
  156. package/dist/widgets/settings-field.vue_vue_type_script_setup_true_lang.js +28 -0
  157. package/dist/widgets/settings-field.vue_vue_type_script_setup_true_lang.js.map +1 -0
  158. package/dist/widgets/shared.js +68 -0
  159. package/dist/widgets/shared.js.map +1 -0
  160. package/dist/widgets/source-field.js +6 -0
  161. package/dist/widgets/source-field.js.map +1 -0
  162. package/dist/widgets/source-field.vue_vue_type_script_setup_true_lang.js +47 -0
  163. package/dist/widgets/source-field.vue_vue_type_script_setup_true_lang.js.map +1 -0
  164. package/dist/widgets/stat-settings.js +6 -0
  165. package/dist/widgets/stat-settings.js.map +1 -0
  166. package/dist/widgets/stat-settings.vue_vue_type_script_setup_true_lang.js +107 -0
  167. package/dist/widgets/stat-settings.vue_vue_type_script_setup_true_lang.js.map +1 -0
  168. package/dist/widgets/stat.js +6 -0
  169. package/dist/widgets/stat.js.map +1 -0
  170. package/dist/widgets/stat.vue_vue_type_script_setup_true_lang.js +72 -0
  171. package/dist/widgets/stat.vue_vue_type_script_setup_true_lang.js.map +1 -0
  172. package/dist/widgets/table-settings.js +6 -0
  173. package/dist/widgets/table-settings.js.map +1 -0
  174. package/dist/widgets/table-settings.vue_vue_type_script_setup_true_lang.js +90 -0
  175. package/dist/widgets/table-settings.vue_vue_type_script_setup_true_lang.js.map +1 -0
  176. package/dist/widgets/table.js +6 -0
  177. package/dist/widgets/table.js.map +1 -0
  178. package/dist/widgets/table.vue_vue_type_script_setup_true_lang.js +91 -0
  179. package/dist/widgets/table.vue_vue_type_script_setup_true_lang.js.map +1 -0
  180. package/dist/widgets/text-settings.js +6 -0
  181. package/dist/widgets/text-settings.js.map +1 -0
  182. package/dist/widgets/text-settings.vue_vue_type_script_setup_true_lang.js +63 -0
  183. package/dist/widgets/text-settings.vue_vue_type_script_setup_true_lang.js.map +1 -0
  184. package/dist/widgets/text.js +6 -0
  185. package/dist/widgets/text.js.map +1 -0
  186. package/dist/widgets/text.vue_vue_type_script_setup_true_lang.js +16 -0
  187. package/dist/widgets/text.vue_vue_type_script_setup_true_lang.js.map +1 -0
  188. package/package.json +98 -4
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard.vue_vue_type_script_setup_true_lang.js","names":["_useModel","_openBlock","_createElementBlock","_normalizeClass","_unref","_createVNode","GridLayout","_normalizeStyle","_withCtx","WidgetFrame","_createCommentVNode","_createElementVNode","_toDisplayString","_createBlock","WidgetSettings","WidgetBody"],"sources":["../../src/dashboard/dashboard.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { LayoutGridPlusIcon, fileIconOf } from '@dou-ui/icons'\nimport type { SheetExporter, SheetWorkbook } from '@dou-ui/sheet/core'\nimport {\n DouDialog,\n DouDrawer,\n DouEmpty,\n DouInput,\n downloadBlob,\n matchesHotkey,\n useLocale,\n} from 'dou-ui'\nimport {\n computed,\n nextTick,\n onBeforeUnmount,\n onMounted,\n provide,\n ref,\n shallowReactive,\n shallowRef,\n toRaw,\n watch,\n} from 'vue'\nimport { emptyDocument } from '../core/document'\nimport { DashboardHistory } from '../core/history'\nimport {\n applySteps,\n cloneJson,\n DashboardTransaction,\n replaceWidgets,\n sameValue,\n} from '../core/transaction'\nimport type { DashboardStep, DashboardTransactionMeta } from '../core/transaction'\nimport type { DashboardDocument, DashboardPlace, DashboardWidget } from '../core/types'\nimport type {\n DashboardPeer,\n DashboardPresence,\n DashboardPresenceAction,\n} from '../core/collaboration'\nimport { dataContextKey } from '../data/context'\nimport { filterText, isEmptyFilter } from '../data/filters'\nimport { createSourceStore, sameSource } from '../data/store'\nimport type {\n DashboardFilters,\n DashboardLinkedFilter,\n DashboardSetFilterOptions,\n} from '../data/types'\nimport GridLayout from '../grid/grid-layout.vue'\nimport { DEFAULT_BREAKPOINTS, DEFAULT_COLUMNS, gridTiers } from '../grid/tiers'\nimport type { GridDragEvent, GridDropEvent, GridLayoutExpose, GridLayouts } from '../grid/types'\nimport { findPlace, fitItem, moveItem } from '../layout/engine'\nimport type { GridItem, GridLayoutOptions } from '../layout/types'\nimport { format, useDashboardMessages } from '../locale'\nimport { DEFAULT_WIDGET_SIZE, defaultPropsOf } from '../widget/define'\nimport type { DashboardWidgetType } from '../widget/define'\nimport type {\n DashboardAddOptions,\n DashboardEmits,\n DashboardExportErrorEvent,\n DashboardExportOptions,\n DashboardExpose,\n DashboardSaveOptions,\n DashboardProps,\n} from './types'\nimport { createObservers, observersKey } from './observers'\nimport { elementToPNG } from './export'\nimport type { DashboardImageOptions } from './export'\nimport WidgetBody from './widget-body.vue'\nimport WidgetFrame from './widget-frame.vue'\nimport WidgetSettings from './widget-settings.vue'\nimport { frameContextKey } from './frame-context'\nimport type { FrameExporter, WidgetAction } from './frame-context'\n\ndefineOptions({ name: 'DouDashboard' })\n\nconst props = withDefaults(defineProps<Omit<DashboardProps, 'modelValue' | 'filters'>>(), {\n widgets: () => [],\n columns: () => DEFAULT_COLUMNS,\n breakpoints: () => DEFAULT_BREAKPOINTS,\n rowHeight: 80,\n gap: 16,\n compact: 'vertical',\n})\nconst emit = defineEmits<Omit<DashboardEmits, 'update:modelValue' | 'update:filters'>>()\nconst model = defineModel<DashboardDocument>({ default: emptyDocument })\nconst filters = defineModel<DashboardFilters>('filters', { default: () => ({}) })\nconst messages = useDashboardMessages(() => props.messages)\n\nconst grid = ref<GridLayoutExpose>()\nconst observers = createObservers()\nprovide(observersKey, observers)\nonBeforeUnmount(() => observers.dispose())\n/** This many widgets are drawn in full at once, the rest when they come near the view. */\nconst EAGER = 16\nconst root = ref<HTMLElement>()\n\nconst typeMap = computed(() => new Map(props.widgets.map((type) => [type.type, type])))\nconst typeOf = (widget: DashboardWidget) => typeMap.value.get(widget.type)\nconst widgetMap = computed(() => new Map(model.value.widgets.map((widget) => [widget.id, widget])))\n/** The first widgets of the document, drawn in full without waiting to be seen. */\nconst eagerIds = computed(() => new Set(model.value.widgets.slice(0, EAGER).map((w) => w.id)))\n/** Widgets added once the dashboard is up are drawn in full straight away (not reactive on purpose). */\nlet mounted = false\nonMounted(() => (mounted = true))\nconst isEager = (id: string) => mounted || eagerIds.value.has(id)\nconst titleOf = (widget: DashboardWidget) => widget.title || typeOf(widget)?.label || widget.type\n\nconst tiers = computed(() => gridTiers(props.columns, props.breakpoints))\n/** The widest breakpoint: its places are the main ones. */\nconst main = computed(() => tiers.value[0]!)\nconst mainOptions = computed<GridLayoutOptions>(() => ({\n columns: Math.max(1, Math.round(main.value.columns)),\n compact: props.compact,\n}))\n\n// ---------------------------------------------------------------------------\n// Changes and history\n\nconst history = new DashboardHistory()\n/** Bumped whenever the history changes, so `canUndo` and `canRedo` update. */\nconst historyVersion = ref(0)\n/** The document last emitted, to tell our own changes from the app's. */\nlet emitted: DashboardDocument | undefined\nwatch(model, (doc) => {\n // the app may hand back a reactive copy of what was emitted\n // a document the app put in itself: the old steps no longer fit it\n if (toRaw(doc) === emitted) return\n history.clear()\n historyVersion.value++\n})\n\nfunction commit(tr: DashboardTransaction) {\n emitted = toRaw(tr.doc)\n model.value = tr.doc\n emit('change', tr)\n}\n\n/** A transaction on the document as it is now, to `dispatch`. */\nfunction transaction() {\n return new DashboardTransaction(model.value)\n}\n\n/** Makes the changes of a transaction, as one undo step (or part of one, by `meta.group`). */\nfunction dispatch(tr: DashboardTransaction) {\n if (toRaw(tr.before) !== toRaw(model.value))\n throw new Error(\n '[@dou-ui/dashboard] the transaction was made on another version of the dashboard',\n )\n if (!tr.changed) return\n const shared = props.collaboration\n if (!shared) {\n history.record(tr)\n historyVersion.value++\n }\n commit(tr)\n shared?.transaction(tr)\n}\n\n/** Steps made into a transaction and dispatched, for the dashboard's own changes. */\nfunction run(steps: DashboardStep[], meta: DashboardTransactionMeta) {\n const tr = applySteps(model.value, steps, meta)\n dispatch(tr)\n return tr\n}\n\nfunction undo() {\n if (props.collaboration) return props.collaboration.undo()\n const tr = history.undo(model.value)\n historyVersion.value++\n if (!tr) return false\n if (tr.changed) commit(tr)\n return true\n}\n\nfunction redo() {\n if (props.collaboration) return props.collaboration.redo()\n const tr = history.redo(model.value)\n historyVersion.value++\n if (!tr) return false\n if (tr.changed) commit(tr)\n return true\n}\n\nconst canUndo = computed(() =>\n props.collaboration ? props.collaboration.canUndo() : (historyVersion.value, history.canUndo),\n)\nconst canRedo = computed(() =>\n props.collaboration ? props.collaboration.canRedo() : (historyVersion.value, history.canRedo),\n)\n\n// ---------------------------------------------------------------------------\n// The grid: widgets as items, places by breakpoint\n\nfunction limitsOf(type: DashboardWidgetType<any> | undefined) {\n const size = type?.size ?? DEFAULT_WIDGET_SIZE\n return {\n w: size.w,\n h: size.h,\n minW: size.minW,\n maxW: size.maxW,\n minH: size.minH,\n maxH: size.maxH,\n }\n}\n\n/** The widgets on the widest breakpoint; those without a place there get the first spot they fit. */\nconst placeOf = (item: DashboardPlace): DashboardPlace => ({\n x: item.x,\n y: item.y,\n w: item.w,\n h: item.h,\n})\n\n// The item of a widget that didn't change is the same object as before, so\n// the grid and its other items have nothing to draw again.\nlet itemCache = new WeakMap<DashboardWidget, GridItem>()\nwatch([typeMap, main], () => (itemCache = new WeakMap()), { flush: 'sync' })\nconst items = computed<GridItem[]>(() => {\n const name = main.value.name\n const options = mainOptions.value\n const placed: GridItem[] = []\n const result = new Map<string, GridItem>()\n const missing: DashboardWidget[] = []\n for (const widget of model.value.widgets) {\n const raw = toRaw(widget)\n const place = raw.place[name]\n if (!place) {\n missing.push(raw)\n continue\n }\n let item = itemCache.get(raw)\n if (!item) {\n const { minW, maxW, minH, maxH } = limitsOf(typeOf(raw))\n item = { id: raw.id, ...placeOf(place), minW, maxW, minH, maxH }\n itemCache.set(raw, item)\n }\n placed.push(item)\n result.set(raw.id, item)\n }\n for (const widget of missing) {\n const fitted = fitItem(\n { id: widget.id, x: 0, y: 0, ...limitsOf(typeOf(widget)) },\n options.columns,\n )\n const item = { ...fitted, ...findPlace(placed, fitted, options) }\n placed.push(item)\n result.set(widget.id, item)\n }\n return model.value.widgets.map((widget) => result.get(widget.id)!)\n})\n\n/** The places on the narrower breakpoints. */\nconst layouts = computed<GridLayouts>(() => {\n const result: GridLayouts = {}\n for (const tier of tiers.value.slice(1)) {\n const list = []\n for (const widget of model.value.widgets) {\n const place = widget.place[tier.name]\n if (place) list.push({ id: widget.id, ...place })\n }\n if (list.length) result[tier.name] = list\n }\n return result\n})\n\n// The grid tells a move with `update:modelValue`, `update:layouts` and\n// `drop` one after another; they are gathered into one change.\nlet pending: { items?: GridItem[]; layouts?: GridLayouts; drop?: { id: string; type: string } } = {}\nlet flushing = false\nfunction gather(part: typeof pending) {\n Object.assign(pending, part)\n if (flushing) return\n flushing = true\n queueMicrotask(flush)\n}\n\nfunction flush() {\n flushing = false\n const { items: list, layouts: saved, drop } = pending\n pending = {}\n const doc = model.value\n const places = new Map<string, DashboardWidget['place']>()\n const setPlace = (id: string, name: string, item: DashboardPlace) => {\n let place = places.get(id)\n if (!place) places.set(id, (place = { ...widgetMap.value.get(id)?.place }))\n place[name] = placeOf(item)\n }\n for (const item of list ?? []) setPlace(item.id, main.value.name, item)\n for (const [name, others] of Object.entries(saved ?? {}))\n for (const item of others ?? []) setPlace(item.id, name, item)\n const steps: DashboardStep[] = []\n for (const [id, place] of places) {\n if (widgetMap.value.has(id)) {\n steps.push({ type: 'update', id, patch: { place } })\n } else if (drop?.id === id) {\n const type = typeMap.value.get(drop.type)\n if (type)\n steps.push({ type: 'add', widget: newWidget(id, type, place), index: doc.widgets.length })\n }\n }\n const tr = run(steps, { origin: drop ? 'drop' : 'layout' })\n if (drop && tr.changed) {\n const widget = widgetMap.value.get(drop.id)\n if (widget) say(format(messages.value.added, { item: titleOf(widget) }))\n }\n}\n\nfunction onDrop(event: GridDropEvent) {\n const data = event.data as { type?: string } | undefined\n if (data?.type) gather({ drop: { id: event.item.id, type: data.type } })\n}\n\nconst dragging = ref(false)\n\n// ---------------------------------------------------------------------------\n// Adding, copying and removing\n\nfunction newId(type: string) {\n const used = widgetMap.value\n for (;;) {\n const id = props.createId?.(type) ?? `${type}-${Math.random().toString(36).slice(2, 8)}`\n if (!used.has(id)) return id\n if (props.createId)\n throw new Error(`[@dou-ui/dashboard] createId gave \"${id}\", which is already in use`)\n }\n}\n\nfunction newWidget(\n id: string,\n type: DashboardWidgetType<any>,\n place: DashboardWidget['place'],\n options: Pick<DashboardAddOptions, 'title' | 'props'> = {},\n): DashboardWidget {\n const widget: DashboardWidget = { id, type: type.type, place }\n if (options.title) widget.title = options.title\n const initial = defaultPropsOf(type)\n if (initial || options.props) widget.props = { ...initial, ...options.props }\n return widget\n}\n\n/**\n * Puts a new item on the widest breakpoint, at `at` (pushing the others\n * away) or in the first spot it fits; the steps also move any widget it\n * pushed.\n */\nfunction placeNew(item: GridItem, at: Pick<DashboardPlace, 'x' | 'y'> | undefined) {\n const options = mainOptions.value\n const fitted = fitItem(item, options.columns)\n const spot = at ?? findPlace(items.value, fitted, options)\n const layout = moveItem([...items.value, { ...fitted, ...spot }], item.id, spot, options)\n const steps: DashboardStep[] = []\n let place: DashboardPlace = { ...fitted, ...spot }\n for (const next of layout) {\n if (next.id === item.id) {\n place = placeOf(next)\n continue\n }\n const widget = widgetMap.value.get(next.id)\n const old = widget && items.value.find((other) => other.id === next.id)\n if (widget && old && (old.x !== next.x || old.y !== next.y))\n steps.push({\n type: 'update',\n id: next.id,\n patch: { place: { ...widget.place, [main.value.name]: placeOf(next) } },\n })\n }\n return { place, steps }\n}\n\nfunction addWidget(typeName: string, options: DashboardAddOptions = {}) {\n const type = typeMap.value.get(typeName)\n if (!type) return undefined\n const id = newId(typeName)\n const { place, steps } = placeNew({ id, x: 0, y: 0, ...limitsOf(type) }, options.at)\n const widget = newWidget(id, type, { [main.value.name]: place }, options)\n run([{ type: 'add', widget, index: model.value.widgets.length }, ...steps], {\n origin: 'api',\n })\n say(format(messages.value.added, { item: titleOf(widget) }))\n if (options.focus !== false) void nextTick(() => focusWidget(id))\n return id\n}\n\n/** Adds a copy of a widget (from this dashboard or the clipboard) beside `near`, or in the first free spot. */\nfunction addCopy(source: DashboardWidget, near?: GridItem) {\n const type = typeOf(source)\n const id = newId(source.type)\n const columns = mainOptions.value.columns\n const size = near ?? { ...limitsOf(type), ...source.place[main.value.name] }\n const at = near\n ? near.x + near.w * 2 <= columns\n ? { x: near.x + near.w, y: near.y }\n : { x: near.x, y: near.y + near.h }\n : undefined\n const { place, steps } = placeNew({ ...limitsOf(type), id, x: 0, y: 0, w: size.w, h: size.h }, at)\n const widget: DashboardWidget = {\n ...cloneJson(source),\n id,\n place: { [main.value.name]: place },\n }\n const index = near\n ? model.value.widgets.findIndex((other) => other.id === near.id) + 1\n : model.value.widgets.length\n run([{ type: 'add', widget, index }, ...steps], { origin: 'copy' })\n say(format(messages.value.added, { item: titleOf(widget) }))\n void nextTick(() => focusWidget(id))\n return id\n}\n\nfunction duplicateWidget(id: string) {\n const widget = widgetMap.value.get(id)\n const item = items.value.find((other) => other.id === id)\n return widget && item ? addCopy(widget, item) : undefined\n}\n\nfunction removeWidget(id: string) {\n const list = model.value.widgets\n const index = list.findIndex((widget) => widget.id === id)\n if (index < 0) return\n const widget = list[index]!\n const hadFocus = root.value?.contains(document.activeElement)\n const neighbour = list[index + 1] ?? list[index - 1]\n if (settingsId.value === id) settingsId.value = undefined\n if (fullscreenId.value === id) fullscreenId.value = undefined\n run([{ type: 'remove', id }], { origin: 'api' })\n say(format(messages.value.removed, { item: titleOf(widget) }))\n if (hadFocus) void nextTick(() => (neighbour ? focusWidget(neighbour.id) : root.value?.focus()))\n}\n\nfunction focusWidget(id: string) {\n grid.value?.focusItem(id)\n}\n\n/** The widget being dragged in, which has no widget yet: its name is its type's. */\nlet adding: { id: string; label: string } | undefined\n\nfunction startAdd(event: PointerEvent, typeName: string) {\n const type = typeMap.value.get(typeName)\n if (!type || !props.editable) return\n adding = { id: newId(typeName), label: type.label }\n grid.value?.startDrop(event, { id: adding.id, ...limitsOf(type) }, { type: typeName })\n}\n\nfunction labelOf(item: GridItem) {\n const widget = widgetMap.value.get(item.id)\n return widget ? titleOf(widget) : item.id === adding?.id ? adding.label : item.id\n}\n\nfunction setProps(id: string, patch: Record<string, unknown>, options: { group?: string } = {}) {\n const widget = widgetMap.value.get(id)\n if (!widget) return\n run([{ type: 'update', id, patch: { props: { ...widget.props, ...patch } } }], {\n origin: 'widget',\n group: options.group,\n })\n}\n\nfunction rename(id: string, title: string) {\n run([{ type: 'update', id, patch: { title: title.trim() || undefined } }], {\n origin: 'title',\n })\n}\n\n// ---------------------------------------------------------------------------\n// Settings and full screen\n\nconst settingsId = ref<string>()\nconst settingsWidget = computed(() =>\n settingsId.value ? widgetMap.value.get(settingsId.value) : undefined,\n)\nconst fullscreenId = ref<string>()\nconst fullscreenWidget = computed(() =>\n fullscreenId.value ? widgetMap.value.get(fullscreenId.value) : undefined,\n)\n/** The widget to give focus back to when a drawer or dialog closes. */\nlet returnTo: string | undefined\n\nfunction openSettings(id: string) {\n if (!props.editable || !widgetMap.value.has(id)) return\n returnTo = id\n settingsId.value = id\n}\n\nfunction openFullscreen(id: string) {\n const widget = widgetMap.value.get(id)\n if (!widget || typeOf(widget)?.fullscreen === false) return\n returnTo = id\n fullscreenId.value = id\n}\n\nfunction restoreFocus() {\n const id = returnTo\n returnTo = undefined\n if (id && widgetMap.value.has(id)) focusWidget(id)\n}\n\nwatch(\n () => props.editable,\n (editable) => {\n if (!editable) settingsId.value = undefined\n },\n)\n\nfunction onAction(id: string, action: WidgetAction) {\n if (action === 'settings') openSettings(id)\n else if (action === 'fullscreen') openFullscreen(id)\n else if (action === 'duplicate') duplicateWidget(id)\n else removeWidget(id)\n}\n\n// ---------------------------------------------------------------------------\n// Editing together\n\n/** Changes from others wait while a widget is dragged here; then the shared document is read again. */\nlet held = false\n\nfunction applyShared(doc: DashboardDocument, origin: 'remote' | 'undo' | 'redo') {\n if (dragging.value) {\n held = true\n return\n }\n const tr = replaceWidgets(new DashboardTransaction(model.value), doc)\n if (!tr.changed) return\n tr.setMeta('history', false).setMeta('origin', origin)\n const ids = new Set(tr.doc.widgets.map((widget) => widget.id))\n if (settingsId.value && !ids.has(settingsId.value)) settingsId.value = undefined\n if (fullscreenId.value && !ids.has(fullscreenId.value)) fullscreenId.value = undefined\n commit(tr)\n}\n\n// one written in the template is new on every render: each one starts over and clears undo\nlet setupTimes: number[] = []\nlet setupWarned = false\nfunction warnRepeatedSetup() {\n if (process.env.NODE_ENV === 'production' || setupWarned) return\n const now = Date.now()\n setupTimes = [...setupTimes.filter((time) => now - time < 1000), now]\n if (setupTimes.length < 4) return\n setupWarned = true\n console.warn(\n '[@dou-ui/dashboard] collaboration keeps being replaced, which starts it over and clears undo each time. Create it once in <script setup> instead of in the template.',\n )\n}\n\nwatch(\n () => props.collaboration,\n (shared, _, onCleanup) => {\n if (shared) warnRepeatedSetup()\n history.clear()\n historyVersion.value++\n if (!shared) return\n const stop = shared.setup({ document: () => model.value, apply: applyShared })\n onCleanup(() => {\n shared.presence?.(null)\n stop()\n })\n },\n { immediate: true },\n)\n\n/** The widget being moved or resized here, and how. */\nconst drag = shallowRef<GridDragEvent>()\nfunction onDragStart(event: GridDragEvent) {\n dragging.value = true\n drag.value = event\n}\nfunction onDragEnd() {\n dragging.value = false\n drag.value = undefined\n if (!held) return\n held = false\n // after the drop's own change, which the grid hands over in a microtask\n queueMicrotask(() => queueMicrotask(() => props.collaboration?.sync()))\n}\n\n/** The widget whose item, or a control in it, has focus. */\nconst focused = ref<string>()\nfunction onFocusIn(event: FocusEvent) {\n const item = (event.target as HTMLElement).closest?.<HTMLElement>('[data-grid-id]')\n focused.value = item?.closest('.dou-dashboard') === root.value ? item?.dataset.gridId : undefined\n}\nfunction onFocusOut(event: FocusEvent) {\n if (!root.value?.contains(event.relatedTarget as Node | null)) focused.value = undefined\n}\n\n/** What this person is doing, shown to the others. */\nconst presence = computed<DashboardPresence | null>(() => {\n const moving = drag.value\n if (moving && moving.kind !== 'drop' && widgetMap.value.has(moving.id))\n return { widget: moving.id, action: moving.kind as DashboardPresenceAction }\n if (settingsId.value) return { widget: settingsId.value, action: 'settings' }\n if (focused.value && widgetMap.value.has(focused.value))\n return { widget: focused.value, action: 'focus' }\n return null\n})\nwatch(\n [presence, () => props.collaboration] as const,\n ([current, shared], previous) => {\n if (!shared?.presence) return\n const [before, sharedBefore] = previous ?? []\n if (\n sharedBefore === shared &&\n before?.widget === current?.widget &&\n before?.action === current?.action\n )\n return\n shared.presence(current)\n },\n { immediate: true },\n)\nconst NO_PEERS: readonly DashboardPeer[] = []\n\nprovide(frameContextKey, {\n widget: (id) => widgetMap.value.get(id),\n type: typeOf,\n title: titleOf,\n editing: () => props.editable,\n messages: () => messages.value,\n eager: isEager,\n all: () => showAll.value,\n setProps,\n action: onAction,\n rename,\n linked: (id) => linksByWidget.value.get(id) ?? NO_LINKS,\n clearFilter: (key) => setFilter(undefined, key, undefined),\n peers: (id) => props.collaboration?.peers?.(id) ?? NO_PEERS,\n setWorkbook,\n exporters: (id) => (workbooks.has(id) ? exporterList.value : NO_EXPORTERS),\n exportTo: exportFromMenu,\n exporting: (id) => exporting.has(id),\n setDrawn: (id, drawn) => (drawn ? drawnIds.add(id) : drawnIds.delete(id)),\n exportError: (id) => exportErrors.get(id),\n clearExportError: (id) => exportErrors.delete(id),\n})\n\n// ---------------------------------------------------------------------------\n// Exporting a widget's content as a file\n\n/** How each mounted widget gives its content as a workbook; see `setWorkbook`. */\nconst workbooks = shallowReactive(new Map<string, () => SheetWorkbook | undefined>())\nfunction setWorkbook(id: string, get: () => SheetWorkbook | undefined) {\n workbooks.set(id, get)\n return () => {\n if (workbooks.get(id) === get) workbooks.delete(id)\n }\n}\n\n// the file code comes with the first exporter, in a chunk of its own\ntype SheetCore = typeof import('@dou-ui/sheet/core')\nconst sheetCore = shallowRef<SheetCore>()\nlet loadingCore: Promise<SheetCore> | undefined\nfunction loadSheetCore() {\n loadingCore ??= import('@dou-ui/sheet/core').then((core) => (sheetCore.value = core))\n return loadingCore\n}\nwatch(\n () => props.exporters?.length,\n (count) => count && void loadSheetCore(),\n { immediate: true },\n)\n\nconst NO_EXPORTERS: FrameExporter[] = []\nconst { locale } = useLocale()\n/** The exporters of the menu, without those that draw a sheet's grid. */\nconst exporterList = computed<FrameExporter[]>(() => {\n const core = sheetCore.value\n if (!core) return NO_EXPORTERS\n const sheetMessages = core.sheetMessagesFor(locale.value.name)\n return (props.exporters ?? [])\n .filter((exporter) => exporter.needs !== 'view')\n .map((exporter) => ({\n name: exporter.name,\n label: typeof exporter.label === 'function' ? exporter.label(sheetMessages) : exporter.label,\n icon: exporter.icon ?? fileIconOf(exporter.extension),\n }))\n})\n\nfunction exporterOf(exporter: string | SheetExporter) {\n if (typeof exporter !== 'string') return exporter\n const found = props.exporters?.find((item) => item.name === exporter)\n if (!found) throw new RangeError(`[@dou-ui/dashboard] there is no exporter \"${exporter}\"`)\n return found\n}\n\n/** A widget's file and sheet name: its title, or its type's name. */\nconst exportNameOf = (widget: DashboardWidget) => titleOf(widget).trim() || widget.type\n\n/** Why something failed, as people read it: a file error's `reason`, or its message. */\nfunction reasonOf(error: unknown) {\n if (error instanceof Error)\n return 'reason' in error && typeof error.reason === 'string' ? error.reason : error.message\n return String(error)\n}\n\n/**\n * A widget's content as a file: its workbook written by the exporter, a\n * name from `exporters` or an exporter itself. Waits for the data shown,\n * and draws the widget first when it is far from the view.\n */\nasync function exportWidget(\n id: string,\n exporter: string | SheetExporter,\n options: DashboardExportOptions = {},\n): Promise<Blob> {\n const chosen = exporterOf(exporter)\n const widget = widgetMap.value.get(id)\n if (!widget) throw new RangeError(`[@dou-ui/dashboard] there is no widget \"${id}\"`)\n const core = await loadSheetCore()\n // a widget drawn already that gives no workbook never will: no need to draw them all\n await settle(5000, !drawnIds.has(id))\n options.signal?.throwIfAborted()\n const get = workbooks.get(id)\n const book = get?.()\n if (!book)\n throw new core.SheetFileError(\n get\n ? `[@dou-ui/dashboard] the widget \"${id}\" has no data to export now`\n : `[@dou-ui/dashboard] the widget \"${id}\" doesn't give its content as a workbook (setWorkbook)`,\n 'empty',\n messages.value.nothingToExport,\n )\n // sheets without a name take the widget's, as the file does\n const name = exportNameOf(widget)\n const named = book.sheets.some((sheet) => !sheet.name)\n ? { ...book, sheets: book.sheets.map((sheet) => (sheet.name ? sheet : { ...sheet, name })) }\n : book\n return core.exportWorkbook(named, chosen, {\n options: options.options,\n signal: options.signal,\n locale: locale.value.name,\n })\n}\n\n/** The widgets drawn in full; the others show only their title until they come near. */\nconst drawnIds = new Set<string>()\n\n/** Why the last export from each widget's menu failed, shown over its content until closed. */\nconst exportErrors = shallowReactive(new Map<string, string>())\n/** The widgets exporting now, whose export button waits. */\nconst exporting = shallowReactive(new Set<string>())\n\n/**\n * Exports a widget's content and saves the file in the downloads, as its\n * menu does. A failure shows over the widget and comes as `exportError`,\n * and the promise rejects.\n */\nasync function saveWidget(\n id: string,\n exporter: string | SheetExporter,\n options: DashboardSaveOptions = {},\n): Promise<void> {\n const chosen = exporterOf(exporter)\n const widget = widgetMap.value.get(id)\n if (!widget) throw new RangeError(`[@dou-ui/dashboard] there is no widget \"${id}\"`)\n const name = options.fileName ?? `${exportNameOf(widget)}.${chosen.extension}`\n exportErrors.delete(id)\n exporting.add(id)\n let blob: Blob\n try {\n blob = await exportWidget(id, chosen, options)\n } catch (error) {\n const event: DashboardExportErrorEvent = {\n widget: id,\n exporter: chosen.name,\n error,\n message: format(messages.value.exportFailed, { name, reason: reasonOf(error) }),\n defaultPrevented: false,\n preventDefault() {\n this.defaultPrevented = true\n },\n }\n emit('exportError', event)\n // the notice is read out itself; told by the app, it is only read out\n if (event.defaultPrevented) say(event.message)\n else exportErrors.set(id, event.message)\n throw error\n } finally {\n exporting.delete(id)\n }\n downloadBlob(blob, name)\n say(format(messages.value.exported, { name }))\n}\n\nfunction exportFromMenu(id: string, name: string) {\n if (exporting.has(id)) return\n // the failure is shown over the widget\n saveWidget(id, name).catch(() => {})\n}\n\n// ---------------------------------------------------------------------------\n// Data and filters\n\nconst store = createSourceStore(() => props.sources)\nonBeforeUnmount(() => store.dispose())\n// a source replaced by one that loads differently loads again\nwatch(\n () => props.sources,\n (next, previous) => {\n const names = new Set([...Object.keys(previous ?? {}), ...Object.keys(next ?? {})])\n for (const name of names) if (!sameSource(previous?.[name], next?.[name])) store.reload(name)\n },\n)\n\n/** The filters set by widgets, by key: they apply to every widget but the one that set them. */\nconst links = shallowRef(new Map<string, DashboardLinkedFilter>())\nconst EMPTY_SET: ReadonlySet<string> = new Set()\nconst NO_LINKS: DashboardLinkedFilter[] = []\nconst linksByWidget = computed(() => {\n const result = new Map<string, DashboardLinkedFilter[]>()\n for (const link of links.value.values()) {\n const list = result.get(link.widget)\n if (list) list.push(link)\n else result.set(link.widget, [link])\n }\n return result\n})\nconst emittedByWidget = computed(() => {\n const result = new Map<string, Set<string>>()\n for (const [id, list] of linksByWidget.value)\n result.set(id, new Set(list.map((link) => link.key)))\n return result\n})\n\n// The values as set last: with `v-model:filters` the model only updates once\n// the parent draws again, so several filters set at once build on this.\nconst values = shallowRef<DashboardFilters>(filters.value)\nwatch(filters, (next) => (values.value = next))\n\nfunction setFilter(\n widget: string | undefined,\n key: string,\n value: unknown,\n options: DashboardSetFilterOptions = {},\n) {\n const next = { ...values.value }\n const nextLinks = new Map(links.value)\n if (isEmptyFilter(value)) delete next[key]\n else next[key] = value\n if (widget && !isEmptyFilter(value) && widgetMap.value.has(widget))\n nextLinks.set(key, { key, value, label: options.label ?? filterText(value), widget })\n else nextLinks.delete(key)\n links.value = nextLinks\n values.value = next\n filters.value = next\n}\n\nfunction clearFilters() {\n links.value = new Map()\n values.value = {}\n filters.value = {}\n}\n\n// a filter changed from outside (the filter bar, the app) is no longer the widget's\nwatch(filters, (current) => {\n let next: Map<string, DashboardLinkedFilter> | undefined\n for (const [key, link] of links.value) {\n if (sameValue(current[key], link.value)) continue\n next ??= new Map(links.value)\n next.delete(key)\n }\n if (next) links.value = next\n})\n\n// the filters of a widget that is removed go with it\nwatch(widgetMap, (map) => {\n for (const link of links.value.values())\n if (!map.has(link.widget)) setFilter(undefined, link.key, undefined)\n})\n\nconst sourceNames = computed(() => Object.keys(props.sources ?? {}))\nprovide(dataContextKey, {\n store,\n sourceNames: () => sourceNames.value,\n filters: () => values.value,\n emitted: (id) => emittedByWidget.value.get(id) ?? EMPTY_SET,\n linked: (id) => linksByWidget.value.get(id) ?? NO_LINKS,\n setFilter,\n})\n\n// ---------------------------------------------------------------------------\n// Keyboard and clipboard\n\nconst CLIPBOARD_KIND = 'dou-dashboard/widget'\n\nfunction isTyping(target: EventTarget | null) {\n const el = target as HTMLElement | null\n if (!el?.tagName) return false\n return (\n el.isContentEditable ||\n el.tagName === 'TEXTAREA' ||\n el.tagName === 'SELECT' ||\n (el.tagName === 'INPUT' &&\n !['checkbox', 'radio', 'button', 'range', 'color'].includes((el as HTMLInputElement).type))\n )\n}\n\n/** The widget whose item itself has focus (not a control inside it). */\nfunction focusedId(target: EventTarget | null) {\n const el = target as HTMLElement | null\n return el?.dataset?.gridId && el.closest('.dou-dashboard') === root.value\n ? el.dataset.gridId\n : undefined\n}\n\nfunction onKeydown(event: KeyboardEvent) {\n if (!props.editable || event.defaultPrevented || isTyping(event.target)) return\n if (matchesHotkey(event, 'mod+z')) {\n event.preventDefault()\n undo()\n return\n }\n if (matchesHotkey(event, 'mod+shift+z') || matchesHotkey(event, 'mod+y')) {\n event.preventDefault()\n redo()\n return\n }\n const id = focusedId(event.target)\n if (!id || dragging.value) return\n if (event.key === 'Delete' || event.key === 'Backspace') {\n event.preventDefault()\n removeWidget(id)\n } else if (matchesHotkey(event, 'mod+d')) {\n event.preventDefault()\n duplicateWidget(id)\n }\n}\n\nfunction onCopy(event: ClipboardEvent, cut = false) {\n if (isTyping(event.target)) return\n const id = focusedId(event.target)\n const widget = id && widgetMap.value.get(id)\n if (!widget || !event.clipboardData) return\n event.clipboardData.setData('text/plain', JSON.stringify({ kind: CLIPBOARD_KIND, widget }))\n event.preventDefault()\n if (cut && props.editable) removeWidget(widget.id)\n}\n\nfunction onPaste(event: ClipboardEvent) {\n if (!props.editable || isTyping(event.target)) return\n let data: { kind?: string; widget?: DashboardWidget } | undefined\n try {\n data = JSON.parse(event.clipboardData?.getData('text/plain') ?? '')\n } catch {\n return\n }\n const widget = data?.kind === CLIPBOARD_KIND ? data.widget : undefined\n if (!widget?.type || !typeMap.value.has(widget.type)) return\n event.preventDefault()\n const id = focusedId(event.target)\n addCopy(widget, id ? items.value.find((item) => item.id === id) : undefined)\n}\n\n// ---------------------------------------------------------------------------\n// Export and print\n\n/** Every widget drawn in full, not only those near the view. */\nconst showAll = ref(false)\nconst nextFrame = () => new Promise<void>((resolve) => requestAnimationFrame(() => resolve()))\n\n/** Draws every widget (unless `all` is false) and waits for their sources (at most `timeout` ms) and one more frame. */\nasync function settle(timeout = 5000, all = true) {\n if (all) showAll.value = true\n await nextTick()\n const start = performance.now()\n await nextFrame()\n while (store.busy() && performance.now() - start < timeout) await nextFrame()\n await nextFrame()\n await nextFrame()\n}\n\n/** The dashboard as it is shown, every widget drawn, as a PNG image. */\nasync function exportPNG(options?: DashboardImageOptions) {\n await settle()\n const el = root.value!\n // the controls are left out; the copy is made before the class goes\n el.classList.add('is-exporting')\n try {\n return elementToPNG(el, options)\n } finally {\n el.classList.remove('is-exporting')\n }\n}\n\n/** Draws every widget, waits for their data, and opens the browser's print dialog. */\nasync function print() {\n await settle()\n window.print()\n}\n\n// printing from the browser's menu: draw what is far from the view, as far as there is time\nfunction onBeforePrint() {\n showAll.value = true\n}\nonMounted(() => window.addEventListener('beforeprint', onBeforePrint))\nonBeforeUnmount(() => window.removeEventListener('beforeprint', onBeforePrint))\n\n// ---------------------------------------------------------------------------\n// Announcements\n\nconst live = ref('')\nfunction say(text: string) {\n live.value = ''\n requestAnimationFrame(() => (live.value = text))\n}\n\nconst breakpointName = shallowRef(main.value.name)\nfunction onBreakpoint(name: string) {\n breakpointName.value = name\n emit('breakpointChange', name)\n}\n\nconst minHeight = computed(() => {\n const gap = Array.isArray(props.gap) ? props.gap[1] : props.gap\n return `${props.rowHeight * 3 + gap * 2}px`\n})\n\ndefineExpose<DashboardExpose>({\n transaction,\n dispatch,\n undo,\n redo,\n get canUndo() {\n return canUndo.value\n },\n get canRedo() {\n return canRedo.value\n },\n addWidget,\n removeWidget,\n duplicateWidget,\n focusWidget,\n openSettings,\n openFullscreen,\n startAdd,\n get types() {\n return props.widgets\n },\n get breakpoint() {\n return grid.value?.breakpoint ?? breakpointName.value\n },\n get editable() {\n return props.editable\n },\n get document() {\n return model.value\n },\n get filters() {\n return values.value\n },\n get linkedFilters() {\n return [...links.value.values()]\n },\n setFilter: (key, value, options) => setFilter(undefined, key, value, options),\n clearFilters,\n refresh: (source) => store.reload(source),\n exportPNG,\n exportWidget: exportWidget as DashboardExpose['exportWidget'],\n saveWidget: saveWidget as DashboardExpose['saveWidget'],\n print,\n})\n</script>\n\n<template>\n <div\n ref=\"root\"\n :class=\"['dou-dashboard', editable && 'is-editable']\"\n role=\"region\"\n :aria-label=\"label || messages.dashboard\"\n tabindex=\"-1\"\n @keydown=\"onKeydown\"\n @copy=\"onCopy($event)\"\n @cut=\"onCopy($event, true)\"\n @paste=\"onPaste\"\n @focusin=\"onFocusIn\"\n @focusout=\"onFocusOut\"\n >\n <GridLayout\n ref=\"grid\"\n :model-value=\"items\"\n :layouts=\"layouts\"\n :columns=\"columns\"\n :breakpoints=\"breakpoints\"\n :row-height=\"rowHeight\"\n :gap=\"gap\"\n :compact=\"compact\"\n :editable=\"editable\"\n :handle=\"true\"\n :item-label=\"labelOf\"\n :messages=\"messages\"\n :style=\"editable && !model.widgets.length ? { minHeight } : undefined\"\n @update:model-value=\"(list) => gather({ items: list })\"\n @update:layouts=\"(next) => gather({ layouts: next })\"\n @drop=\"onDrop\"\n @drag-start=\"onDragStart\"\n @drag-end=\"onDragEnd\"\n @breakpoint-change=\"onBreakpoint\"\n >\n <template #item=\"{ item }\">\n <WidgetFrame :id=\"item.id\" />\n </template>\n </GridLayout>\n <div v-if=\"editable && !model.widgets.length\" class=\"dou-dashboard__empty\">\n <DouEmpty size=\"sm\" :title=\"messages.emptyTitle\" :description=\"messages.emptyHint\">\n <template #icon><LayoutGridPlusIcon /></template>\n </DouEmpty>\n </div>\n <div class=\"dou-dashboard__live\" aria-live=\"polite\" aria-atomic=\"true\">{{ live }}</div>\n <DouDrawer\n :open=\"!!settingsWidget\"\n :title=\"\n settingsWidget ? format(messages.settingsTitle, { title: titleOf(settingsWidget) }) : ''\n \"\n :size=\"360\"\n @update:open=\"(open) => !open && (settingsId = undefined)\"\n @closed=\"restoreFocus\"\n >\n <div v-if=\"settingsWidget\" class=\"dou-dashboard__settings\">\n <label class=\"dou-dashboard__field\">\n <span class=\"dou-dashboard__field-label\">{{ messages.titleField }}</span>\n <DouInput\n :model-value=\"settingsWidget.title ?? ''\"\n :placeholder=\"typeOf(settingsWidget)?.label ?? settingsWidget.type\"\n @update:model-value=\"\n (value) =>\n run(\n [\n {\n type: 'update',\n id: settingsWidget!.id,\n patch: { title: value || undefined },\n },\n ],\n { origin: 'settings', group: `title:${settingsWidget!.id}` },\n )\n \"\n />\n </label>\n <WidgetSettings\n v-if=\"typeOf(settingsWidget)?.settings\"\n :widget=\"settingsWidget\"\n :type=\"typeOf(settingsWidget)!\"\n :set-props=\"setProps\"\n @update:props=\"\n (value) =>\n run([{ type: 'update', id: settingsWidget!.id, patch: { props: value } }], {\n origin: 'settings',\n group: `props:${settingsWidget!.id}`,\n })\n \"\n />\n </div>\n </DouDrawer>\n <DouDialog\n :open=\"!!fullscreenWidget\"\n :title=\"fullscreenWidget ? titleOf(fullscreenWidget) : ''\"\n fullscreen\n @update:open=\"(open) => !open && (fullscreenId = undefined)\"\n @closed=\"restoreFocus\"\n >\n <WidgetBody\n v-if=\"fullscreenWidget\"\n class=\"is-fullscreen\"\n :widget=\"fullscreenWidget\"\n :type=\"typeOf(fullscreenWidget)\"\n :editing=\"false\"\n fullscreen\n :messages=\"messages\"\n :set-props=\"setProps\"\n />\n </DouDialog>\n </div>\n</template>\n"],"mappings":"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"}
@@ -0,0 +1,77 @@
1
+ import { fitPixelRatio, svgToPNG } from "dou-ui";
2
+ // src/dashboard/export.ts
3
+ var SKIP = /* @__PURE__ */ new Set([
4
+ "SCRIPT",
5
+ "STYLE",
6
+ "LINK",
7
+ "TEMPLATE",
8
+ "NOSCRIPT"
9
+ ]);
10
+ /** Every computed property of an element, as an inline style. */
11
+ function styleText(el) {
12
+ const style = getComputedStyle(el);
13
+ let text = "";
14
+ for (let i = 0; i < style.length; i++) {
15
+ const name = style[i];
16
+ text += `${name}:${style.getPropertyValue(name)};`;
17
+ }
18
+ return `${text}scrollbar-width:none;`;
19
+ }
20
+ /**
21
+ * A copy of an element and what is in it, each with its computed style
22
+ * inline, so it looks the same without the page's stylesheets; canvases
23
+ * become images of what they show.
24
+ */
25
+ function cloneStyled(source) {
26
+ if (source.nodeType === Node.TEXT_NODE) return source.cloneNode();
27
+ if (source.nodeType !== Node.ELEMENT_NODE) return null;
28
+ const el = source;
29
+ if (SKIP.has(el.tagName)) return null;
30
+ if (el.tagName === "CANVAS") {
31
+ const img = document.createElement("img");
32
+ try {
33
+ img.src = el.toDataURL();
34
+ } catch {}
35
+ img.setAttribute("style", styleText(el));
36
+ return img;
37
+ }
38
+ const copy = el.cloneNode(false);
39
+ copy.setAttribute("style", styleText(el));
40
+ if (el.tagName === "INPUT" || el.tagName === "TEXTAREA") {
41
+ const value = el.value;
42
+ if (el.tagName === "TEXTAREA") copy.textContent = value;
43
+ else copy.setAttribute("value", value);
44
+ }
45
+ for (const child of el.childNodes) {
46
+ const next = cloneStyled(child);
47
+ if (next) copy.appendChild(next);
48
+ }
49
+ return copy;
50
+ }
51
+ var PNG_SOURCE = {
52
+ scope: "@dou-ui/dashboard",
53
+ hint: "use print(), which splits it into pages"
54
+ };
55
+ /** Draws an element as it is shown into a PNG image. */
56
+ async function elementToPNG(el, options = {}) {
57
+ const { pixelRatio, background = true, padding = 16 } = options;
58
+ const width = el.scrollWidth;
59
+ const height = el.scrollHeight;
60
+ const outerWidth = width + padding * 2;
61
+ const outerHeight = height + padding * 2;
62
+ fitPixelRatio(outerWidth, outerHeight, pixelRatio, PNG_SOURCE);
63
+ const copy = cloneStyled(el);
64
+ copy.style.margin = "0";
65
+ copy.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
66
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${outerWidth}" height="${outerHeight}"><foreignObject x="${padding}" y="${padding}" width="${width}" height="${height}">${new XMLSerializer().serializeToString(copy)}</foreignObject></svg>`;
67
+ return svgToPNG(svg, {
68
+ width: outerWidth,
69
+ height: outerHeight,
70
+ pixelRatio,
71
+ ...PNG_SOURCE,
72
+ background: background ? getComputedStyle(document.body).backgroundColor || "#fff" : void 0
73
+ });
74
+ }
75
+ export { elementToPNG };
76
+
77
+ //# sourceMappingURL=export.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export.js","names":[],"sources":["../../src/dashboard/export.ts"],"sourcesContent":["import { fitPixelRatio, svgToPNG } from 'dou-ui'\n\n/** Options of a dashboard's `exportPNG`. */\nexport interface DashboardImageOptions {\n /** Pixels per CSS px. 2 by default, sharp on most screens. */\n pixelRatio?: number\n /** Fill the background with the page's color. On by default; `false` leaves it clear. */\n background?: boolean\n /** Room around the dashboard, in px. 16 by default. */\n padding?: number\n}\n\nconst SKIP = new Set(['SCRIPT', 'STYLE', 'LINK', 'TEMPLATE', 'NOSCRIPT'])\n\n/** Every computed property of an element, as an inline style. */\nfunction styleText(el: Element) {\n const style = getComputedStyle(el)\n let text = ''\n for (let i = 0; i < style.length; i++) {\n const name = style[i]!\n text += `${name}:${style.getPropertyValue(name)};`\n }\n // no scrollbars in the picture\n return `${text}scrollbar-width:none;`\n}\n\n/**\n * A copy of an element and what is in it, each with its computed style\n * inline, so it looks the same without the page's stylesheets; canvases\n * become images of what they show.\n */\nfunction cloneStyled(source: Node): Node | null {\n if (source.nodeType === Node.TEXT_NODE) return source.cloneNode()\n if (source.nodeType !== Node.ELEMENT_NODE) return null\n const el = source as HTMLElement\n if (SKIP.has(el.tagName)) return null\n if (el.tagName === 'CANVAS') {\n const img = document.createElement('img')\n try {\n img.src = (el as HTMLCanvasElement).toDataURL()\n } catch {\n // a canvas with pixels from elsewhere can't be read: leave it blank\n }\n img.setAttribute('style', styleText(el))\n return img\n }\n const copy = el.cloneNode(false) as HTMLElement\n copy.setAttribute('style', styleText(el))\n if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') {\n const value = (el as HTMLInputElement).value\n if (el.tagName === 'TEXTAREA') copy.textContent = value\n else copy.setAttribute('value', value)\n }\n for (const child of el.childNodes) {\n const next = cloneStyled(child)\n if (next) copy.appendChild(next)\n }\n return copy\n}\n\nconst PNG_SOURCE = { scope: '@dou-ui/dashboard', hint: 'use print(), which splits it into pages' }\n\n/** Draws an element as it is shown into a PNG image. */\nexport async function elementToPNG(el: HTMLElement, options: DashboardImageOptions = {}) {\n const { pixelRatio, background = true, padding = 16 } = options\n const width = el.scrollWidth\n const height = el.scrollHeight\n const outerWidth = width + padding * 2\n const outerHeight = height + padding * 2\n // too big for a canvas: fail before copying every element's style\n fitPixelRatio(outerWidth, outerHeight, pixelRatio, PNG_SOURCE)\n const copy = cloneStyled(el) as HTMLElement\n copy.style.margin = '0'\n copy.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml')\n const html = new XMLSerializer().serializeToString(copy)\n const svg =\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${outerWidth}\" height=\"${outerHeight}\">` +\n `<foreignObject x=\"${padding}\" y=\"${padding}\" width=\"${width}\" height=\"${height}\">${html}</foreignObject></svg>`\n return svgToPNG(svg, {\n width: outerWidth,\n height: outerHeight,\n pixelRatio,\n ...PNG_SOURCE,\n background: background ? getComputedStyle(document.body).backgroundColor || '#fff' : undefined,\n })\n}\n"],"mappings":";;AAYA,IAAM,uBAAO,IAAI,IAAI;CAAC;CAAU;CAAS;CAAQ;CAAY;AAAU,CAAC;;AAGxE,SAAS,UAAU,IAAa;CAC9B,MAAM,QAAQ,iBAAiB,EAAE;CACjC,IAAI,OAAO;CACX,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;EACrC,MAAM,OAAO,MAAM;EACnB,QAAQ,GAAG,KAAK,GAAG,MAAM,iBAAiB,IAAI,EAAE;CAClD;CAEA,OAAO,GAAG,KAAK;AACjB;;;;;;AAOA,SAAS,YAAY,QAA2B;CAC9C,IAAI,OAAO,aAAa,KAAK,WAAW,OAAO,OAAO,UAAU;CAChE,IAAI,OAAO,aAAa,KAAK,cAAc,OAAO;CAClD,MAAM,KAAK;CACX,IAAI,KAAK,IAAI,GAAG,OAAO,GAAG,OAAO;CACjC,IAAI,GAAG,YAAY,UAAU;EAC3B,MAAM,MAAM,SAAS,cAAc,KAAK;EACxC,IAAI;GACF,IAAI,MAAO,GAAyB,UAAU;EAChD,QAAQ,CAER;EACA,IAAI,aAAa,SAAS,UAAU,EAAE,CAAC;EACvC,OAAO;CACT;CACA,MAAM,OAAO,GAAG,UAAU,KAAK;CAC/B,KAAK,aAAa,SAAS,UAAU,EAAE,CAAC;CACxC,IAAI,GAAG,YAAY,WAAW,GAAG,YAAY,YAAY;EACvD,MAAM,QAAS,GAAwB;EACvC,IAAI,GAAG,YAAY,YAAY,KAAK,cAAc;OAC7C,KAAK,aAAa,SAAS,KAAK;CACvC;CACA,KAAK,MAAM,SAAS,GAAG,YAAY;EACjC,MAAM,OAAO,YAAY,KAAK;EAC9B,IAAI,MAAM,KAAK,YAAY,IAAI;CACjC;CACA,OAAO;AACT;AAEA,IAAM,aAAa;CAAE,OAAO;CAAqB,MAAM;AAA0C;;AAGjG,eAAsB,aAAa,IAAiB,UAAiC,CAAC,GAAG;CACvF,MAAM,EAAE,YAAY,aAAa,MAAM,UAAU,OAAO;CACxD,MAAM,QAAQ,GAAG;CACjB,MAAM,SAAS,GAAG;CAClB,MAAM,aAAa,QAAQ,UAAU;CACrC,MAAM,cAAc,SAAS,UAAU;CAEvC,cAAc,YAAY,aAAa,YAAY,UAAU;CAC7D,MAAM,OAAO,YAAY,EAAE;CAC3B,KAAK,MAAM,SAAS;CACpB,KAAK,aAAa,SAAS,8BAA8B;CAEzD,MAAM,MACJ,kDAAkD,WAAW,YAAY,YAAY,sBAChE,QAAQ,OAAO,QAAQ,WAAW,MAAM,YAAY,OAAO,IAHrE,IAAI,cAAc,CAAC,CAAC,kBAAkB,IAGmC,EAAK;CAC3F,OAAO,SAAS,KAAK;EACnB,OAAO;EACP,QAAQ;EACR;EACA,GAAG;EACH,YAAY,aAAa,iBAAiB,SAAS,IAAI,CAAC,CAAC,mBAAmB,SAAS,KAAA;CACvF,CAAC;AACH"}
@@ -0,0 +1,6 @@
1
+ import filter_bar_vue_vue_type_script_setup_true_lang_default from "./filter-bar.vue_vue_type_script_setup_true_lang.js";
2
+ // src/dashboard/filter-bar.vue
3
+ var filter_bar_default = filter_bar_vue_vue_type_script_setup_true_lang_default;
4
+ export { filter_bar_default as default };
5
+
6
+ //# sourceMappingURL=filter-bar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter-bar.js","names":["_sfc_main"],"sources":["../../src/dashboard/filter-bar.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { SearchIcon } from '@dou-ui/icons'\nimport { DouButton, DouDatePicker, DouInput, DouSelect, DouTag } from 'dou-ui'\nimport { computed, onBeforeUnmount, ref, useId, watch } from 'vue'\nimport { isEmptyFilter } from '../data/filters'\nimport type { DashboardFilterField } from '../data/types'\nimport { format, useDashboardMessages } from '../locale'\nimport type { DashboardFilterBarProps } from './types'\n\ndefineOptions({ name: 'DouDashboardFilterBar' })\n\nconst props = withDefaults(defineProps<DashboardFilterBarProps>(), {\n for: null,\n fields: () => [],\n clearable: true,\n})\ndefineSlots<{\n /** More controls at the end of the bar, e.g. a refresh button. */\n default?: () => unknown\n}>()\n\nconst messages = useDashboardMessages(() => props.messages)\nconst id = useId()\nconst dashboard = computed(() => props.for)\nconst values = computed(() => dashboard.value?.filters ?? {})\nconst fieldKeys = computed(() => new Set(props.fields.map((field) => field.key)))\n/** Filters set by widgets that no field shows. */\nconst linked = computed(() =>\n (dashboard.value?.linkedFilters ?? []).filter((link) => !fieldKeys.value.has(link.key)),\n)\nconst titleOf = (widgetId: string) => {\n const widget = dashboard.value?.document.widgets.find((item) => item.id === widgetId)\n const type = widget && dashboard.value?.types.find((item) => item.type === widget.type)\n return widget?.title || type?.label || widgetId\n}\nconst anySet = computed(() => Object.values(values.value).some((value) => !isEmptyFilter(value)))\n\nconst set = (key: string, value: unknown) => dashboard.value?.setFilter(key, value)\n\n// search fields apply once typing pauses\nconst typed = ref<Record<string, string>>({})\nconst timers = new Map<string, ReturnType<typeof setTimeout>>()\nwatch(\n values,\n (next) => {\n for (const field of props.fields)\n if (field.type === 'search' && !timers.has(field.key))\n typed.value[field.key] =\n typeof next[field.key] === 'string' ? (next[field.key] as string) : ''\n },\n { immediate: true },\n)\nfunction onType(field: DashboardFilterField, text: string) {\n typed.value[field.key] = text\n clearTimeout(timers.get(field.key))\n timers.set(\n field.key,\n setTimeout(() => {\n timers.delete(field.key)\n set(field.key, text.trim())\n }, 300),\n )\n}\nonBeforeUnmount(() => timers.forEach((timer) => clearTimeout(timer)))\n</script>\n\n<template>\n <div class=\"dou-dashboard-filter\" role=\"group\" :aria-label=\"messages.filterBar\">\n <div\n v-for=\"field in fields\"\n :key=\"field.key\"\n class=\"dou-dashboard-filter__field\"\n role=\"group\"\n :aria-labelledby=\"`${id}-${field.key}`\"\n >\n <span :id=\"`${id}-${field.key}`\" class=\"dou-dashboard-filter__label\">{{ field.label }}</span>\n <DouSelect\n v-if=\"field.type === 'select'\"\n class=\"dou-dashboard-filter__control\"\n size=\"sm\"\n :model-value=\"(values[field.key] as any) ?? (field.multiple ? [] : null)\"\n :options=\"field.options\"\n :multiple=\"field.multiple\"\n :max-tag-count=\"field.multiple ? 1 : undefined\"\n :placeholder=\"field.placeholder\"\n :aria-label=\"field.label\"\n clearable\n @update:model-value=\"(value) => set(field.key, value)\"\n />\n <DouDatePicker\n v-else-if=\"field.type === 'date-range'\"\n class=\"dou-dashboard-filter__control is-range\"\n size=\"sm\"\n range\n clearable\n :model-value=\"(values[field.key] as any) ?? null\"\n @update:model-value=\"(value) => set(field.key, value)\"\n />\n <DouInput\n v-else\n class=\"dou-dashboard-filter__control\"\n size=\"sm\"\n clearable\n :model-value=\"typed[field.key] ?? ''\"\n :placeholder=\"field.placeholder\"\n :aria-label=\"field.label\"\n @update:model-value=\"(value) => onType(field, value)\"\n >\n <template #prefix><SearchIcon /></template>\n </DouInput>\n </div>\n <DouTag\n v-for=\"link in linked\"\n :key=\"link.key\"\n class=\"dou-dashboard-filter__link\"\n closable\n :close-label=\"format(messages.clearFilter, { value: link.label })\"\n @close=\"set(link.key, undefined)\"\n >\n {{ format(messages.linkedFilter, { title: titleOf(link.widget), value: link.label }) }}\n </DouTag>\n <DouButton\n v-if=\"clearable && anySet\"\n class=\"dou-dashboard-filter__clear\"\n size=\"sm\"\n variant=\"ghost\"\n @click=\"dashboard?.clearFilters()\"\n >\n {{ messages.clearFilters }}\n </DouButton>\n <div v-if=\"$slots.default\" class=\"dou-dashboard-filter__extra\"><slot /></div>\n </div>\n</template>\n"],"mappings":""}
@@ -0,0 +1,150 @@
1
+ import { format, useDashboardMessages } from "../locale.js";
2
+ import { isEmptyFilter } from "../data/filters.js";
3
+ import { DouButton, DouDatePicker, DouInput, DouSelect, DouTag } from "dou-ui";
4
+ import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, onBeforeUnmount, openBlock, ref, renderList, renderSlot, toDisplayString, unref, useId, watch, withCtx } from "vue";
5
+ import { SearchIcon } from "@dou-ui/icons";
6
+ // src/dashboard/filter-bar.vue?vue&type=script&setup=true&lang.ts
7
+ var _hoisted_1 = ["aria-label"];
8
+ var _hoisted_2 = ["aria-labelledby"];
9
+ var _hoisted_3 = ["id"];
10
+ var _hoisted_4 = {
11
+ key: 1,
12
+ class: "dou-dashboard-filter__extra"
13
+ };
14
+ var filter_bar_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
15
+ name: "DouDashboardFilterBar",
16
+ __name: "filter-bar",
17
+ props: {
18
+ for: { default: null },
19
+ fields: { default: () => [] },
20
+ clearable: {
21
+ type: Boolean,
22
+ default: true
23
+ },
24
+ messages: {}
25
+ },
26
+ setup(__props) {
27
+ const props = __props;
28
+ const messages = useDashboardMessages(() => props.messages);
29
+ const id = useId();
30
+ const dashboard = computed(() => props.for);
31
+ const values = computed(() => dashboard.value?.filters ?? {});
32
+ const fieldKeys = computed(() => new Set(props.fields.map((field) => field.key)));
33
+ /** Filters set by widgets that no field shows. */
34
+ const linked = computed(() => (dashboard.value?.linkedFilters ?? []).filter((link) => !fieldKeys.value.has(link.key)));
35
+ const titleOf = (widgetId) => {
36
+ const widget = dashboard.value?.document.widgets.find((item) => item.id === widgetId);
37
+ const type = widget && dashboard.value?.types.find((item) => item.type === widget.type);
38
+ return widget?.title || type?.label || widgetId;
39
+ };
40
+ const anySet = computed(() => Object.values(values.value).some((value) => !isEmptyFilter(value)));
41
+ const set = (key, value) => dashboard.value?.setFilter(key, value);
42
+ const typed = ref({});
43
+ const timers = /* @__PURE__ */ new Map();
44
+ watch(values, (next) => {
45
+ for (const field of props.fields) if (field.type === "search" && !timers.has(field.key)) typed.value[field.key] = typeof next[field.key] === "string" ? next[field.key] : "";
46
+ }, { immediate: true });
47
+ function onType(field, text) {
48
+ typed.value[field.key] = text;
49
+ clearTimeout(timers.get(field.key));
50
+ timers.set(field.key, setTimeout(() => {
51
+ timers.delete(field.key);
52
+ set(field.key, text.trim());
53
+ }, 300));
54
+ }
55
+ onBeforeUnmount(() => timers.forEach((timer) => clearTimeout(timer)));
56
+ return (_ctx, _cache) => {
57
+ return openBlock(), createElementBlock("div", {
58
+ class: "dou-dashboard-filter",
59
+ role: "group",
60
+ "aria-label": unref(messages).filterBar
61
+ }, [
62
+ (openBlock(true), createElementBlock(Fragment, null, renderList(__props.fields, (field) => {
63
+ return openBlock(), createElementBlock("div", {
64
+ key: field.key,
65
+ class: "dou-dashboard-filter__field",
66
+ role: "group",
67
+ "aria-labelledby": `${unref(id)}-${field.key}`
68
+ }, [createElementVNode("span", {
69
+ id: `${unref(id)}-${field.key}`,
70
+ class: "dou-dashboard-filter__label"
71
+ }, toDisplayString(field.label), 9, _hoisted_3), field.type === "select" ? (openBlock(), createBlock(unref(DouSelect), {
72
+ key: 0,
73
+ class: "dou-dashboard-filter__control",
74
+ size: "sm",
75
+ "model-value": values.value[field.key] ?? (field.multiple ? [] : null),
76
+ options: field.options,
77
+ multiple: field.multiple,
78
+ "max-tag-count": field.multiple ? 1 : void 0,
79
+ placeholder: field.placeholder,
80
+ "aria-label": field.label,
81
+ clearable: "",
82
+ "onUpdate:modelValue": (value) => set(field.key, value)
83
+ }, null, 8, [
84
+ "model-value",
85
+ "options",
86
+ "multiple",
87
+ "max-tag-count",
88
+ "placeholder",
89
+ "aria-label",
90
+ "onUpdate:modelValue"
91
+ ])) : field.type === "date-range" ? (openBlock(), createBlock(unref(DouDatePicker), {
92
+ key: 1,
93
+ class: "dou-dashboard-filter__control is-range",
94
+ size: "sm",
95
+ range: "",
96
+ clearable: "",
97
+ "model-value": values.value[field.key] ?? null,
98
+ "onUpdate:modelValue": (value) => set(field.key, value)
99
+ }, null, 8, ["model-value", "onUpdate:modelValue"])) : (openBlock(), createBlock(unref(DouInput), {
100
+ key: 2,
101
+ class: "dou-dashboard-filter__control",
102
+ size: "sm",
103
+ clearable: "",
104
+ "model-value": typed.value[field.key] ?? "",
105
+ placeholder: field.placeholder,
106
+ "aria-label": field.label,
107
+ "onUpdate:modelValue": (value) => onType(field, value)
108
+ }, {
109
+ prefix: withCtx(() => [createVNode(unref(SearchIcon))]),
110
+ _: 1
111
+ }, 8, [
112
+ "model-value",
113
+ "placeholder",
114
+ "aria-label",
115
+ "onUpdate:modelValue"
116
+ ]))], 8, _hoisted_2);
117
+ }), 128)),
118
+ (openBlock(true), createElementBlock(Fragment, null, renderList(linked.value, (link) => {
119
+ return openBlock(), createBlock(unref(DouTag), {
120
+ key: link.key,
121
+ class: "dou-dashboard-filter__link",
122
+ closable: "",
123
+ "close-label": unref(format)(unref(messages).clearFilter, { value: link.label }),
124
+ onClose: ($event) => set(link.key, void 0)
125
+ }, {
126
+ default: withCtx(() => [createTextVNode(toDisplayString(unref(format)(unref(messages).linkedFilter, {
127
+ title: titleOf(link.widget),
128
+ value: link.label
129
+ })), 1)]),
130
+ _: 2
131
+ }, 1032, ["close-label", "onClose"]);
132
+ }), 128)),
133
+ __props.clearable && anySet.value ? (openBlock(), createBlock(unref(DouButton), {
134
+ key: 0,
135
+ class: "dou-dashboard-filter__clear",
136
+ size: "sm",
137
+ variant: "ghost",
138
+ onClick: _cache[0] || (_cache[0] = ($event) => dashboard.value?.clearFilters())
139
+ }, {
140
+ default: withCtx(() => [createTextVNode(toDisplayString(unref(messages).clearFilters), 1)]),
141
+ _: 1
142
+ })) : createCommentVNode("", true),
143
+ _ctx.$slots.default ? (openBlock(), createElementBlock("div", _hoisted_4, [renderSlot(_ctx.$slots, "default")])) : createCommentVNode("", true)
144
+ ], 8, _hoisted_1);
145
+ };
146
+ }
147
+ });
148
+ export { filter_bar_vue_vue_type_script_setup_true_lang_default as default };
149
+
150
+ //# sourceMappingURL=filter-bar.vue_vue_type_script_setup_true_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter-bar.vue_vue_type_script_setup_true_lang.js","names":["$slots","_openBlock","_createElementBlock","_unref","_Fragment","_renderList","_createElementVNode","_toDisplayString","_createBlock","_withCtx","_createVNode","_createTextVNode","_createCommentVNode","_renderSlot"],"sources":["../../src/dashboard/filter-bar.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { SearchIcon } from '@dou-ui/icons'\nimport { DouButton, DouDatePicker, DouInput, DouSelect, DouTag } from 'dou-ui'\nimport { computed, onBeforeUnmount, ref, useId, watch } from 'vue'\nimport { isEmptyFilter } from '../data/filters'\nimport type { DashboardFilterField } from '../data/types'\nimport { format, useDashboardMessages } from '../locale'\nimport type { DashboardFilterBarProps } from './types'\n\ndefineOptions({ name: 'DouDashboardFilterBar' })\n\nconst props = withDefaults(defineProps<DashboardFilterBarProps>(), {\n for: null,\n fields: () => [],\n clearable: true,\n})\ndefineSlots<{\n /** More controls at the end of the bar, e.g. a refresh button. */\n default?: () => unknown\n}>()\n\nconst messages = useDashboardMessages(() => props.messages)\nconst id = useId()\nconst dashboard = computed(() => props.for)\nconst values = computed(() => dashboard.value?.filters ?? {})\nconst fieldKeys = computed(() => new Set(props.fields.map((field) => field.key)))\n/** Filters set by widgets that no field shows. */\nconst linked = computed(() =>\n (dashboard.value?.linkedFilters ?? []).filter((link) => !fieldKeys.value.has(link.key)),\n)\nconst titleOf = (widgetId: string) => {\n const widget = dashboard.value?.document.widgets.find((item) => item.id === widgetId)\n const type = widget && dashboard.value?.types.find((item) => item.type === widget.type)\n return widget?.title || type?.label || widgetId\n}\nconst anySet = computed(() => Object.values(values.value).some((value) => !isEmptyFilter(value)))\n\nconst set = (key: string, value: unknown) => dashboard.value?.setFilter(key, value)\n\n// search fields apply once typing pauses\nconst typed = ref<Record<string, string>>({})\nconst timers = new Map<string, ReturnType<typeof setTimeout>>()\nwatch(\n values,\n (next) => {\n for (const field of props.fields)\n if (field.type === 'search' && !timers.has(field.key))\n typed.value[field.key] =\n typeof next[field.key] === 'string' ? (next[field.key] as string) : ''\n },\n { immediate: true },\n)\nfunction onType(field: DashboardFilterField, text: string) {\n typed.value[field.key] = text\n clearTimeout(timers.get(field.key))\n timers.set(\n field.key,\n setTimeout(() => {\n timers.delete(field.key)\n set(field.key, text.trim())\n }, 300),\n )\n}\nonBeforeUnmount(() => timers.forEach((timer) => clearTimeout(timer)))\n</script>\n\n<template>\n <div class=\"dou-dashboard-filter\" role=\"group\" :aria-label=\"messages.filterBar\">\n <div\n v-for=\"field in fields\"\n :key=\"field.key\"\n class=\"dou-dashboard-filter__field\"\n role=\"group\"\n :aria-labelledby=\"`${id}-${field.key}`\"\n >\n <span :id=\"`${id}-${field.key}`\" class=\"dou-dashboard-filter__label\">{{ field.label }}</span>\n <DouSelect\n v-if=\"field.type === 'select'\"\n class=\"dou-dashboard-filter__control\"\n size=\"sm\"\n :model-value=\"(values[field.key] as any) ?? (field.multiple ? [] : null)\"\n :options=\"field.options\"\n :multiple=\"field.multiple\"\n :max-tag-count=\"field.multiple ? 1 : undefined\"\n :placeholder=\"field.placeholder\"\n :aria-label=\"field.label\"\n clearable\n @update:model-value=\"(value) => set(field.key, value)\"\n />\n <DouDatePicker\n v-else-if=\"field.type === 'date-range'\"\n class=\"dou-dashboard-filter__control is-range\"\n size=\"sm\"\n range\n clearable\n :model-value=\"(values[field.key] as any) ?? null\"\n @update:model-value=\"(value) => set(field.key, value)\"\n />\n <DouInput\n v-else\n class=\"dou-dashboard-filter__control\"\n size=\"sm\"\n clearable\n :model-value=\"typed[field.key] ?? ''\"\n :placeholder=\"field.placeholder\"\n :aria-label=\"field.label\"\n @update:model-value=\"(value) => onType(field, value)\"\n >\n <template #prefix><SearchIcon /></template>\n </DouInput>\n </div>\n <DouTag\n v-for=\"link in linked\"\n :key=\"link.key\"\n class=\"dou-dashboard-filter__link\"\n closable\n :close-label=\"format(messages.clearFilter, { value: link.label })\"\n @close=\"set(link.key, undefined)\"\n >\n {{ format(messages.linkedFilter, { title: titleOf(link.widget), value: link.label }) }}\n </DouTag>\n <DouButton\n v-if=\"clearable && anySet\"\n class=\"dou-dashboard-filter__clear\"\n size=\"sm\"\n variant=\"ghost\"\n @click=\"dashboard?.clearFilters()\"\n >\n {{ messages.clearFilters }}\n </DouButton>\n <div v-if=\"$slots.default\" class=\"dou-dashboard-filter__extra\"><slot /></div>\n </div>\n</template>\n"],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ // src/dashboard/frame-context.ts
2
+ var frameContextKey = Symbol("dou-dashboard-frame");
3
+ export { frameContextKey };
4
+
5
+ //# sourceMappingURL=frame-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frame-context.js","names":[],"sources":["../../src/dashboard/frame-context.ts"],"sourcesContent":["import type { SheetWorkbook } from '@dou-ui/sheet/core'\nimport type { Component, InjectionKey } from 'vue'\nimport type { DashboardPeer } from '../core/collaboration'\nimport type { DashboardWidget } from '../core/types'\nimport type { DashboardLinkedFilter } from '../data/types'\nimport type { DashboardMessages } from '../locale'\nimport type { DashboardWidgetType } from '../widget/define'\n\nexport type WidgetAction = 'settings' | 'fullscreen' | 'duplicate' | 'remove'\n\n/** An exporter as the export menu lists it. */\nexport interface FrameExporter {\n name: string\n label: string\n icon?: Component\n}\n\n/**\n * What a widget's frame reads from its dashboard. The frame looks its widget\n * up by id in a computed of its own, so a change to one widget draws only\n * that widget's frame again, not every frame on the grid.\n */\nexport interface FrameContext {\n widget: (id: string) => DashboardWidget | undefined\n type: (widget: DashboardWidget) => DashboardWidgetType<any> | undefined\n title: (widget: DashboardWidget) => string\n editing: () => boolean\n messages: () => DashboardMessages\n /** Whether a frame mounting now is drawn in full at once. */\n eager: (id: string) => boolean\n /** Whether every widget is drawn in full now, to export or print the dashboard. */\n all: () => boolean\n setProps: (id: string, patch: Record<string, unknown>, options?: { group?: string }) => void\n action: (id: string, action: WidgetAction) => void\n rename: (id: string, title: string) => void\n /** The filters a widget set, shown in its title bar. */\n linked: (id: string) => DashboardLinkedFilter[]\n clearFilter: (key: string) => void\n /** The others at a widget, when editing together. */\n peers: (id: string) => readonly DashboardPeer[]\n /** How a widget gives its content as a workbook; returns what undoes it. */\n setWorkbook: (id: string, get: () => SheetWorkbook | undefined) => () => void\n /** The exporters a widget's menu lists: none unless it gives a workbook. */\n exporters: (id: string) => FrameExporter[]\n /** Writes a widget's content with an exporter, by name, and downloads the file. */\n exportTo: (id: string, exporter: string) => void\n /** Tells whether a widget is drawn in full, not only its title. */\n setDrawn: (id: string, drawn: boolean) => void\n /** Whether a widget's content is being exported now. */\n exporting: (id: string) => boolean\n /** Why the last export from a widget's menu failed. */\n exportError: (id: string) => string | undefined\n clearExportError: (id: string) => void\n}\n\nexport const frameContextKey: InjectionKey<FrameContext> = Symbol('dou-dashboard-frame')\n"],"mappings":";AAuDA,IAAa,kBAA8C,OAAO,qBAAqB"}
@@ -0,0 +1,48 @@
1
+ // src/dashboard/observers.ts
2
+ var observersKey = Symbol("dou-dashboard-observers");
3
+ function createObservers() {
4
+ const sized = /* @__PURE__ */ new Map();
5
+ const seen = /* @__PURE__ */ new Map();
6
+ let resize;
7
+ let intersect;
8
+ const run = (map) => (entries) => {
9
+ for (const entry of entries) {
10
+ if ("isIntersecting" in entry && !entry.isIntersecting) continue;
11
+ map.get(entry.target)?.(entry);
12
+ }
13
+ };
14
+ return {
15
+ size(el, callback) {
16
+ if (typeof ResizeObserver === "undefined") return () => {};
17
+ resize ??= new ResizeObserver(run(sized));
18
+ sized.set(el, callback);
19
+ resize.observe(el);
20
+ return () => {
21
+ sized.delete(el);
22
+ resize?.unobserve(el);
23
+ };
24
+ },
25
+ near(el, callback) {
26
+ if (typeof IntersectionObserver === "undefined") {
27
+ callback();
28
+ return () => {};
29
+ }
30
+ intersect ??= new IntersectionObserver(run(seen), { rootMargin: "200% 0px" });
31
+ seen.set(el, callback);
32
+ intersect.observe(el);
33
+ return () => {
34
+ seen.delete(el);
35
+ intersect?.unobserve(el);
36
+ };
37
+ },
38
+ dispose() {
39
+ resize?.disconnect();
40
+ intersect?.disconnect();
41
+ sized.clear();
42
+ seen.clear();
43
+ }
44
+ };
45
+ }
46
+ export { createObservers, observersKey };
47
+
48
+ //# sourceMappingURL=observers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"observers.js","names":[],"sources":["../../src/dashboard/observers.ts"],"sourcesContent":["import type { InjectionKey } from 'vue'\n\ntype Callback = (entry: ResizeObserverEntry | IntersectionObserverEntry) => void\n\n/**\n * One `ResizeObserver` and one `IntersectionObserver` for all the widgets of\n * a dashboard, instead of one per widget: with hundreds of widgets, creating\n * the observers is a large part of mounting them.\n */\nexport interface DashboardObservers {\n /** Calls `callback` with the element's new size; returns a function that stops. */\n size: (el: Element, callback: (entry: ResizeObserverEntry) => void) => () => void\n /** Calls `callback` once the element comes within two screens of view; returns a function that stops. */\n near: (el: Element, callback: () => void) => () => void\n dispose: () => void\n}\n\nexport const observersKey: InjectionKey<DashboardObservers> = Symbol('dou-dashboard-observers')\n\nexport function createObservers(): DashboardObservers {\n const sized = new Map<Element, Callback>()\n const seen = new Map<Element, Callback>()\n let resize: ResizeObserver | undefined\n let intersect: IntersectionObserver | undefined\n const run =\n (map: Map<Element, Callback>) =>\n (entries: (ResizeObserverEntry | IntersectionObserverEntry)[]) => {\n for (const entry of entries) {\n if ('isIntersecting' in entry && !entry.isIntersecting) continue\n map.get(entry.target)?.(entry)\n }\n }\n return {\n size(el, callback) {\n if (typeof ResizeObserver === 'undefined') return () => {}\n resize ??= new ResizeObserver(run(sized))\n sized.set(el, callback as Callback)\n resize.observe(el)\n return () => {\n sized.delete(el)\n resize?.unobserve(el)\n }\n },\n near(el, callback) {\n if (typeof IntersectionObserver === 'undefined') {\n callback()\n return () => {}\n }\n intersect ??= new IntersectionObserver(run(seen), { rootMargin: '200% 0px' })\n seen.set(el, callback)\n intersect.observe(el)\n return () => {\n seen.delete(el)\n intersect?.unobserve(el)\n }\n },\n dispose() {\n resize?.disconnect()\n intersect?.disconnect()\n sized.clear()\n seen.clear()\n },\n }\n}\n"],"mappings":";AAiBA,IAAa,eAAiD,OAAO,yBAAyB;AAE9F,SAAgB,kBAAsC;CACpD,MAAM,wBAAQ,IAAI,IAAuB;CACzC,MAAM,uBAAO,IAAI,IAAuB;CACxC,IAAI;CACJ,IAAI;CACJ,MAAM,OACH,SACA,YAAiE;EAChE,KAAK,MAAM,SAAS,SAAS;GAC3B,IAAI,oBAAoB,SAAS,CAAC,MAAM,gBAAgB;GACxD,IAAI,IAAI,MAAM,MAAM,CAAC,GAAG,KAAK;EAC/B;CACF;CACF,OAAO;EACL,KAAK,IAAI,UAAU;GACjB,IAAI,OAAO,mBAAmB,aAAa,aAAa,CAAC;GACzD,WAAW,IAAI,eAAe,IAAI,KAAK,CAAC;GACxC,MAAM,IAAI,IAAI,QAAoB;GAClC,OAAO,QAAQ,EAAE;GACjB,aAAa;IACX,MAAM,OAAO,EAAE;IACf,QAAQ,UAAU,EAAE;GACtB;EACF;EACA,KAAK,IAAI,UAAU;GACjB,IAAI,OAAO,yBAAyB,aAAa;IAC/C,SAAS;IACT,aAAa,CAAC;GAChB;GACA,cAAc,IAAI,qBAAqB,IAAI,IAAI,GAAG,EAAE,YAAY,WAAW,CAAC;GAC5E,KAAK,IAAI,IAAI,QAAQ;GACrB,UAAU,QAAQ,EAAE;GACpB,aAAa;IACX,KAAK,OAAO,EAAE;IACd,WAAW,UAAU,EAAE;GACzB;EACF;EACA,UAAU;GACR,QAAQ,WAAW;GACnB,WAAW,WAAW;GACtB,MAAM,MAAM;GACZ,KAAK,MAAM;EACb;CACF;AACF"}
@@ -0,0 +1,6 @@
1
+ import palette_vue_vue_type_script_setup_true_lang_default from "./palette.vue_vue_type_script_setup_true_lang.js";
2
+ // src/dashboard/palette.vue
3
+ var palette_default = palette_vue_vue_type_script_setup_true_lang_default;
4
+ export { palette_default as default };
5
+
6
+ //# sourceMappingURL=palette.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"palette.js","names":["_sfc_main"],"sources":["../../src/dashboard/palette.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { format, useDashboardMessages } from '../locale'\nimport type { DashboardWidgetType } from '../widget/define'\nimport type { DashboardPaletteProps } from './types'\n\ndefineOptions({ name: 'DouDashboardPalette' })\n\nconst props = withDefaults(defineProps<DashboardPaletteProps>(), {\n for: null,\n direction: 'vertical',\n})\ndefineSlots<{\n /** Replaces what a button shows; `type` is the widget type. */\n default?: (props: { type: DashboardWidgetType<any> }) => unknown\n}>()\n\nconst messages = useDashboardMessages()\nconst dashboard = computed(() => props.for)\nconst list = computed(() => {\n const all = dashboard.value?.types ?? []\n if (!props.types) return all\n const byName = new Map(all.map((type) => [type.type, type]))\n return props.types.flatMap((name) => byName.get(name) ?? [])\n})\nconst disabled = computed(() => !dashboard.value?.editable)\n</script>\n\n<template>\n <ul\n :class=\"['dou-dashboard-palette', `is-${direction}`]\"\n role=\"list\"\n :aria-label=\"messages.palette\"\n >\n <li v-for=\"type in list\" :key=\"type.type\">\n <button\n type=\"button\"\n class=\"dou-dashboard-palette__item\"\n :disabled=\"disabled\"\n :aria-label=\"format(messages.paletteItem, { label: type.label })\"\n @pointerdown=\"dashboard?.startAdd($event, type.type)\"\n @click=\"dashboard?.addWidget(type.type)\"\n >\n <slot :type=\"type\">\n <component :is=\"type.icon\" v-if=\"type.icon\" class=\"dou-dashboard-palette__icon\" />\n <span class=\"dou-dashboard-palette__label\">{{ type.label }}</span>\n </slot>\n </button>\n </li>\n </ul>\n</template>\n"],"mappings":""}
@@ -0,0 +1,54 @@
1
+ import { format, useDashboardMessages } from "../locale.js";
2
+ import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, defineComponent, normalizeClass, openBlock, renderList, renderSlot, resolveDynamicComponent, toDisplayString, unref } from "vue";
3
+ // src/dashboard/palette.vue?vue&type=script&setup=true&lang.ts
4
+ var _hoisted_1 = ["aria-label"];
5
+ var _hoisted_2 = [
6
+ "disabled",
7
+ "aria-label",
8
+ "onPointerdown",
9
+ "onClick"
10
+ ];
11
+ var _hoisted_3 = { class: "dou-dashboard-palette__label" };
12
+ var palette_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
13
+ name: "DouDashboardPalette",
14
+ __name: "palette",
15
+ props: {
16
+ for: { default: null },
17
+ types: {},
18
+ direction: { default: "vertical" }
19
+ },
20
+ setup(__props) {
21
+ const props = __props;
22
+ const messages = useDashboardMessages();
23
+ const dashboard = computed(() => props.for);
24
+ const list = computed(() => {
25
+ const all = dashboard.value?.types ?? [];
26
+ if (!props.types) return all;
27
+ const byName = new Map(all.map((type) => [type.type, type]));
28
+ return props.types.flatMap((name) => byName.get(name) ?? []);
29
+ });
30
+ const disabled = computed(() => !dashboard.value?.editable);
31
+ return (_ctx, _cache) => {
32
+ return openBlock(), createElementBlock("ul", {
33
+ class: normalizeClass(["dou-dashboard-palette", `is-${__props.direction}`]),
34
+ role: "list",
35
+ "aria-label": unref(messages).palette
36
+ }, [(openBlock(true), createElementBlock(Fragment, null, renderList(list.value, (type) => {
37
+ return openBlock(), createElementBlock("li", { key: type.type }, [createElementVNode("button", {
38
+ type: "button",
39
+ class: "dou-dashboard-palette__item",
40
+ disabled: disabled.value,
41
+ "aria-label": unref(format)(unref(messages).paletteItem, { label: type.label }),
42
+ onPointerdown: ($event) => dashboard.value?.startAdd($event, type.type),
43
+ onClick: ($event) => dashboard.value?.addWidget(type.type)
44
+ }, [renderSlot(_ctx.$slots, "default", { type }, () => [type.icon ? (openBlock(), createBlock(resolveDynamicComponent(type.icon), {
45
+ key: 0,
46
+ class: "dou-dashboard-palette__icon"
47
+ })) : createCommentVNode("", true), createElementVNode("span", _hoisted_3, toDisplayString(type.label), 1)])], 40, _hoisted_2)]);
48
+ }), 128))], 10, _hoisted_1);
49
+ };
50
+ }
51
+ });
52
+ export { palette_vue_vue_type_script_setup_true_lang_default as default };
53
+
54
+ //# sourceMappingURL=palette.vue_vue_type_script_setup_true_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"palette.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createElementBlock","_normalizeClass","_unref","_Fragment","_renderList","_createElementVNode","_renderSlot","_createBlock","_resolveDynamicComponent","_createCommentVNode","_toDisplayString"],"sources":["../../src/dashboard/palette.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { format, useDashboardMessages } from '../locale'\nimport type { DashboardWidgetType } from '../widget/define'\nimport type { DashboardPaletteProps } from './types'\n\ndefineOptions({ name: 'DouDashboardPalette' })\n\nconst props = withDefaults(defineProps<DashboardPaletteProps>(), {\n for: null,\n direction: 'vertical',\n})\ndefineSlots<{\n /** Replaces what a button shows; `type` is the widget type. */\n default?: (props: { type: DashboardWidgetType<any> }) => unknown\n}>()\n\nconst messages = useDashboardMessages()\nconst dashboard = computed(() => props.for)\nconst list = computed(() => {\n const all = dashboard.value?.types ?? []\n if (!props.types) return all\n const byName = new Map(all.map((type) => [type.type, type]))\n return props.types.flatMap((name) => byName.get(name) ?? [])\n})\nconst disabled = computed(() => !dashboard.value?.editable)\n</script>\n\n<template>\n <ul\n :class=\"['dou-dashboard-palette', `is-${direction}`]\"\n role=\"list\"\n :aria-label=\"messages.palette\"\n >\n <li v-for=\"type in list\" :key=\"type.type\">\n <button\n type=\"button\"\n class=\"dou-dashboard-palette__item\"\n :disabled=\"disabled\"\n :aria-label=\"format(messages.paletteItem, { label: type.label })\"\n @pointerdown=\"dashboard?.startAdd($event, type.type)\"\n @click=\"dashboard?.addWidget(type.type)\"\n >\n <slot :type=\"type\">\n <component :is=\"type.icon\" v-if=\"type.icon\" class=\"dou-dashboard-palette__icon\" />\n <span class=\"dou-dashboard-palette__label\">{{ type.label }}</span>\n </slot>\n </button>\n </li>\n </ul>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;EAQA,MAAM,QAAQ;EASd,MAAM,WAAW,qBAAqB;EACtC,MAAM,YAAY,eAAe,MAAM,GAAG;EAC1C,MAAM,OAAO,eAAe;GAC1B,MAAM,MAAM,UAAU,OAAO,SAAS,CAAC;GACvC,IAAI,CAAC,MAAM,OAAO,OAAO;GACzB,MAAM,SAAS,IAAI,IAAI,IAAI,KAAK,SAAS,CAAC,KAAK,MAAM,IAAI,CAAC,CAAC;GAC3D,OAAO,MAAM,MAAM,SAAS,SAAS,OAAO,IAAI,IAAI,KAAK,CAAC,CAAC;EAC7D,CAAC;EACD,MAAM,WAAW,eAAe,CAAC,UAAU,OAAO,QAAQ;;GAIxD,OAAAA,UAAA,GAAAC,mBAoBK,MAAA;IAnBF,OAAKC,eAAA,CAAA,yBAAA,MAAkC,QAAA,WAAS,CAAA;IACjD,MAAK;IACJ,cAAYC,MAAA,QAAA,CAAQ,CAAC;G,KAEtBH,UAAA,IAAA,GAAAC,mBAcKG,UAAA,MAAAC,WAdc,KAAA,QAAR,SAAI;IAAf,OAAAL,UAAA,GAAAC,mBAcK,MAAA,EAdqB,KAAK,KAAK,K,IAClCK,mBAYS,UAAA;KAXP,MAAK;KACL,OAAM;KACL,UAAU,SAAA;KACV,cAAYH,MAAA,MAAA,CAAM,CAACA,MAAA,QAAA,CAAQ,CAAC,aAAW,EAAA,OAAW,KAAK,MAAK,CAAA;KAC5D,gBAAW,WAAE,UAAA,OAAW,SAAS,QAAQ,KAAK,IAAI;KAClD,UAAK,WAAE,UAAA,OAAW,UAAU,KAAK,IAAI;I,IAEtCI,WAGO,KAAA,QAAA,WAAA,EAHM,KAAI,SAGV,CAF4B,KAAK,QAAtCP,UAAA,GAAAQ,YAAkFC,wBAAlE,KAAK,IAAI,GAAA;;KAAmB,OAAM;I,oCAClDH,mBAAkE,QAAlE,YAAkEK,gBAApB,KAAK,KAAK,GAAA,CAAA,C"}
@@ -0,0 +1,6 @@
1
+ import widget_body_vue_vue_type_script_setup_true_lang_default from "./widget-body.vue_vue_type_script_setup_true_lang.js";
2
+ // src/dashboard/widget-body.vue
3
+ var widget_body_default = widget_body_vue_vue_type_script_setup_true_lang_default;
4
+ export { widget_body_default as default };
5
+
6
+ //# sourceMappingURL=widget-body.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widget-body.js","names":["_sfc_main"],"sources":["../../src/dashboard/widget-body.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { AlertCircleIcon } from '@dou-ui/icons'\nimport { DouButton, DouEmpty, DouSpinner } from 'dou-ui'\nimport { inject, onBeforeUnmount, onMounted, ref } from 'vue'\nimport type { SheetWorkbook } from '@dou-ui/sheet/core'\nimport type { DashboardWidget } from '../core/types'\nimport type { DashboardMessages } from '../locale'\nimport { format } from '../locale'\nimport { provideWidgetContext } from '../widget/context'\nimport { observersKey } from './observers'\nimport type { DashboardWidgetType } from '../widget/define'\n\ndefineOptions({ name: 'DouDashboardWidgetBody' })\n\nconst props = defineProps<{\n widget: DashboardWidget\n type?: DashboardWidgetType<any>\n editing: boolean\n fullscreen?: boolean\n messages: DashboardMessages\n setProps: (id: string, patch: Record<string, unknown>, options?: { group?: string }) => void\n /** Where the widget's workbook goes, for its export menu; the full-screen copy has none. */\n setWorkbook?: (id: string, get: () => SheetWorkbook | undefined) => () => void\n}>()\n\nconst body = ref<HTMLElement>()\nconst width = ref(0)\nconst height = ref(0)\nconst observers = inject(observersKey)\nlet stop: (() => void) | undefined\nonMounted(() => {\n // not read here: that would lay the page out once per widget. The first\n // observation comes before the page is painted.\n stop = observers?.size(body.value!, (entry) => {\n width.value = entry.contentRect.width\n height.value = entry.contentRect.height\n })\n})\nonBeforeUnmount(() => stop?.())\nconst { status } = provideWidgetContext({\n widget: () => props.widget,\n editing: () => props.editing,\n fullscreen: () => !!props.fullscreen,\n size: { width, height },\n setProps: props.setProps,\n setWorkbook: props.setWorkbook,\n})\n</script>\n\n<template>\n <div class=\"dou-dashboard__body\" :aria-busy=\"status.state === 'loading' || undefined\">\n <div ref=\"body\" class=\"dou-dashboard__content\">\n <component :is=\"type.component\" v-if=\"type\" />\n <div v-else class=\"dou-dashboard__notice\">\n {{ format(messages.unknownType, { type: widget.type }) }}\n </div>\n </div>\n <div v-if=\"status.state === 'loading'\" class=\"dou-dashboard__status is-loading\" role=\"status\">\n <DouSpinner size=\"sm\" :label=\"messages.loading\" />\n </div>\n <div v-else-if=\"status.state === 'error'\" class=\"dou-dashboard__status is-error\" role=\"alert\">\n <AlertCircleIcon class=\"dou-dashboard__status-icon\" />\n <span>{{ status.message || messages.error }}</span>\n <DouButton v-if=\"status.retry\" size=\"sm\" variant=\"soft\" @click=\"status.retry()\">\n {{ messages.retry }}\n </DouButton>\n </div>\n <div v-else-if=\"status.state === 'empty'\" class=\"dou-dashboard__status is-empty\">\n <DouEmpty size=\"sm\" :title=\"status.message || messages.empty\" />\n </div>\n </div>\n</template>\n"],"mappings":""}