@omnitend/dashboard-for-laravel 0.34.0 → 0.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/charts.js +137 -97
- package/dist/charts.js.map +1 -1
- package/dist/charts.umd.cjs +1 -1
- package/dist/charts.umd.cjs.map +1 -1
- package/dist/components/charts/chartTheme.d.ts +29 -3
- package/dist/components/charts/useThemedChart.d.ts +7 -2
- package/dist/components/extended/DXForm.vue.d.ts +28 -6
- package/dist/components/extended/DXTable.vue.d.ts +38 -1
- package/dist/components/extended/DXTableEditorModal.vue.d.ts +7 -2
- package/dist/components/extended/DXTablePagination.vue.d.ts +22 -1
- package/dist/composables/useContainerWidth.d.ts +96 -0
- package/dist/dashboard-for-laravel.js +8288 -8105
- package/dist/dashboard-for-laravel.js.map +1 -1
- package/dist/dashboard-for-laravel.umd.cjs +7 -7
- package/dist/dashboard-for-laravel.umd.cjs.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/style.css +1 -1
- package/docs/public/api-reference.json +48 -8
- package/docs/public/docs-map.md +1 -1
- package/package.json +1 -1
- package/resources/css/theme.scss +96 -4
- package/resources/js/components/charts/DXBarChart.vue +7 -2
- package/resources/js/components/charts/DXDoughnutChart.vue +33 -10
- package/resources/js/components/charts/DXLineChart.vue +7 -2
- package/resources/js/components/charts/chartTheme.ts +106 -11
- package/resources/js/components/charts/useThemedChart.ts +39 -21
- package/resources/js/components/extended/DXDashboardSidebar.vue +1 -1
- package/resources/js/components/extended/DXForm.vue +83 -9
- package/resources/js/components/extended/DXTable.vue +216 -12
- package/resources/js/components/extended/DXTableEditorModal.vue +7 -2
- package/resources/js/components/extended/DXTablePagination.vue +165 -8
- package/resources/js/components/extended/DXTableShell.vue +105 -1
- package/resources/js/composables/useContainerWidth.ts +303 -0
- package/resources/js/index.ts +7 -0
package/dist/charts.umd.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"charts.umd.cjs","sources":["../resources/js/components/charts/chartTheme.ts","../resources/js/components/charts/useThemedChart.ts","../resources/js/components/charts/DXBarChart.vue","../resources/js/components/charts/DXLineChart.vue","../resources/js/components/charts/DXDoughnutChart.vue"],"sourcesContent":["/**\n * Shared theming for the DX*Chart wrappers: the dedicated categorical chart\n * palette (--dx-chart-* from the live theme), dashboard-friendly default options (no gridlines /\n * legend clutter, responsive, formatted ticks), and a one-time Chart.js\n * registration. chart.js / vue-chartjs are OPTIONAL peer deps.\n */\nimport { Chart, registerables } from \"chart.js\";\n\nexport type ChartValueFormat = \"number\" | \"currency\" | \"percent\";\n\nconst isPlainObject = (value: unknown): value is Record<string, any> =>\n !!value && typeof value === \"object\" && !Array.isArray(value);\n\n/** Deep-merge consumer `options` over the themed defaults (arrays replace). */\nexport function mergeOptions(\n base: Record<string, any>,\n override: Record<string, any>,\n): Record<string, any> {\n const result: Record<string, any> = { ...base };\n for (const [key, value] of Object.entries(override)) {\n if (isPlainObject(value) && isPlainObject(result[key])) {\n result[key] = mergeOptions(result[key], value);\n } else {\n result[key] = value;\n }\n }\n return result;\n}\n\nlet registered = false;\n\n/**\n * Register Chart.js (idempotent). Uses the full `registerables` set so every\n * controller / scale / element / plugin (incl. Legend, Tooltip, Filler) is\n * available — chart.js is an external peer dep, so this adds no bundle weight.\n */\nexport function registerCharts(): void {\n if (registered) return;\n Chart.register(...registerables);\n registered = true;\n}\n\n// The dedicated data-viz palette (`$dx-chart-palette` in theme.scss) — eight\n// vivid hues in a fixed, CVD-validated order, published as --dx-chart-1..8.\n// Deliberately NOT the semantic --bs-* colours (#141): the base theme colours\n// are dark AA \"emphasis\" shades (too muted for series), and status colours\n// shouldn't impersonate \"series 2\". Fallbacks mirror the shipped theme so\n// charts still render without the CSS (SSR / tests / a consumer who didn't\n// import it). Sync with theme.scss's $dx-chart-palette is enforced by a test\n// that parses the Sass source (charts.test.ts), which is why this is exported.\nexport const PALETTE_VARS: Array<[string, string]> = [\n [\"--dx-chart-1\", \"#2563eb\"], // blue\n [\"--dx-chart-2\", \"#65a30d\"], // lime\n [\"--dx-chart-3\", \"#7c3aed\"], // violet\n [\"--dx-chart-4\", \"#0d9488\"], // teal\n [\"--dx-chart-5\", \"#ea580c\"], // orange\n [\"--dx-chart-6\", \"#0891b2\"], // cyan\n [\"--dx-chart-7\", \"#d97706\"], // amber\n [\"--dx-chart-8\", \"#db2777\"], // pink\n];\n\n/** Resolve the themed categorical palette from the document root. */\nexport function getPalette(): string[] {\n if (typeof document === \"undefined\" || typeof getComputedStyle === \"undefined\") {\n return PALETTE_VARS.map(([, fallback]) => fallback);\n }\n const styles = getComputedStyle(document.documentElement);\n return PALETTE_VARS.map(([name, fallback]) => {\n const value = styles.getPropertyValue(name).trim();\n return value || fallback;\n });\n}\n\n/** Read a single theme colour (e.g. the body text colour) with a fallback. */\nfunction themeColor(name: string, fallback: string): string {\n if (typeof document === \"undefined\" || typeof getComputedStyle === \"undefined\") {\n return fallback;\n }\n const value = getComputedStyle(document.documentElement)\n .getPropertyValue(name)\n .trim();\n return value || fallback;\n}\n\n/** Add an alpha channel to a hex or rgb() colour (best-effort). */\nexport function withAlpha(color: string, alpha: number): string {\n const hex = color.replace(/^#/, \"\");\n if (/^[0-9a-f]{6}$/i.test(hex)) {\n const r = parseInt(hex.slice(0, 2), 16);\n const g = parseInt(hex.slice(2, 4), 16);\n const b = parseInt(hex.slice(4, 6), 16);\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n }\n const rgbMatch = color.match(/^rgba?\\(([^)]+)\\)$/);\n if (rgbMatch) {\n const [r, g, b] = rgbMatch[1].split(\",\").map((n) => n.trim());\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n }\n return color;\n}\n\n/** Format a numeric value per the chart's value format (for tick/tooltips). */\nexport function formatValue(\n value: number,\n format: ChartValueFormat | undefined,\n currencySymbol: string,\n locale?: string,\n): string {\n const num = new Intl.NumberFormat(locale, {\n maximumFractionDigits: 2,\n }).format(value);\n if (format === \"currency\") return `${currencySymbol}${num}`;\n if (format === \"percent\") return `${num}%`;\n return num;\n}\n\nexport interface BaseOptionArgs {\n valueFormat?: ChartValueFormat;\n currencySymbol?: string;\n locale?: string;\n /** Show the legend (default: false for a single dataset, true for many). */\n showLegend?: boolean;\n /** Whether the chart has value axes (bar/line) vs none (doughnut). */\n hasValueAxis?: boolean;\n}\n\n/** Dashboard default Chart.js options merged under any consumer `options`. */\nexport function baseOptions(args: BaseOptionArgs): Record<string, any> {\n const {\n valueFormat,\n currencySymbol = \"£\",\n locale,\n showLegend = false,\n hasValueAxis = true,\n } = args;\n\n const gridColor = withAlpha(themeColor(\"--bs-border-color\", \"#dee2e6\"), 0.6);\n const tickColor = themeColor(\"--bs-secondary-color\", \"#6c757d\");\n\n const options: Record<string, any> = {\n responsive: true,\n maintainAspectRatio: false,\n plugins: {\n legend: { display: showLegend, labels: { color: tickColor } },\n tooltip: {\n callbacks: {\n label: (ctx: any) => {\n const raw = ctx.parsed?.y ?? ctx.parsed ?? ctx.raw;\n const value = typeof raw === \"number\" ? raw : Number(raw);\n const formatted = Number.isFinite(value)\n ? formatValue(value, valueFormat, currencySymbol, locale)\n : String(raw);\n const label = ctx.dataset?.label ?? ctx.label ?? \"\";\n return label ? `${label}: ${formatted}` : formatted;\n },\n },\n },\n },\n };\n\n if (hasValueAxis) {\n options.scales = {\n x: {\n grid: { display: false },\n border: { color: gridColor },\n ticks: { color: tickColor },\n },\n y: {\n beginAtZero: true,\n grid: { color: gridColor, drawBorder: false },\n border: { display: false },\n ticks: {\n color: tickColor,\n callback: (value: number | string) =>\n formatValue(Number(value), valueFormat, currencySymbol, locale),\n },\n },\n };\n }\n\n return options;\n}\n\n/**\n * Apply the themed palette to any dataset that doesn't set its own colours.\n * Bar: one colour per dataset (or per bar for a single dataset). Line: a themed\n * stroke + translucent fill. Doughnut/pie: one colour per slice.\n */\nexport function applyPalette(\n datasets: any[],\n kind: \"bar\" | \"line\" | \"doughnut\",\n labelsCount: number,\n): any[] {\n const palette = getPalette();\n const single = datasets.length === 1;\n\n return datasets.map((dataset, index) => {\n const next = { ...dataset };\n const color = palette[index % palette.length];\n\n if (kind === \"doughnut\") {\n if (next.backgroundColor === undefined) {\n next.backgroundColor = Array.from(\n { length: labelsCount },\n (_, i) => palette[i % palette.length],\n );\n }\n return next;\n }\n\n if (kind === \"line\") {\n if (next.borderColor === undefined) next.borderColor = color;\n if (next.backgroundColor === undefined) {\n next.backgroundColor = withAlpha(color, 0.15);\n }\n if (next.tension === undefined) next.tension = 0.3;\n if (next.fill === undefined) next.fill = true;\n if (next.pointRadius === undefined) next.pointRadius = 2;\n return next;\n }\n\n // bar\n if (next.backgroundColor === undefined) {\n next.backgroundColor =\n single && labelsCount > 1\n ? Array.from({ length: labelsCount }, () => color)\n : color;\n }\n if (next.borderRadius === undefined) next.borderRadius = 4;\n return next;\n });\n}\n","import { computed, type ComputedRef } from \"vue\";\nimport type { ChartData, ChartDataset, ChartOptions } from \"chart.js\";\nimport {\n baseOptions,\n applyPalette,\n mergeOptions,\n type ChartValueFormat,\n} from \"./chartTheme\";\n\n/**\n * Shared props for the value-axis chart wrappers (`DXBarChart`, `DXLineChart`).\n * `DXBarChart` and `DXLineChart` differ ONLY in the chart.js component they\n * render and the palette kind — everything else (theming, option-merge,\n * legend-default) is identical, and lived duplicated in both SFCs (#135).\n */\nexport interface ThemedChartProps<TType extends \"bar\" | \"line\"> {\n /** X-axis category labels. */\n labels: string[];\n /** Chart.js datasets. Colours are themed when a dataset omits them. */\n datasets: ChartDataset<TType>[];\n /** Chart.js options; deep-merged over the themed defaults. */\n options?: ChartOptions<TType>;\n /** Format value-axis ticks and tooltips. */\n valueFormat?: ChartValueFormat;\n /** Currency symbol for valueFormat: \"currency\" (default \"£\"). */\n currencySymbol?: string;\n /** Locale for number formatting. */\n locale?: string;\n /** Show the legend (default: only when there is more than one dataset). */\n showLegend?: boolean;\n}\n\n/**\n * The themed-chart body shared by `DXBarChart` and `DXLineChart`: applies the\n * data-viz palette to un-coloured datasets and deep-merges the consumer's\n * `options` over the dashboard-friendly defaults. Pass the reactive `props`\n * object (property access stays reactive inside the computeds).\n */\nexport function useThemedChart<TType extends \"bar\" | \"line\">(\n type: TType,\n props: ThemedChartProps<TType>,\n): {\n chartData: ComputedRef<ChartData<TType>>;\n mergedOptions: ComputedRef<ChartOptions<TType>>;\n} {\n const chartData = computed(\n () =>\n ({\n labels: props.labels,\n datasets: applyPalette(props.datasets, type, props.labels.length),\n }) as ChartData<TType>,\n );\n\n const mergedOptions = computed(\n () =>\n mergeOptions(\n baseOptions({\n valueFormat: props.valueFormat,\n currencySymbol: props.currencySymbol,\n locale: props.locale,\n showLegend: props.showLegend ?? props.datasets.length > 1,\n hasValueAxis: true,\n }),\n props.options ?? {},\n ) as ChartOptions<TType>,\n );\n\n return { chartData, mergedOptions };\n}\n","<!--\n @component\n A themed bar chart. Thin wrapper around vue-chartjs (chart.js) with the\n theme's dedicated chart palette (--dx-chart-1..8) applied and dashboard-friendly defaults (no x gridlines,\n formatted value ticks, responsive, legend hidden for a single series). Pass\n `labels` + `datasets`; `options` deep-merges over the defaults.\n\n Requires the optional peer deps `chart.js` and `vue-chartjs`.\n-->\n<template>\n <div class=\"dx-chart\">\n <Bar :data=\"chartData\" :options=\"mergedOptions\" />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { Bar } from \"vue-chartjs\";\nimport { registerCharts } from \"./chartTheme\";\nimport { useThemedChart, type ThemedChartProps } from \"./useThemedChart\";\n\nregisterCharts();\n\n// `showLegend` defaults to `undefined` (not Vue's Boolean-cast `false`) so the\n// `?? datasets.length > 1` default can actually fire when it isn't passed.\nconst props = withDefaults(defineProps<ThemedChartProps<\"bar\">>(), {\n currencySymbol: \"£\",\n showLegend: undefined,\n});\n\nconst { chartData, mergedOptions } = useThemedChart(\"bar\", props);\n</script>\n","<!--\n @component\n A themed line chart. Thin wrapper around vue-chartjs (chart.js) with the\n theme's dedicated chart palette applied (--dx-chart-1..8 stroke + translucent fill, smoothed line)\n and dashboard-friendly defaults. Pass `labels` + `datasets`; `options`\n deep-merges over the defaults.\n\n Requires the optional peer deps `chart.js` and `vue-chartjs`.\n-->\n<template>\n <div class=\"dx-chart\">\n <Line :data=\"chartData\" :options=\"mergedOptions\" />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { Line } from \"vue-chartjs\";\nimport { registerCharts } from \"./chartTheme\";\nimport { useThemedChart, type ThemedChartProps } from \"./useThemedChart\";\n\nregisterCharts();\n\n// `showLegend` defaults to `undefined` (not Vue's Boolean-cast `false`) so the\n// `?? datasets.length > 1` default can actually fire when it isn't passed.\nconst props = withDefaults(defineProps<ThemedChartProps<\"line\">>(), {\n currencySymbol: \"£\",\n showLegend: undefined,\n});\n\nconst { chartData, mergedOptions } = useThemedChart(\"line\", props);\n</script>\n","<!--\n @component\n A themed doughnut chart. Thin wrapper around vue-chartjs (chart.js) with one\n chart-palette colour (--dx-chart-1..8) per slice and dashboard-friendly defaults. Pass\n `labels` + either `data` (a number array) or full `datasets`; `options`\n deep-merges over the defaults.\n\n Requires the optional peer deps `chart.js` and `vue-chartjs`.\n-->\n<template>\n <div class=\"dx-chart\">\n <Doughnut :data=\"chartData\" :options=\"mergedOptions\" />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from \"vue\";\nimport { Doughnut } from \"vue-chartjs\";\nimport {\n registerCharts,\n baseOptions,\n applyPalette,\n mergeOptions,\n type ChartValueFormat,\n} from \"./chartTheme\";\n\nregisterCharts();\n\ninterface Props {\n /** Slice labels. */\n labels: string[];\n /** Convenience: a single number array (one value per label). */\n data?: number[];\n /** Full Chart.js datasets (alternative to `data`). */\n datasets?: any[];\n /** Chart.js options; deep-merged over the themed defaults. */\n options?: Record<string, any>;\n /** Format tooltip values. */\n valueFormat?: ChartValueFormat;\n /** Currency symbol for valueFormat: \"currency\" (default \"£\"). */\n currencySymbol?: string;\n /** Locale for number formatting. */\n locale?: string;\n /** Show the legend (default: true — slices need identifying). */\n showLegend?: boolean;\n}\n\n// `showLegend` defaults to `undefined` (not Vue's Boolean-cast `false`) so the\n// `?? true` default can actually fire when it isn't passed.\nconst props = withDefaults(defineProps<Props>(), {\n currencySymbol: \"£\",\n showLegend: undefined,\n});\n\nconst resolvedDatasets = computed(() =>\n props.datasets ?? [{ data: props.data ?? [] }],\n);\n\nconst chartData = computed(() => ({\n labels: props.labels,\n datasets: applyPalette(resolvedDatasets.value, \"doughnut\", props.labels.length),\n}));\n\nconst mergedOptions = computed(() =>\n mergeOptions(\n baseOptions({\n valueFormat: props.valueFormat,\n currencySymbol: props.currencySymbol,\n locale: props.locale,\n showLegend: props.showLegend ?? true,\n hasValueAxis: false,\n }),\n props.options ?? {},\n ),\n);\n</script>\n"],"names":["isPlainObject","value","mergeOptions","base","override","result","key","registered","registerCharts","Chart","registerables","PALETTE_VARS","getPalette","fallback","styles","name","themeColor","withAlpha","color","alpha","hex","r","g","b","rgbMatch","n","formatValue","format","currencySymbol","locale","num","baseOptions","args","valueFormat","showLegend","hasValueAxis","gridColor","tickColor","options","ctx","raw","_a","formatted","label","_b","applyPalette","datasets","kind","labelsCount","palette","single","dataset","index","next","_","i","useThemedChart","type","props","chartData","computed","mergedOptions","__props","_openBlock","_createElementBlock","_hoisted_1","_createVNode","_unref","Bar","Line","resolvedDatasets","Doughnut"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"charts.umd.cjs","sources":["../resources/js/components/charts/chartTheme.ts","../resources/js/components/charts/useThemedChart.ts","../resources/js/components/charts/DXBarChart.vue","../resources/js/components/charts/DXLineChart.vue","../resources/js/components/charts/DXDoughnutChart.vue"],"sourcesContent":["/**\n * Shared theming for the DX*Chart wrappers: the dedicated categorical chart\n * palette (--dx-chart-* from the live theme), dashboard-friendly default options (no gridlines /\n * legend clutter, responsive, formatted ticks), and a one-time Chart.js\n * registration. chart.js / vue-chartjs are OPTIONAL peer deps.\n */\nimport { Chart, registerables } from \"chart.js\";\nimport { getCurrentScope, onScopeDispose, ref, type Ref } from \"vue\";\n\nexport type ChartValueFormat = \"number\" | \"currency\" | \"percent\";\n\nconst isPlainObject = (value: unknown): value is Record<string, any> =>\n !!value && typeof value === \"object\" && !Array.isArray(value);\n\n/** Deep-merge consumer `options` over the themed defaults (arrays replace). */\nexport function mergeOptions(\n base: Record<string, any>,\n override: Record<string, any>,\n): Record<string, any> {\n const result: Record<string, any> = { ...base };\n for (const [key, value] of Object.entries(override)) {\n if (isPlainObject(value) && isPlainObject(result[key])) {\n result[key] = mergeOptions(result[key], value);\n } else {\n result[key] = value;\n }\n }\n return result;\n}\n\nlet registered = false;\n\n/**\n * Register Chart.js (idempotent). Uses the full `registerables` set so every\n * controller / scale / element / plugin (incl. Legend, Tooltip, Filler) is\n * available — chart.js is an external peer dep, so this adds no bundle weight.\n */\nexport function registerCharts(): void {\n if (registered) return;\n Chart.register(...registerables);\n registered = true;\n}\n\n// The dedicated data-viz palette (`$dx-chart-palette` in theme.scss) — eight\n// vivid hues in a fixed, CVD-validated order, published as --dx-chart-1..8.\n// Deliberately NOT the semantic --bs-* colours (#141): the base theme colours\n// are dark AA \"emphasis\" shades (too muted for series), and status colours\n// shouldn't impersonate \"series 2\". Fallbacks mirror the shipped theme so\n// charts still render without the CSS (SSR / tests / a consumer who didn't\n// import it). Sync with theme.scss's $dx-chart-palette is enforced by a test\n// that parses the Sass source (charts.test.ts), which is why this is exported.\nexport const PALETTE_VARS: Array<[string, string]> = [\n [\"--dx-chart-1\", \"#2563eb\"], // blue\n [\"--dx-chart-2\", \"#65a30d\"], // lime\n [\"--dx-chart-3\", \"#7c3aed\"], // violet\n [\"--dx-chart-4\", \"#0d9488\"], // teal\n [\"--dx-chart-5\", \"#ea580c\"], // orange\n [\"--dx-chart-6\", \"#0891b2\"], // cyan\n [\"--dx-chart-7\", \"#d97706\"], // amber\n [\"--dx-chart-8\", \"#db2777\"], // pink\n];\n\n/**\n * The element whose computed style the theme variables are read from.\n *\n * Bootstrap 5.3 allows `data-bs-theme` on ANY container, not just `<html>` — a\n * dark card on a light page is a supported pattern. Reading the variables off\n * `document.documentElement` therefore gives a *nested* scope the wrong palette\n * (#161), so every resolver takes the chart's own container and only falls back\n * to the root when it has none (SSR, a caller outside a component).\n */\nfunction themeScope(scope?: Element | null): Element | null {\n if (typeof document === \"undefined\" || typeof getComputedStyle === \"undefined\") {\n return null;\n }\n if (scope !== undefined && scope !== null) return scope;\n return document.documentElement;\n}\n\n/**\n * Resolve the themed categorical palette.\n *\n * @param scope The chart's container element; its computed style decides which\n * `data-bs-theme` scope applies. Omit (or pass null) to read the document\n * root — the pre-#161 behaviour, kept so the exported signature stays\n * backward compatible.\n */\nexport function getPalette(scope?: Element | null): string[] {\n const host = themeScope(scope);\n if (host === null) {\n return PALETTE_VARS.map(([, fallback]) => fallback);\n }\n const styles = getComputedStyle(host);\n return PALETTE_VARS.map(([name, fallback]) => {\n const value = styles.getPropertyValue(name).trim();\n return value || fallback;\n });\n}\n\n/** Read a single theme colour (e.g. the body text colour) with a fallback. */\nfunction themeColor(name: string, fallback: string, scope?: Element | null): string {\n const host = themeScope(scope);\n if (host === null) {\n return fallback;\n }\n const value = getComputedStyle(host).getPropertyValue(name).trim();\n return value || fallback;\n}\n\n// ---------------------------------------------------------------------------\n// Colour-mode reactivity (#161)\n//\n// The palette lives in CSS variables, which Vue cannot track: a computed that\n// calls getPalette() depends only on the props it also reads, so flipping\n// `data-bs-theme` repaints the page but leaves every MOUNTED chart on the old\n// palette until a prop changes or it remounts. One shared MutationObserver\n// bumps a version ref that the chart computeds read, which re-resolves the\n// variables. `subtree: true` because the attribute can sit on any container,\n// and `attributeFilter` keeps the observer from firing on unrelated mutations.\n// ---------------------------------------------------------------------------\n\nconst colorModeVersion = ref(0);\nlet colorModeObserver: MutationObserver | null = null;\nlet colorModeWatchers = 0;\n\n/**\n * A version ref that increments whenever a `data-bs-theme` attribute changes\n * anywhere in the document. Read it inside a computed that resolves theme\n * variables to make that computed re-run on a colour-mode switch.\n *\n * The observer is started on first use and disconnected when the last consumer's\n * effect scope is disposed, so nothing leaks after the charts unmount. Called\n * outside an effect scope (or without a DOM) it is a no-op returning a static\n * ref — there would be no teardown hook to disconnect the observer.\n */\nexport function useColorModeVersion(): Ref<number> {\n if (\n typeof document === \"undefined\" ||\n typeof MutationObserver === \"undefined\" ||\n getCurrentScope() === undefined\n ) {\n return colorModeVersion;\n }\n\n colorModeWatchers += 1;\n if (colorModeObserver === null) {\n colorModeObserver = new MutationObserver(() => {\n colorModeVersion.value += 1;\n });\n colorModeObserver.observe(document.documentElement, {\n attributes: true,\n attributeFilter: [\"data-bs-theme\"],\n subtree: true,\n });\n }\n\n onScopeDispose(() => {\n colorModeWatchers -= 1;\n if (colorModeWatchers === 0 && colorModeObserver !== null) {\n colorModeObserver.disconnect();\n colorModeObserver = null;\n }\n });\n\n return colorModeVersion;\n}\n\n/** Add an alpha channel to a hex or rgb() colour (best-effort). */\nexport function withAlpha(color: string, alpha: number): string {\n const hex = color.replace(/^#/, \"\");\n if (/^[0-9a-f]{6}$/i.test(hex)) {\n const r = parseInt(hex.slice(0, 2), 16);\n const g = parseInt(hex.slice(2, 4), 16);\n const b = parseInt(hex.slice(4, 6), 16);\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n }\n const rgbMatch = color.match(/^rgba?\\(([^)]+)\\)$/);\n if (rgbMatch) {\n const [r, g, b] = rgbMatch[1].split(\",\").map((n) => n.trim());\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n }\n return color;\n}\n\n/** Format a numeric value per the chart's value format (for tick/tooltips). */\nexport function formatValue(\n value: number,\n format: ChartValueFormat | undefined,\n currencySymbol: string,\n locale?: string,\n): string {\n const num = new Intl.NumberFormat(locale, {\n maximumFractionDigits: 2,\n }).format(value);\n if (format === \"currency\") return `${currencySymbol}${num}`;\n if (format === \"percent\") return `${num}%`;\n return num;\n}\n\nexport interface BaseOptionArgs {\n valueFormat?: ChartValueFormat;\n currencySymbol?: string;\n locale?: string;\n /** Show the legend (default: false for a single dataset, true for many). */\n showLegend?: boolean;\n /** Whether the chart has value axes (bar/line) vs none (doughnut). */\n hasValueAxis?: boolean;\n /** The chart's container element — decides which `data-bs-theme` scope the\n * grid/tick colours resolve against (#161). Defaults to the document root. */\n scope?: Element | null;\n}\n\n/** Dashboard default Chart.js options merged under any consumer `options`. */\nexport function baseOptions(args: BaseOptionArgs): Record<string, any> {\n const {\n valueFormat,\n currencySymbol = \"£\",\n locale,\n showLegend = false,\n hasValueAxis = true,\n scope,\n } = args;\n\n const gridColor = withAlpha(\n themeColor(\"--bs-border-color\", \"#dee2e6\", scope),\n 0.6,\n );\n const tickColor = themeColor(\"--bs-secondary-color\", \"#6c757d\", scope);\n\n const options: Record<string, any> = {\n responsive: true,\n maintainAspectRatio: false,\n plugins: {\n legend: { display: showLegend, labels: { color: tickColor } },\n tooltip: {\n callbacks: {\n label: (ctx: any) => {\n const raw = ctx.parsed?.y ?? ctx.parsed ?? ctx.raw;\n const value = typeof raw === \"number\" ? raw : Number(raw);\n const formatted = Number.isFinite(value)\n ? formatValue(value, valueFormat, currencySymbol, locale)\n : String(raw);\n const label = ctx.dataset?.label ?? ctx.label ?? \"\";\n return label ? `${label}: ${formatted}` : formatted;\n },\n },\n },\n },\n };\n\n if (hasValueAxis) {\n options.scales = {\n x: {\n grid: { display: false },\n border: { color: gridColor },\n ticks: { color: tickColor },\n },\n y: {\n beginAtZero: true,\n grid: { color: gridColor, drawBorder: false },\n border: { display: false },\n ticks: {\n color: tickColor,\n callback: (value: number | string) =>\n formatValue(Number(value), valueFormat, currencySymbol, locale),\n },\n },\n };\n }\n\n return options;\n}\n\n/**\n * Apply the themed palette to any dataset that doesn't set its own colours.\n * Bar: one colour per dataset (or per bar for a single dataset). Line: a themed\n * stroke + translucent fill. Doughnut/pie: one colour per slice.\n *\n * `scope` is the chart's container element; the palette is resolved from its\n * computed style so a nested `data-bs-theme` container themes correctly (#161).\n * Omit it to resolve against the document root.\n */\nexport function applyPalette(\n datasets: any[],\n kind: \"bar\" | \"line\" | \"doughnut\",\n labelsCount: number,\n scope?: Element | null,\n): any[] {\n const palette = getPalette(scope);\n const single = datasets.length === 1;\n\n return datasets.map((dataset, index) => {\n const next = { ...dataset };\n const color = palette[index % palette.length];\n\n if (kind === \"doughnut\") {\n if (next.backgroundColor === undefined) {\n next.backgroundColor = Array.from(\n { length: labelsCount },\n (_, i) => palette[i % palette.length],\n );\n }\n return next;\n }\n\n if (kind === \"line\") {\n if (next.borderColor === undefined) next.borderColor = color;\n if (next.backgroundColor === undefined) {\n next.backgroundColor = withAlpha(color, 0.15);\n }\n if (next.tension === undefined) next.tension = 0.3;\n if (next.fill === undefined) next.fill = true;\n if (next.pointRadius === undefined) next.pointRadius = 2;\n return next;\n }\n\n // bar\n if (next.backgroundColor === undefined) {\n next.backgroundColor =\n single && labelsCount > 1\n ? Array.from({ length: labelsCount }, () => color)\n : color;\n }\n if (next.borderRadius === undefined) next.borderRadius = 4;\n return next;\n });\n}\n","import { computed, type ComputedRef, type Ref } from \"vue\";\nimport type { ChartData, ChartDataset, ChartOptions } from \"chart.js\";\nimport {\n baseOptions,\n applyPalette,\n mergeOptions,\n useColorModeVersion,\n type ChartValueFormat,\n} from \"./chartTheme\";\n\n/**\n * Shared props for the value-axis chart wrappers (`DXBarChart`, `DXLineChart`).\n * `DXBarChart` and `DXLineChart` differ ONLY in the chart.js component they\n * render and the palette kind — everything else (theming, option-merge,\n * legend-default) is identical, and lived duplicated in both SFCs (#135).\n */\nexport interface ThemedChartProps<TType extends \"bar\" | \"line\"> {\n /** X-axis category labels. */\n labels: string[];\n /** Chart.js datasets. Colours are themed when a dataset omits them. */\n datasets: ChartDataset<TType>[];\n /** Chart.js options; deep-merged over the themed defaults. */\n options?: ChartOptions<TType>;\n /** Format value-axis ticks and tooltips. */\n valueFormat?: ChartValueFormat;\n /** Currency symbol for valueFormat: \"currency\" (default \"£\"). */\n currencySymbol?: string;\n /** Locale for number formatting. */\n locale?: string;\n /** Show the legend (default: only when there is more than one dataset). */\n showLegend?: boolean;\n}\n\n/**\n * The themed-chart body shared by `DXBarChart` and `DXLineChart`: applies the\n * data-viz palette to un-coloured datasets and deep-merges the consumer's\n * `options` over the dashboard-friendly defaults. Pass the reactive `props`\n * object (property access stays reactive inside the computeds).\n *\n * `host` is the chart's own container element (a template ref). It is BOTH the\n * `data-bs-theme` scope the palette resolves against and — being a ref read\n * inside the computeds — the trigger that re-themes once it is populated on\n * mount, since the first render happens before a template ref exists (#161).\n */\nexport function useThemedChart<TType extends \"bar\" | \"line\">(\n type: TType,\n props: ThemedChartProps<TType>,\n host: Ref<HTMLElement | null>,\n): {\n chartData: ComputedRef<ChartData<TType>>;\n mergedOptions: ComputedRef<ChartOptions<TType>>;\n} {\n // Bumped by a MutationObserver on every `data-bs-theme` change; read below\n // so the CSS-variable palette — invisible to Vue's reactivity — is\n // re-resolved when the colour mode flips under a mounted chart.\n const colorModeVersion = useColorModeVersion();\n\n const chartData = computed(() => {\n void colorModeVersion.value;\n return {\n labels: props.labels,\n datasets: applyPalette(\n props.datasets,\n type,\n props.labels.length,\n host.value,\n ),\n } as ChartData<TType>;\n });\n\n const mergedOptions = computed(() => {\n void colorModeVersion.value;\n return mergeOptions(\n baseOptions({\n valueFormat: props.valueFormat,\n currencySymbol: props.currencySymbol,\n locale: props.locale,\n showLegend: props.showLegend ?? props.datasets.length > 1,\n hasValueAxis: true,\n scope: host.value,\n }),\n props.options ?? {},\n ) as ChartOptions<TType>;\n });\n\n return { chartData, mergedOptions };\n}\n","<!--\n @component\n A themed bar chart. Thin wrapper around vue-chartjs (chart.js) with the\n theme's dedicated chart palette (--dx-chart-1..8) applied and dashboard-friendly defaults (no x gridlines,\n formatted value ticks, responsive, legend hidden for a single series). Pass\n `labels` + `datasets`; `options` deep-merges over the defaults.\n\n Requires the optional peer deps `chart.js` and `vue-chartjs`.\n-->\n<template>\n <div ref=\"host\" class=\"dx-chart\">\n <Bar :data=\"chartData\" :options=\"mergedOptions\" />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref } from \"vue\";\nimport { Bar } from \"vue-chartjs\";\nimport { registerCharts } from \"./chartTheme\";\nimport { useThemedChart, type ThemedChartProps } from \"./useThemedChart\";\n\nregisterCharts();\n\n// `showLegend` defaults to `undefined` (not Vue's Boolean-cast `false`) so the\n// `?? datasets.length > 1` default can actually fire when it isn't passed.\nconst props = withDefaults(defineProps<ThemedChartProps<\"bar\">>(), {\n currencySymbol: \"£\",\n showLegend: undefined,\n});\n\n// The container is the `data-bs-theme` scope the palette resolves against, so a\n// chart inside a dark card on a light page themes from the card (#161).\nconst host = ref<HTMLElement | null>(null);\n\nconst { chartData, mergedOptions } = useThemedChart(\"bar\", props, host);\n</script>\n","<!--\n @component\n A themed line chart. Thin wrapper around vue-chartjs (chart.js) with the\n theme's dedicated chart palette applied (--dx-chart-1..8 stroke + translucent fill, smoothed line)\n and dashboard-friendly defaults. Pass `labels` + `datasets`; `options`\n deep-merges over the defaults.\n\n Requires the optional peer deps `chart.js` and `vue-chartjs`.\n-->\n<template>\n <div ref=\"host\" class=\"dx-chart\">\n <Line :data=\"chartData\" :options=\"mergedOptions\" />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref } from \"vue\";\nimport { Line } from \"vue-chartjs\";\nimport { registerCharts } from \"./chartTheme\";\nimport { useThemedChart, type ThemedChartProps } from \"./useThemedChart\";\n\nregisterCharts();\n\n// `showLegend` defaults to `undefined` (not Vue's Boolean-cast `false`) so the\n// `?? datasets.length > 1` default can actually fire when it isn't passed.\nconst props = withDefaults(defineProps<ThemedChartProps<\"line\">>(), {\n currencySymbol: \"£\",\n showLegend: undefined,\n});\n\n// The container is the `data-bs-theme` scope the palette resolves against, so a\n// chart inside a dark card on a light page themes from the card (#161).\nconst host = ref<HTMLElement | null>(null);\n\nconst { chartData, mergedOptions } = useThemedChart(\"line\", props, host);\n</script>\n","<!--\n @component\n A themed doughnut chart. Thin wrapper around vue-chartjs (chart.js) with one\n chart-palette colour (--dx-chart-1..8) per slice and dashboard-friendly defaults. Pass\n `labels` + either `data` (a number array) or full `datasets`; `options`\n deep-merges over the defaults.\n\n Requires the optional peer deps `chart.js` and `vue-chartjs`.\n-->\n<template>\n <div ref=\"host\" class=\"dx-chart\">\n <Doughnut :data=\"chartData\" :options=\"mergedOptions\" />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, ref } from \"vue\";\nimport { Doughnut } from \"vue-chartjs\";\nimport {\n registerCharts,\n baseOptions,\n applyPalette,\n mergeOptions,\n useColorModeVersion,\n type ChartValueFormat,\n} from \"./chartTheme\";\n\nregisterCharts();\n\ninterface Props {\n /** Slice labels. */\n labels: string[];\n /** Convenience: a single number array (one value per label). */\n data?: number[];\n /** Full Chart.js datasets (alternative to `data`). */\n datasets?: any[];\n /** Chart.js options; deep-merged over the themed defaults. */\n options?: Record<string, any>;\n /** Format tooltip values. */\n valueFormat?: ChartValueFormat;\n /** Currency symbol for valueFormat: \"currency\" (default \"£\"). */\n currencySymbol?: string;\n /** Locale for number formatting. */\n locale?: string;\n /** Show the legend (default: true — slices need identifying). */\n showLegend?: boolean;\n}\n\n// `showLegend` defaults to `undefined` (not Vue's Boolean-cast `false`) so the\n// `?? true` default can actually fire when it isn't passed.\nconst props = withDefaults(defineProps<Props>(), {\n currencySymbol: \"£\",\n showLegend: undefined,\n});\n\n// The container is the `data-bs-theme` scope the palette resolves against, so a\n// chart inside a dark card on a light page themes from the card. Reading the ref\n// inside the computeds also re-themes on mount, when the first (pre-ref) render\n// had to fall back to the document root (#161). DXDoughnutChart doesn't share\n// `useThemedChart` (its data shape differs), so the wiring is repeated here.\nconst host = ref<HTMLElement | null>(null);\n\n// Bumped by a MutationObserver on every `data-bs-theme` change; read below so\n// the CSS-variable palette — invisible to Vue's reactivity — is re-resolved\n// when the colour mode flips under a mounted chart.\nconst colorModeVersion = useColorModeVersion();\n\nconst resolvedDatasets = computed(() =>\n props.datasets ?? [{ data: props.data ?? [] }],\n);\n\nconst chartData = computed(() => {\n void colorModeVersion.value;\n return {\n labels: props.labels,\n datasets: applyPalette(\n resolvedDatasets.value,\n \"doughnut\",\n props.labels.length,\n host.value,\n ),\n };\n});\n\nconst mergedOptions = computed(() => {\n void colorModeVersion.value;\n return mergeOptions(\n baseOptions({\n valueFormat: props.valueFormat,\n currencySymbol: props.currencySymbol,\n locale: props.locale,\n showLegend: props.showLegend ?? true,\n hasValueAxis: false,\n scope: host.value,\n }),\n props.options ?? {},\n );\n});\n</script>\n"],"names":["isPlainObject","value","mergeOptions","base","override","result","key","registered","registerCharts","Chart","registerables","PALETTE_VARS","themeScope","scope","getPalette","host","fallback","styles","name","themeColor","colorModeVersion","ref","colorModeObserver","colorModeWatchers","useColorModeVersion","getCurrentScope","onScopeDispose","withAlpha","color","alpha","hex","r","g","b","rgbMatch","n","formatValue","format","currencySymbol","locale","num","baseOptions","args","valueFormat","showLegend","hasValueAxis","gridColor","tickColor","options","ctx","raw","_a","formatted","label","_b","applyPalette","datasets","kind","labelsCount","palette","single","dataset","index","next","_","i","useThemedChart","type","props","chartData","computed","mergedOptions","__props","_createElementBlock","_createVNode","_unref","Bar","Line","resolvedDatasets","Doughnut"],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Ref } from "vue";
|
|
1
2
|
export type ChartValueFormat = "number" | "currency" | "percent";
|
|
2
3
|
/** Deep-merge consumer `options` over the themed defaults (arrays replace). */
|
|
3
4
|
export declare function mergeOptions(base: Record<string, any>, override: Record<string, any>): Record<string, any>;
|
|
@@ -8,8 +9,26 @@ export declare function mergeOptions(base: Record<string, any>, override: Record
|
|
|
8
9
|
*/
|
|
9
10
|
export declare function registerCharts(): void;
|
|
10
11
|
export declare const PALETTE_VARS: Array<[string, string]>;
|
|
11
|
-
/**
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Resolve the themed categorical palette.
|
|
14
|
+
*
|
|
15
|
+
* @param scope The chart's container element; its computed style decides which
|
|
16
|
+
* `data-bs-theme` scope applies. Omit (or pass null) to read the document
|
|
17
|
+
* root — the pre-#161 behaviour, kept so the exported signature stays
|
|
18
|
+
* backward compatible.
|
|
19
|
+
*/
|
|
20
|
+
export declare function getPalette(scope?: Element | null): string[];
|
|
21
|
+
/**
|
|
22
|
+
* A version ref that increments whenever a `data-bs-theme` attribute changes
|
|
23
|
+
* anywhere in the document. Read it inside a computed that resolves theme
|
|
24
|
+
* variables to make that computed re-run on a colour-mode switch.
|
|
25
|
+
*
|
|
26
|
+
* The observer is started on first use and disconnected when the last consumer's
|
|
27
|
+
* effect scope is disposed, so nothing leaks after the charts unmount. Called
|
|
28
|
+
* outside an effect scope (or without a DOM) it is a no-op returning a static
|
|
29
|
+
* ref — there would be no teardown hook to disconnect the observer.
|
|
30
|
+
*/
|
|
31
|
+
export declare function useColorModeVersion(): Ref<number>;
|
|
13
32
|
/** Add an alpha channel to a hex or rgb() colour (best-effort). */
|
|
14
33
|
export declare function withAlpha(color: string, alpha: number): string;
|
|
15
34
|
/** Format a numeric value per the chart's value format (for tick/tooltips). */
|
|
@@ -22,6 +41,9 @@ export interface BaseOptionArgs {
|
|
|
22
41
|
showLegend?: boolean;
|
|
23
42
|
/** Whether the chart has value axes (bar/line) vs none (doughnut). */
|
|
24
43
|
hasValueAxis?: boolean;
|
|
44
|
+
/** The chart's container element — decides which `data-bs-theme` scope the
|
|
45
|
+
* grid/tick colours resolve against (#161). Defaults to the document root. */
|
|
46
|
+
scope?: Element | null;
|
|
25
47
|
}
|
|
26
48
|
/** Dashboard default Chart.js options merged under any consumer `options`. */
|
|
27
49
|
export declare function baseOptions(args: BaseOptionArgs): Record<string, any>;
|
|
@@ -29,5 +51,9 @@ export declare function baseOptions(args: BaseOptionArgs): Record<string, any>;
|
|
|
29
51
|
* Apply the themed palette to any dataset that doesn't set its own colours.
|
|
30
52
|
* Bar: one colour per dataset (or per bar for a single dataset). Line: a themed
|
|
31
53
|
* stroke + translucent fill. Doughnut/pie: one colour per slice.
|
|
54
|
+
*
|
|
55
|
+
* `scope` is the chart's container element; the palette is resolved from its
|
|
56
|
+
* computed style so a nested `data-bs-theme` container themes correctly (#161).
|
|
57
|
+
* Omit it to resolve against the document root.
|
|
32
58
|
*/
|
|
33
|
-
export declare function applyPalette(datasets: any[], kind: "bar" | "line" | "doughnut", labelsCount: number): any[];
|
|
59
|
+
export declare function applyPalette(datasets: any[], kind: "bar" | "line" | "doughnut", labelsCount: number, scope?: Element | null): any[];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ComputedRef } from "vue";
|
|
1
|
+
import { type ComputedRef, type Ref } from "vue";
|
|
2
2
|
import type { ChartData, ChartDataset, ChartOptions } from "chart.js";
|
|
3
3
|
import { type ChartValueFormat } from "./chartTheme";
|
|
4
4
|
/**
|
|
@@ -28,8 +28,13 @@ export interface ThemedChartProps<TType extends "bar" | "line"> {
|
|
|
28
28
|
* data-viz palette to un-coloured datasets and deep-merges the consumer's
|
|
29
29
|
* `options` over the dashboard-friendly defaults. Pass the reactive `props`
|
|
30
30
|
* object (property access stays reactive inside the computeds).
|
|
31
|
+
*
|
|
32
|
+
* `host` is the chart's own container element (a template ref). It is BOTH the
|
|
33
|
+
* `data-bs-theme` scope the palette resolves against and — being a ref read
|
|
34
|
+
* inside the computeds — the trigger that re-themes once it is populated on
|
|
35
|
+
* mount, since the first render happens before a template ref exists (#161).
|
|
31
36
|
*/
|
|
32
|
-
export declare function useThemedChart<TType extends "bar" | "line">(type: TType, props: ThemedChartProps<TType>): {
|
|
37
|
+
export declare function useThemedChart<TType extends "bar" | "line">(type: TType, props: ThemedChartProps<TType>, host: Ref<HTMLElement | null>): {
|
|
33
38
|
chartData: ComputedRef<ChartData<TType>>;
|
|
34
39
|
mergedOptions: ComputedRef<ChartOptions<TType>>;
|
|
35
40
|
};
|
|
@@ -44,12 +44,33 @@ interface Props {
|
|
|
44
44
|
*/
|
|
45
45
|
cardTabs?: boolean;
|
|
46
46
|
/**
|
|
47
|
-
* Form-wide field layout:
|
|
48
|
-
*
|
|
49
|
-
* `
|
|
50
|
-
*
|
|
47
|
+
* Form-wide field layout:
|
|
48
|
+
*
|
|
49
|
+
* - `"vertical"` (default) — label above input, always.
|
|
50
|
+
* - `"horizontal"` — label left, input right, always.
|
|
51
|
+
* - `"auto"` — horizontal when the form's **own container** is at least
|
|
52
|
+
* `layoutThreshold` px wide, vertical below that. Container-driven, not
|
|
53
|
+
* viewport-driven: a page narrowed by the dashboard sidebar, or a form
|
|
54
|
+
* inside a modal, stacks even though the window is wide (which no
|
|
55
|
+
* Bootstrap media query can see).
|
|
56
|
+
*
|
|
57
|
+
* Overridable per-field via `field.layout`. A field with `span: true`
|
|
58
|
+
* always renders full-width, regardless of layout.
|
|
51
59
|
*/
|
|
52
|
-
layout?: "vertical" | "horizontal";
|
|
60
|
+
layout?: "vertical" | "horizontal" | "auto";
|
|
61
|
+
/**
|
|
62
|
+
* Container width (px) at or above which `layout: "auto"` goes horizontal.
|
|
63
|
+
* Ignored for the explicit `"vertical"`/`"horizontal"` layouts.
|
|
64
|
+
*
|
|
65
|
+
* Default 640, measured rather than guessed: with the default 3-column
|
|
66
|
+
* label the label's text area is `containerWidth / 4 - 18` px, so 640 gives
|
|
67
|
+
* it 142px — enough for a ~20-character label ("Unit price (ex VAT)"
|
|
68
|
+
* measures 128px at the theme's label font) to stay on one line, with the
|
|
69
|
+
* control column still 474px. Below ~584px that label starts wrapping,
|
|
70
|
+
* which is the cramped-label symptom this exists to avoid. Raise it if your
|
|
71
|
+
* labels run longer, or lower `labelCols` instead.
|
|
72
|
+
*/
|
|
73
|
+
layoutThreshold?: number;
|
|
53
74
|
/**
|
|
54
75
|
* Label column width for horizontal layout (mirrors BFormGroup's
|
|
55
76
|
* `labelCols`/`labelCols*` props). Overridable per-field via
|
|
@@ -73,13 +94,14 @@ declare const __VLS_component: import("vue").DefineComponent<__VLS_PublicProps,
|
|
|
73
94
|
onSubmit?: (() => any) | undefined;
|
|
74
95
|
"onUpdate:activeTab"?: ((value: number) => any) | undefined;
|
|
75
96
|
}>, {
|
|
76
|
-
layout: "vertical" | "horizontal";
|
|
97
|
+
layout: "vertical" | "horizontal" | "auto";
|
|
77
98
|
card: boolean;
|
|
78
99
|
submitText: string;
|
|
79
100
|
submitLoadingText: string;
|
|
80
101
|
showSubmit: boolean;
|
|
81
102
|
autoErrorTab: boolean;
|
|
82
103
|
cardTabs: boolean;
|
|
104
|
+
layoutThreshold: number;
|
|
83
105
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
84
106
|
declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
|
|
85
107
|
export default _default;
|
|
@@ -67,6 +67,21 @@ export interface TableField {
|
|
|
67
67
|
filter?: FilterType;
|
|
68
68
|
filterOptions?: FilterOption[];
|
|
69
69
|
filterPlaceholder?: string;
|
|
70
|
+
/**
|
|
71
|
+
* For a `select` filter on a **client-side** table: derive the dropdown's
|
|
72
|
+
* options from the distinct values present in the loaded rows. Defaults to
|
|
73
|
+
* `true` — with no `filterOptions` and no server `filterValues` the
|
|
74
|
+
* alternative is an empty dropdown, which can only ever be a bug.
|
|
75
|
+
*
|
|
76
|
+
* Set `false` to opt out (e.g. a column whose raw values are opaque ids you
|
|
77
|
+
* would rather label yourself via `filterOptions`).
|
|
78
|
+
*
|
|
79
|
+
* Explicit `filterOptions` and server-supplied `filterValues` still WIN, in
|
|
80
|
+
* that order. Provider/API and Inertia modes ignore this flag: they only
|
|
81
|
+
* ever hold one page of rows, so there is no complete value set to derive
|
|
82
|
+
* from (see `getFieldFilterOptions`).
|
|
83
|
+
*/
|
|
84
|
+
deriveFilterOptions?: boolean;
|
|
70
85
|
/**
|
|
71
86
|
* Fixed width for this column, applied via a `<colgroup>` `<col>`. A number
|
|
72
87
|
* is treated as pixels (`200` → `200px`); a string is used verbatim
|
|
@@ -233,6 +248,18 @@ export interface Props<TItem = any> {
|
|
|
233
248
|
* borderless variant — the same header, filter row, table and pagination
|
|
234
249
|
* rendered directly on the page background (no card border/shadow), for
|
|
235
250
|
* data-heavy admin index pages that read as busy inside a card.
|
|
251
|
+
*
|
|
252
|
+
* The table renders FLUSH to the card border (rows and stripes reach the
|
|
253
|
+
* edge) while the header and pagination stay padded. Only the table region
|
|
254
|
+
* is rounded to follow the card's corners — the card itself does not clip
|
|
255
|
+
* (#166), so a `position: absolute` popover you render into `#header` or
|
|
256
|
+
* any other slot is free to overhang the card.
|
|
257
|
+
*
|
|
258
|
+
* The one place content is still clipped is INSIDE the table: Bootstrap's
|
|
259
|
+
* `.table-responsive` is a horizontal scroll container, so an overlay in a
|
|
260
|
+
* `cell(<key>)` slot is bounded by it. Teleport it (bvn's own menus already
|
|
261
|
+
* do — `DDropdown` teleports to `<body>`, the column-filter menus are
|
|
262
|
+
* `fixed`), or pass `:responsive="false"`.
|
|
236
263
|
*/
|
|
237
264
|
card?: boolean;
|
|
238
265
|
/**
|
|
@@ -253,8 +280,13 @@ export interface Props<TItem = any> {
|
|
|
253
280
|
* **"horizontal"** to match the Omni Tend form convention (a raw modal form
|
|
254
281
|
* previously fell back to DXForm's vertical default, which looked out of
|
|
255
282
|
* place next to horizontal page forms). Pass `"vertical"` to opt out.
|
|
283
|
+
*
|
|
284
|
+
* `"auto"` hands the decision to `DXForm`'s container-width observer, which
|
|
285
|
+
* measures the FORM rather than the window — so a modal narrowed by its own
|
|
286
|
+
* breakpoint stacks to vertical even on a wide viewport, where a media query
|
|
287
|
+
* would still report "plenty of room". See `useContainerWidth`.
|
|
256
288
|
*/
|
|
257
|
-
editLayout?: 'vertical' | 'horizontal';
|
|
289
|
+
editLayout?: 'vertical' | 'horizontal' | 'auto';
|
|
258
290
|
/**
|
|
259
291
|
* Label column width for the edit modal's horizontal layout, forwarded to
|
|
260
292
|
* `DXForm`'s `labelCols` (a single width or a per-breakpoint object). Only
|
|
@@ -390,6 +422,11 @@ declare const _default: <T = any>(__VLS_props: NonNullable<Awaited<typeof __VLS_
|
|
|
390
422
|
}>): void;
|
|
391
423
|
attrs: any;
|
|
392
424
|
slots: {
|
|
425
|
+
[x: `head-end(${string})`]: ((props: {
|
|
426
|
+
field: TableField;
|
|
427
|
+
label: string;
|
|
428
|
+
}) => any) | undefined;
|
|
429
|
+
} & {
|
|
393
430
|
[x: string]: ((props: any) => any) | undefined;
|
|
394
431
|
} & {
|
|
395
432
|
[x: string]: ((props: any) => any) | undefined;
|
|
@@ -27,8 +27,13 @@ interface Props {
|
|
|
27
27
|
pendingAction: 'save' | 'delete' | null;
|
|
28
28
|
/** Delete endpoint — gates the Delete button. */
|
|
29
29
|
deleteUrl?: string;
|
|
30
|
-
/**
|
|
31
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Field layout forwarded to the modal's DXForm (defaults handled upstream).
|
|
32
|
+
* Includes `"auto"`, which lets the form stack to vertical when the MODAL is
|
|
33
|
+
* narrow even though the viewport is wide — the container-query case a
|
|
34
|
+
* viewport media query cannot see.
|
|
35
|
+
*/
|
|
36
|
+
layout?: 'vertical' | 'horizontal' | 'auto';
|
|
32
37
|
/** Label column width forwarded to DXForm (horizontal layout). */
|
|
33
38
|
labelCols?: LabelCols;
|
|
34
39
|
/** Wrap the modal form in a card (opt-in — off by default). */
|
|
@@ -18,6 +18,25 @@ interface Props {
|
|
|
18
18
|
pluralItemName: string;
|
|
19
19
|
/** Whether a column filter is active — gates the "Filtered from N" note. */
|
|
20
20
|
hasActiveFilters: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Upper bound (px) on the pager ROW width at which the number window may
|
|
23
|
+
* collapse to the compact "current / last" status (#162). At or above it
|
|
24
|
+
* the full windowed pager always renders, whatever the page count.
|
|
25
|
+
*
|
|
26
|
+
* Default **576** — Bootstrap's `sm` breakpoint, i.e. the width the rest of
|
|
27
|
+
* the design system already treats as phone-sized. Its job is to GUARANTEE
|
|
28
|
+
* that a desktop-width table is never quietly downgraded: a 45-page window
|
|
29
|
+
* that doesn't quite fit at 800px should wrap (today's behaviour), not lose
|
|
30
|
+
* its numbers.
|
|
31
|
+
*
|
|
32
|
+
* Below it, compact engages only when the full pager genuinely would not
|
|
33
|
+
* fit on one line (see `fullPagerWidth`) — so a three-page pager still
|
|
34
|
+
* shows `1 2 3` on a phone, where "2 / 3" would be strictly worse.
|
|
35
|
+
*
|
|
36
|
+
* Measured on the ROW, not the viewport, so a table squeezed by a sidebar
|
|
37
|
+
* or inside a modal collapses even though the window is wide.
|
|
38
|
+
*/
|
|
39
|
+
compactThreshold?: number;
|
|
21
40
|
}
|
|
22
41
|
declare const _default: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
23
42
|
"page-change": (page: number) => any;
|
|
@@ -25,5 +44,7 @@ declare const _default: import("vue").DefineComponent<Props, {}, {}, {}, {}, imp
|
|
|
25
44
|
}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{
|
|
26
45
|
"onPage-change"?: ((page: number) => any) | undefined;
|
|
27
46
|
"onPer-page-change"?: ((value: string | number | (string | number)[] | null) => any) | undefined;
|
|
28
|
-
}>, {
|
|
47
|
+
}>, {
|
|
48
|
+
compactThreshold: number;
|
|
49
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
29
50
|
export default _default;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useContainerWidth — container-query-style responsiveness for Vue components.
|
|
3
|
+
*
|
|
4
|
+
* Bootstrap's breakpoints are **media** queries: they only ever see the WINDOW.
|
|
5
|
+
* A dashboard page is narrowed by the persistent sidebar (and a form inside a
|
|
6
|
+
* modal is narrower still), so a component can have very little room while the
|
|
7
|
+
* viewport is wide — and no media query ever fires. This composable closes that
|
|
8
|
+
* gap by observing an element's **own rendered width** with a `ResizeObserver`
|
|
9
|
+
* and exposing it reactively, so the template can pick a layout from the space
|
|
10
|
+
* it actually has.
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* const { containerRef, width, isBelow, isNarrowerThan } = useContainerWidth({
|
|
14
|
+
* threshold: 640,
|
|
15
|
+
* });
|
|
16
|
+
* // <div ref="containerRef"> … </div>
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* One observer can drive several independent decisions — `isBelow` uses the
|
|
20
|
+
* configured `threshold`, and `isNarrowerThan(n)` answers for any other width:
|
|
21
|
+
*
|
|
22
|
+
* ```ts
|
|
23
|
+
* const stacked = computed(() => isNarrowerThan(576));
|
|
24
|
+
* const hideDetailColumn = computed(() => isNarrowerThan(900));
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
import { type Ref } from "vue";
|
|
28
|
+
/**
|
|
29
|
+
* What to observe: a template ref, or a getter (so the element can be resolved
|
|
30
|
+
* lazily / conditionally — e.g. `() => enabled ? el.value : null` to observe
|
|
31
|
+
* nothing at all).
|
|
32
|
+
*/
|
|
33
|
+
export type ContainerWidthTarget = Ref<HTMLElement | null | undefined> | (() => HTMLElement | null | undefined);
|
|
34
|
+
export interface UseContainerWidthOptions {
|
|
35
|
+
/**
|
|
36
|
+
* Width (px) that `isBelow` compares against. Omit if you only need
|
|
37
|
+
* `width` / `isNarrowerThan`. Pass a getter (`() => props.threshold`) when
|
|
38
|
+
* it can change at runtime — `isBelow` re-evaluates when it does.
|
|
39
|
+
*/
|
|
40
|
+
threshold?: number | (() => number | undefined);
|
|
41
|
+
/**
|
|
42
|
+
* Hysteresis band (px) applied to `isBelow` only, to stop a layout flipping
|
|
43
|
+
* back and forth at the boundary. Once `isBelow` is true it stays true
|
|
44
|
+
* until the width reaches `threshold + hysteresis`.
|
|
45
|
+
*
|
|
46
|
+
* Defaults to `0` so `isBelow` means exactly `width < threshold` unless you
|
|
47
|
+
* ask for otherwise. Opt in whenever the layout you drive from it can
|
|
48
|
+
* change the observed width — the classic loop is: narrow layout is taller
|
|
49
|
+
* → an ancestor with `overflow:auto` gains a vertical scrollbar → the
|
|
50
|
+
* container loses ~15-17px → it crosses back over the threshold → repeat.
|
|
51
|
+
* A band wider than a scrollbar (24px is comfortable) breaks that cycle.
|
|
52
|
+
* (`isNarrowerThan` is deliberately raw — it holds no per-width state.)
|
|
53
|
+
*/
|
|
54
|
+
hysteresis?: number;
|
|
55
|
+
/**
|
|
56
|
+
* Width assumed before the first measurement, and permanently where there
|
|
57
|
+
* is no `ResizeObserver` (SSR, or a very old browser).
|
|
58
|
+
*
|
|
59
|
+
* Defaults to `0` — i.e. "assume the narrowest until proven otherwise", so
|
|
60
|
+
* an unmeasured render picks the STACKED layout. That is the mobile-first
|
|
61
|
+
* choice and the safe one: a stacked layout is legible at every width,
|
|
62
|
+
* whereas a wide layout guessed wrongly truncates content. It also means
|
|
63
|
+
* the server-rendered markup matches the first client render.
|
|
64
|
+
*/
|
|
65
|
+
initialWidth?: number;
|
|
66
|
+
/**
|
|
67
|
+
* Which box to report. `content-box` (default) is the space actually
|
|
68
|
+
* available to children — what a layout decision usually wants.
|
|
69
|
+
* `border-box` matches `getBoundingClientRect().width`.
|
|
70
|
+
*/
|
|
71
|
+
box?: "content-box" | "border-box";
|
|
72
|
+
/**
|
|
73
|
+
* Extra debounce (ms) before publishing a new width. Defaults to `0`:
|
|
74
|
+
* `ResizeObserver` already batches to one delivery per frame, and the
|
|
75
|
+
* callback reads the size the observer *hands* it (no forced layout), so
|
|
76
|
+
* there is nothing to thrash. Set it only for genuinely expensive
|
|
77
|
+
* downstream work — a timer adds a frame of visible lag.
|
|
78
|
+
*/
|
|
79
|
+
debounce?: number;
|
|
80
|
+
}
|
|
81
|
+
export interface UseContainerWidthReturn {
|
|
82
|
+
/** Bind to the element you want measured (`<div ref="containerRef">`). */
|
|
83
|
+
containerRef: Ref<HTMLElement | null>;
|
|
84
|
+
/** The observed width in px. `initialWidth` until first measured. */
|
|
85
|
+
width: Readonly<Ref<number>>;
|
|
86
|
+
/** False until a real measurement has landed (always false without a `ResizeObserver`). */
|
|
87
|
+
hasMeasured: Readonly<Ref<boolean>>;
|
|
88
|
+
/** `width < threshold`, with `hysteresis` applied. False when no `threshold` was given. */
|
|
89
|
+
isBelow: Readonly<Ref<boolean>>;
|
|
90
|
+
/** Raw `width < px`, for driving other decisions off the same observer. */
|
|
91
|
+
isNarrowerThan: (px: number) => boolean;
|
|
92
|
+
/** Disconnect early. Called automatically when the owning scope is disposed. */
|
|
93
|
+
stop: () => void;
|
|
94
|
+
}
|
|
95
|
+
export declare function useContainerWidth(options?: UseContainerWidthOptions): UseContainerWidthReturn;
|
|
96
|
+
export declare function useContainerWidth(target: ContainerWidthTarget, options?: UseContainerWidthOptions): UseContainerWidthReturn;
|