@omnitend/dashboard-for-laravel 0.41.1 → 0.42.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 (62) hide show
  1. package/README.md +2 -1
  2. package/dist/assets/MavenPro-Bold-35b16f06.woff2 +0 -0
  3. package/dist/assets/MavenPro-Medium-febc8674.woff2 +0 -0
  4. package/dist/assets/MavenPro-Regular-aee3df9e.woff2 +0 -0
  5. package/dist/assets/MavenPro-SemiBold-c555dbfa.woff2 +0 -0
  6. package/dist/assets/Poppins-Medium-5800fa57.woff2 +0 -0
  7. package/dist/assets/Poppins-SemiBold-94a36267.woff2 +0 -0
  8. package/dist/charts.js +148 -130
  9. package/dist/charts.js.map +1 -1
  10. package/dist/charts.umd.cjs +1 -1
  11. package/dist/charts.umd.cjs.map +1 -1
  12. package/dist/components/base/DModal.vue.d.ts +26 -2
  13. package/dist/components/charts/chartTheme.d.ts +14 -2
  14. package/dist/components/extended/DXField.vue.d.ts +16 -5
  15. package/dist/components/extended/DXForm.vue.d.ts +25 -9
  16. package/dist/components/extended/DXLoading.vue.d.ts +9 -0
  17. package/dist/components/extended/DXModalActions.vue.d.ts +12 -0
  18. package/dist/components/extended/DXNumberStepper.vue.d.ts +33 -0
  19. package/dist/components/extended/DXSaveButton.vue.d.ts +38 -0
  20. package/dist/components/extended/DXSwitch.vue.d.ts +1 -1
  21. package/dist/components/extended/DXTable.vue.d.ts +8 -2
  22. package/dist/composables/useForm.d.ts +7 -0
  23. package/dist/dashboard-for-laravel.js +9547 -9246
  24. package/dist/dashboard-for-laravel.js.map +1 -1
  25. package/dist/dashboard-for-laravel.umd.cjs +7 -7
  26. package/dist/dashboard-for-laravel.umd.cjs.map +1 -1
  27. package/dist/index.d.ts +5 -0
  28. package/dist/style.css +2 -2
  29. package/dist/types/index.d.ts +20 -3
  30. package/docs/public/api-reference.json +219 -17
  31. package/docs/public/docs-map.md +4 -2
  32. package/docs/public/llms.txt +7 -2
  33. package/package.json +1 -1
  34. package/resources/css/theme.scss +524 -103
  35. package/resources/fonts/maven-pro/MavenPro-Bold.woff2 +0 -0
  36. package/resources/fonts/maven-pro/MavenPro-Medium.woff2 +0 -0
  37. package/resources/fonts/maven-pro/MavenPro-Regular.woff2 +0 -0
  38. package/resources/fonts/maven-pro/MavenPro-SemiBold.woff2 +0 -0
  39. package/resources/fonts/maven-pro/OFL.txt +93 -0
  40. package/resources/fonts/poppins/OFL.txt +93 -0
  41. package/resources/fonts/poppins/Poppins-Medium.woff2 +0 -0
  42. package/resources/fonts/poppins/Poppins-SemiBold.woff2 +0 -0
  43. package/resources/js/components/base/DModal.vue +13 -2
  44. package/resources/js/components/charts/DXBarChart.vue +2 -1
  45. package/resources/js/components/charts/DXDoughnutChart.vue +2 -1
  46. package/resources/js/components/charts/DXLineChart.vue +2 -1
  47. package/resources/js/components/charts/chartTheme.ts +68 -21
  48. package/resources/js/components/extended/DXDashboard.vue +4 -2
  49. package/resources/js/components/extended/DXDashboardSidebar.vue +52 -20
  50. package/resources/js/components/extended/DXField.vue +59 -28
  51. package/resources/js/components/extended/DXForm.vue +122 -21
  52. package/resources/js/components/extended/DXLoading.vue +47 -0
  53. package/resources/js/components/extended/DXModalActions.vue +13 -0
  54. package/resources/js/components/extended/DXNumberStepper.vue +243 -0
  55. package/resources/js/components/extended/DXSaveButton.vue +82 -0
  56. package/resources/js/components/extended/DXSwitch.vue +8 -5
  57. package/resources/js/components/extended/DXTable.vue +58 -22
  58. package/resources/js/components/extended/DXTablePagination.vue +1 -1
  59. package/resources/js/composables/useForm.ts +11 -0
  60. package/resources/js/composables/useToast.ts +1 -1
  61. package/resources/js/index.ts +5 -0
  62. package/resources/js/types/index.ts +21 -3
@@ -1,2 +1,2 @@
1
- (function(i,o){typeof exports=="object"&&typeof module<"u"?o(exports,require("vue"),require("vue-chartjs"),require("chart.js")):typeof define=="function"&&define.amd?define(["exports","vue","vue-chartjs","chart.js"],o):(i=typeof globalThis<"u"?globalThis:i||self,o(i.DashboardForLaravelCharts={},i.Vue,i.VueChartjs,i.Chart))})(this,(function(i,o,h,v){"use strict";const _=r=>!!r&&typeof r=="object"&&!Array.isArray(r);function b(r,e){const t={...r};for(const[n,a]of Object.entries(e))_(a)&&_(t[n])?t[n]=b(t[n],a):t[n]=a;return t}let k=!1;function g(){k||(v.Chart.register(...v.registerables),k=!0)}const S=[["--dx-chart-1","#2563eb"],["--dx-chart-2","#65a30d"],["--dx-chart-3","#7c3aed"],["--dx-chart-4","#0d9488"],["--dx-chart-5","#ea580c"],["--dx-chart-6","#0891b2"],["--dx-chart-7","#d97706"],["--dx-chart-8","#db2777"]];function x(r){return typeof document>"u"||typeof getComputedStyle>"u"?null:r??document.documentElement}function N(r){const e=x(r);if(e===null)return S.map(([,n])=>n);const t=getComputedStyle(e);return S.map(([n,a])=>t.getPropertyValue(n).trim()||a)}function D(r,e,t){const n=x(t);return n===null?e:getComputedStyle(n).getPropertyValue(r).trim()||e}const y=o.ref(0);let m=null,C=0;function $(){return typeof document>"u"||typeof MutationObserver>"u"||o.getCurrentScope()===void 0||(C+=1,m===null&&(m=new MutationObserver(()=>{y.value+=1}),m.observe(document.documentElement,{attributes:!0,attributeFilter:["data-bs-theme"],subtree:!0})),o.onScopeDispose(()=>{C-=1,C===0&&m!==null&&(m.disconnect(),m=null)})),y}function V(r,e){const t=r.replace(/^#/,"");if(/^[0-9a-f]{6}$/i.test(t)){const a=parseInt(t.slice(0,2),16),s=parseInt(t.slice(2,4),16),c=parseInt(t.slice(4,6),16);return`rgba(${a}, ${s}, ${c}, ${e})`}const n=r.match(/^rgba?\(([^)]+)\)$/);if(n){const[a,s,c]=n[1].split(",").map(d=>d.trim());return`rgba(${a}, ${s}, ${c}, ${e})`}return r}function w(r,e,t,n){const a=new Intl.NumberFormat(n,{maximumFractionDigits:2}).format(r);return e==="currency"?`${t}${a}`:e==="percent"?`${a}%`:a}function B(r){const{valueFormat:e,currencySymbol:t="£",locale:n,showLegend:a=!1,hasValueAxis:s=!0,scope:c}=r,d=V(D("--bs-border-color","#dee2e6",c),.6),l=D("--bs-secondary-color","#6c757d",c),f={responsive:!0,maintainAspectRatio:!1,plugins:{legend:{display:a,labels:{color:l}},tooltip:{callbacks:{label:u=>{var j,E;const p=((j=u.parsed)==null?void 0:j.y)??u.parsed??u.raw,A=typeof p=="number"?p:Number(p),O=Number.isFinite(A)?w(A,e,t,n):String(p),M=((E=u.dataset)==null?void 0:E.label)??u.label??"";return M?`${M}: ${O}`:O}}}}};return s&&(f.scales={x:{grid:{display:!1},border:{color:d},ticks:{color:l}},y:{beginAtZero:!0,grid:{color:d,drawBorder:!1},border:{display:!1},ticks:{color:l,callback:u=>w(Number(u),e,t,n)}}}),f}function F(r,e,t,n){const a=N(n),s=r.length===1;return r.map((c,d)=>{const l={...c},f=a[d%a.length];return e==="doughnut"?(l.backgroundColor===void 0&&(l.backgroundColor=Array.from({length:t},(u,p)=>a[p%a.length])),l):e==="line"?(l.borderColor===void 0&&(l.borderColor=f),l.backgroundColor===void 0&&(l.backgroundColor=V(f,.15)),l.tension===void 0&&(l.tension=.3),l.fill===void 0&&(l.fill=!0),l.pointRadius===void 0&&(l.pointRadius=2),l):(l.backgroundColor===void 0&&(l.backgroundColor=s&&t>1?Array.from({length:t},()=>f):f),l.borderRadius===void 0&&(l.borderRadius=4),l)})}function L(r,e,t){const n=$(),a=o.computed(()=>(n.value,{labels:e.labels,datasets:F(e.datasets,r,e.labels.length,t.value)})),s=o.computed(()=>(n.value,b(B({valueFormat:e.valueFormat,currencySymbol:e.currencySymbol,locale:e.locale,showLegend:e.showLegend??e.datasets.length>1,hasValueAxis:!0,scope:t.value}),e.options??{})));return{chartData:a,mergedOptions:s}}const P=o.defineComponent({__name:"DXBarChart",props:{labels:{},datasets:{},options:{},valueFormat:{},currencySymbol:{default:"£"},locale:{},showLegend:{type:Boolean,default:void 0}},setup(r){g();const e=r,t=o.ref(null),{chartData:n,mergedOptions:a}=L("bar",e,t);return(s,c)=>(o.openBlock(),o.createElementBlock("div",{ref_key:"host",ref:t,class:"dx-chart"},[o.createVNode(o.unref(h.Bar),{data:o.unref(n),options:o.unref(a)},null,8,["data","options"])],512))}}),R=o.defineComponent({__name:"DXLineChart",props:{labels:{},datasets:{},options:{},valueFormat:{},currencySymbol:{default:"£"},locale:{},showLegend:{type:Boolean,default:void 0}},setup(r){g();const e=r,t=o.ref(null),{chartData:n,mergedOptions:a}=L("line",e,t);return(s,c)=>(o.openBlock(),o.createElementBlock("div",{ref_key:"host",ref:t,class:"dx-chart"},[o.createVNode(o.unref(h.Line),{data:o.unref(n),options:o.unref(a)},null,8,["data","options"])],512))}}),T=o.defineComponent({__name:"DXDoughnutChart",props:{labels:{},data:{},datasets:{},options:{},valueFormat:{},currencySymbol:{default:"£"},locale:{},showLegend:{type:Boolean,default:void 0}},setup(r){g();const e=r,t=o.ref(null),n=$(),a=o.computed(()=>e.datasets??[{data:e.data??[]}]),s=o.computed(()=>(n.value,{labels:e.labels,datasets:F(a.value,"doughnut",e.labels.length,t.value)})),c=o.computed(()=>(n.value,b(B({valueFormat:e.valueFormat,currencySymbol:e.currencySymbol,locale:e.locale,showLegend:e.showLegend??!0,hasValueAxis:!1,scope:t.value}),e.options??{})));return(d,l)=>(o.openBlock(),o.createElementBlock("div",{ref_key:"host",ref:t,class:"dx-chart"},[o.createVNode(o.unref(h.Doughnut),{data:s.value,options:c.value},null,8,["data","options"])],512))}});i.DXBarChart=P,i.DXDoughnutChart=T,i.DXLineChart=R,Object.defineProperty(i,Symbol.toStringTag,{value:"Module"})}));
1
+ (function(c,n){typeof exports=="object"&&typeof module<"u"?n(exports,require("vue"),require("vue-chartjs"),require("chart.js")):typeof define=="function"&&define.amd?define(["exports","vue","vue-chartjs","chart.js"],n):(c=typeof globalThis<"u"?globalThis:c||self,n(c.DashboardForLaravelCharts={},c.Vue,c.VueChartjs,c.Chart))})(this,(function(c,n,g,S){"use strict";const V=t=>!!t&&typeof t=="object"&&!Array.isArray(t);function y(t,e){const r={...t};for(const[a,l]of Object.entries(e))V(l)&&V(r[a])?r[a]=y(r[a],l):r[a]=l;return r}let D=!1;function C(){D||(S.Chart.register(...S.registerables),D=!0)}const N=[["--dx-chart-1","#7fd7fd"],["--dx-chart-2","#7bf25a"],["--dx-chart-3","#e46ab9"],["--dx-chart-4","#d6e86a"],["--dx-chart-5","#b9a3f0"],["--dx-chart-6","#efd574"],["--dx-chart-7","#9fb4ff"],["--dx-chart-8","#f7a072"]],R=[["--dx-chart-line-1","#5590aa"],["--dx-chart-line-2","#4d9839"],["--dx-chart-line-3","#d161a9"],["--dx-chart-line-4","#848f41"],["--dx-chart-line-5","#907fbb"],["--dx-chart-line-6","#99884a"],["--dx-chart-line-7","#7686be"],["--dx-chart-line-8","#ba7956"]],T=["--dx-chart-edge","#121419"];function B(t){return typeof document>"u"||typeof getComputedStyle>"u"?null:t??document.documentElement}function j(t){return v(N,t)}function X(t){return v(R,t)}function I(t){return v([T],t)[0]}function v(t,e){const r=B(e);if(r===null)return t.map(([,l])=>l);const a=getComputedStyle(r);return t.map(([l,s])=>a.getPropertyValue(l).trim()||s)}function $(t,e,r){const a=B(r);return a===null?e:getComputedStyle(a).getPropertyValue(t).trim()||e}const x=n.ref(0);let f=null,_=0;function L(){return typeof document>"u"||typeof MutationObserver>"u"||n.getCurrentScope()===void 0||(_+=1,f===null&&(f=new MutationObserver(()=>{x.value+=1}),f.observe(document.documentElement,{attributes:!0,attributeFilter:["data-bs-theme"],subtree:!0})),n.onScopeDispose(()=>{_-=1,_===0&&f!==null&&(f.disconnect(),f=null)})),x}function A(t,e){const r=t.replace(/^#/,"");if(/^[0-9a-f]{6}$/i.test(r)){const l=parseInt(r.slice(0,2),16),s=parseInt(r.slice(2,4),16),i=parseInt(r.slice(4,6),16);return`rgba(${l}, ${s}, ${i}, ${e})`}const a=t.match(/^rgba?\(([^)]+)\)$/);if(a){const[l,s,i]=a[1].split(",").map(u=>u.trim());return`rgba(${l}, ${s}, ${i}, ${e})`}return t}function w(t,e,r,a){const l=new Intl.NumberFormat(a,{maximumFractionDigits:2}).format(t);return e==="currency"?`${r}${l}`:e==="percent"?`${l}%`:l}function E(t){const{valueFormat:e,currencySymbol:r="£",locale:a,showLegend:l=!1,hasValueAxis:s=!0,scope:i}=t,u=A($("--bs-border-color","#dee2e6",i),.6),h=$("--bs-secondary-color","#6c757d",i),b={responsive:!0,maintainAspectRatio:!1,plugins:{legend:{display:l,labels:{color:h}},tooltip:{callbacks:{label:o=>{var M,P;const d=((M=o.parsed)==null?void 0:M.y)??o.parsed??o.raw,m=typeof d=="number"?d:Number(d),k=Number.isFinite(m)?w(m,e,r,a):String(d),p=((P=o.dataset)==null?void 0:P.label)??o.label??"";return p?`${p}: ${k}`:k}}}}};return s&&(b.scales={x:{grid:{display:!1},border:{color:u},ticks:{color:h}},y:{beginAtZero:!0,grid:{color:u,drawBorder:!1},border:{display:!1},ticks:{color:h,callback:o=>w(Number(o),e,r,a)}}}),b}function F(t,e,r,a){const l=j(a),s=X(a),i=I(a),u=t.length===1;return t.map((h,b)=>{const o={...h},d=l[b%l.length],m=s[b%s.length];return e==="doughnut"?(o.backgroundColor===void 0&&(o.backgroundColor=Array.from({length:r},(k,p)=>l[p%l.length])),o.borderColor===void 0&&(o.borderColor=i),o.borderWidth===void 0&&(o.borderWidth=1),o):e==="line"?(o.borderColor===void 0&&(o.borderColor=m),o.backgroundColor===void 0&&(o.backgroundColor=A(d,.35)),o.pointBackgroundColor===void 0&&(o.pointBackgroundColor=d),o.pointBorderColor===void 0&&(o.pointBorderColor=m),o.tension===void 0&&(o.tension=.3),o.fill===void 0&&(o.fill=!0),o.pointRadius===void 0&&(o.pointRadius=2),o):(o.backgroundColor===void 0&&(o.backgroundColor=u&&r>1?Array.from({length:r},()=>d):d),o.borderColor===void 0&&(o.borderColor=i),o.borderWidth===void 0&&(o.borderWidth=1),o.borderRadius===void 0&&(o.borderRadius=4),o)})}function O(t,e,r){const a=L(),l=n.computed(()=>(a.value,{labels:e.labels,datasets:F(e.datasets,t,e.labels.length,r.value)})),s=n.computed(()=>(a.value,y(E({valueFormat:e.valueFormat,currencySymbol:e.currencySymbol,locale:e.locale,showLegend:e.showLegend??e.datasets.length>1,hasValueAxis:!0,scope:r.value}),e.options??{})));return{chartData:l,mergedOptions:s}}const W=n.defineComponent({__name:"DXBarChart",props:{labels:{},datasets:{},options:{},valueFormat:{},currencySymbol:{default:"£"},locale:{},showLegend:{type:Boolean,default:void 0}},setup(t){C();const e=t,r=n.ref(null),{chartData:a,mergedOptions:l}=O("bar",e,r);return(s,i)=>(n.openBlock(),n.createElementBlock("div",{ref_key:"host",ref:r,class:"dx-chart"},[n.createVNode(n.unref(g.Bar),{data:n.unref(a),options:n.unref(l)},null,8,["data","options"])],512))}}),q=n.defineComponent({__name:"DXLineChart",props:{labels:{},datasets:{},options:{},valueFormat:{},currencySymbol:{default:"£"},locale:{},showLegend:{type:Boolean,default:void 0}},setup(t){C();const e=t,r=n.ref(null),{chartData:a,mergedOptions:l}=O("line",e,r);return(s,i)=>(n.openBlock(),n.createElementBlock("div",{ref_key:"host",ref:r,class:"dx-chart"},[n.createVNode(n.unref(g.Line),{data:n.unref(a),options:n.unref(l)},null,8,["data","options"])],512))}}),G=n.defineComponent({__name:"DXDoughnutChart",props:{labels:{},data:{},datasets:{},options:{},valueFormat:{},currencySymbol:{default:"£"},locale:{},showLegend:{type:Boolean,default:void 0}},setup(t){C();const e=t,r=n.ref(null),a=L(),l=n.computed(()=>e.datasets??[{data:e.data??[]}]),s=n.computed(()=>(a.value,{labels:e.labels,datasets:F(l.value,"doughnut",e.labels.length,r.value)})),i=n.computed(()=>(a.value,y(E({valueFormat:e.valueFormat,currencySymbol:e.currencySymbol,locale:e.locale,showLegend:e.showLegend??!0,hasValueAxis:!1,scope:r.value}),e.options??{})));return(u,h)=>(n.openBlock(),n.createElementBlock("div",{ref_key:"host",ref:r,class:"dx-chart"},[n.createVNode(n.unref(g.Doughnut),{data:s.value,options:i.value},null,8,["data","options"])],512))}});c.DXBarChart=W,c.DXDoughnutChart=G,c.DXLineChart=q,Object.defineProperty(c,Symbol.toStringTag,{value:"Module"})}));
2
2
  //# sourceMappingURL=charts.umd.cjs.map
@@ -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\";\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
+ {"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// light, vivid fills in a fixed, CVD-validated order, published as\n// --dx-chart-1..8. Deliberately NOT read from the semantic --bs-* colours\n// (#141). Fallbacks mirror the shipped theme so charts still render without\n// the CSS (SSR / tests / a consumer who didn't import it). Sync with\n// theme.scss is enforced by a test that parses the Sass source\n// (charts.test.ts), which is why these lists are exported.\nexport const PALETTE_VARS: Array<[string, string]> = [\n [\"--dx-chart-1\", \"#7fd7fd\"], // sky\n [\"--dx-chart-2\", \"#7bf25a\"], // lime\n [\"--dx-chart-3\", \"#e46ab9\"], // magenta\n [\"--dx-chart-4\", \"#d6e86a\"], // lime-yellow\n [\"--dx-chart-5\", \"#b9a3f0\"], // lavender\n [\"--dx-chart-6\", \"#efd574\"], // butter\n [\"--dx-chart-7\", \"#9fb4ff\"], // periwinkle\n [\"--dx-chart-8\", \"#f7a072\"], // peach\n];\n\n// The line/point shade for each slot (`$dx-chart-line-palette`): the fill\n// darkened to 3.5:1 on white, so a line stroke or point outline clears WCAG\n// 1.4.11 non-text contrast where the light fill alone would not. Same order.\nexport const LINE_PALETTE_VARS: Array<[string, string]> = [\n [\"--dx-chart-line-1\", \"#5590aa\"],\n [\"--dx-chart-line-2\", \"#4d9839\"],\n [\"--dx-chart-line-3\", \"#d161a9\"],\n [\"--dx-chart-line-4\", \"#848f41\"],\n [\"--dx-chart-line-5\", \"#907fbb\"],\n [\"--dx-chart-line-6\", \"#99884a\"],\n [\"--dx-chart-line-7\", \"#7686be\"],\n [\"--dx-chart-line-8\", \"#ba7956\"],\n];\n\n// The 1px outline on bars and doughnut segments (`$dx-chart-edge`): it carries\n// the non-text contrast for the light fills. Dark mode remaps it to the dark\n// body colour.\nexport const EDGE_VAR: [string, string] = [\"--dx-chart-edge\", \"#121419\"];\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 return resolveVars(PALETTE_VARS, scope);\n}\n\n/**\n * Resolve the themed line/point shades (`--dx-chart-line-1..8`), slot for slot\n * with getPalette(). Same scope rules as getPalette().\n */\nexport function getLinePalette(scope?: Element | null): string[] {\n return resolveVars(LINE_PALETTE_VARS, scope);\n}\n\n/** Resolve the bar/segment edge colour (`--dx-chart-edge`). */\nexport function getEdgeColor(scope?: Element | null): string {\n return resolveVars([EDGE_VAR], scope)[0];\n}\n\nfunction resolveVars(vars: Array<[string, string]>, scope?: Element | null): string[] {\n const host = themeScope(scope);\n if (host === null) {\n return vars.map(([, fallback]) => fallback);\n }\n const styles = getComputedStyle(host);\n return 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 * Each property is filled in only when the caller left it undefined.\n * Bar: one fill per dataset (or per bar for a single dataset), with a 1px\n * edge outline. Line: the slot's darker line shade for the stroke and point\n * outlines, the fill for point centres, and a 35% fill wash under the line.\n * Doughnut/pie: one fill per slice, with a 1px edge outline.\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 linePalette = getLinePalette(scope);\n const edge = getEdgeColor(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 const lineColor = linePalette[index % linePalette.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 if (next.borderColor === undefined) next.borderColor = edge;\n if (next.borderWidth === undefined) next.borderWidth = 1;\n return next;\n }\n\n if (kind === \"line\") {\n if (next.borderColor === undefined) next.borderColor = lineColor;\n if (next.backgroundColor === undefined) {\n next.backgroundColor = withAlpha(color, 0.35);\n }\n if (next.pointBackgroundColor === undefined) next.pointBackgroundColor = color;\n if (next.pointBorderColor === undefined) next.pointBorderColor = lineColor;\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.borderColor === undefined) next.borderColor = edge;\n if (next.borderWidth === undefined) next.borderWidth = 1;\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 fills, a 1px --dx-chart-edge outline) applied\n 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-line-1..8 stroke and point outline,\n --dx-chart-1..8 point centre and translucent area 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, a 1px --dx-chart-edge outline, and\n 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","LINE_PALETTE_VARS","EDGE_VAR","themeScope","scope","getPalette","resolveVars","getLinePalette","getEdgeColor","vars","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","linePalette","edge","single","dataset","index","next","lineColor","_","i","useThemedChart","type","props","chartData","computed","mergedOptions","__props","_createElementBlock","_createVNode","_unref","Bar","Line","resolvedDatasets","Doughnut"],"mappings":"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"}
@@ -1,6 +1,30 @@
1
- declare var __VLS_7: string | number, __VLS_8: any;
1
+ declare var __VLS_6: {
2
+ cancel: () => void;
3
+ close: () => void;
4
+ ok: () => void;
5
+ id: string;
6
+ show: () => void;
7
+ hide: (trigger?: string, noTriggerEmit?: boolean) => void;
8
+ toggle: () => void;
9
+ active: boolean;
10
+ visible: boolean;
11
+ }, __VLS_11: {
12
+ cancel: () => void;
13
+ close: () => void;
14
+ ok: () => void;
15
+ id: string;
16
+ show: () => void;
17
+ hide: (trigger?: string, noTriggerEmit?: boolean) => void;
18
+ toggle: () => void;
19
+ active: boolean;
20
+ visible: boolean;
21
+ }, __VLS_14: string, __VLS_15: any;
2
22
  type __VLS_Slots = {} & {
3
- [K in NonNullable<typeof __VLS_7>]?: (props: typeof __VLS_8) => any;
23
+ [K in NonNullable<typeof __VLS_14>]?: (props: typeof __VLS_15) => any;
24
+ } & {
25
+ default?: (props: typeof __VLS_6) => any;
26
+ } & {
27
+ actions?: (props: typeof __VLS_11) => any;
4
28
  };
5
29
  declare const __VLS_component: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
6
30
  declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
@@ -9,6 +9,8 @@ export declare function mergeOptions(base: Record<string, any>, override: Record
9
9
  */
10
10
  export declare function registerCharts(): void;
11
11
  export declare const PALETTE_VARS: Array<[string, string]>;
12
+ export declare const LINE_PALETTE_VARS: Array<[string, string]>;
13
+ export declare const EDGE_VAR: [string, string];
12
14
  /**
13
15
  * Resolve the themed categorical palette.
14
16
  *
@@ -18,6 +20,13 @@ export declare const PALETTE_VARS: Array<[string, string]>;
18
20
  * backward compatible.
19
21
  */
20
22
  export declare function getPalette(scope?: Element | null): string[];
23
+ /**
24
+ * Resolve the themed line/point shades (`--dx-chart-line-1..8`), slot for slot
25
+ * with getPalette(). Same scope rules as getPalette().
26
+ */
27
+ export declare function getLinePalette(scope?: Element | null): string[];
28
+ /** Resolve the bar/segment edge colour (`--dx-chart-edge`). */
29
+ export declare function getEdgeColor(scope?: Element | null): string;
21
30
  /**
22
31
  * A version ref that increments whenever a `data-bs-theme` attribute changes
23
32
  * anywhere in the document. Read it inside a computed that resolves theme
@@ -49,8 +58,11 @@ export interface BaseOptionArgs {
49
58
  export declare function baseOptions(args: BaseOptionArgs): Record<string, any>;
50
59
  /**
51
60
  * Apply the themed palette to any dataset that doesn't set its own colours.
52
- * Bar: one colour per dataset (or per bar for a single dataset). Line: a themed
53
- * stroke + translucent fill. Doughnut/pie: one colour per slice.
61
+ * Each property is filled in only when the caller left it undefined.
62
+ * Bar: one fill per dataset (or per bar for a single dataset), with a 1px
63
+ * edge outline. Line: the slot's darker line shade for the stroke and point
64
+ * outlines, the fill for point centres, and a 35% fill wash under the line.
65
+ * Doughnut/pie: one fill per slice, with a 1px edge outline.
54
66
  *
55
67
  * `scope` is the chart's container element; the palette is resolved from its
56
68
  * computed style so a nested `data-bs-theme` container themes correctly (#161).
@@ -22,7 +22,11 @@ interface Props {
22
22
  * standalone.
23
23
  */
24
24
  layout?: "vertical" | "horizontal";
25
- /** Label column width for horizontal layout (mirrors BFormGroup). */
25
+ /**
26
+ * Label column width for horizontal layout (mirrors BFormGroup's
27
+ * `labelCols`/`labelCols*`). Omitted: the label takes
28
+ * `--dx-form-label-width` (45%) from `sm` up and stacks below.
29
+ */
26
30
  labelCols?: LabelCols;
27
31
  /**
28
32
  * Skip rendering the field's own (outer) label. Applies to the standard
@@ -53,10 +57,15 @@ declare var __VLS_1: {
53
57
  model: any;
54
58
  value: any;
55
59
  update: typeof setValue;
56
- }, __VLS_52: {
60
+ }, __VLS_12: {
61
+ field: FieldDefinition;
62
+ model: any;
63
+ value: any;
64
+ update: typeof setValue;
65
+ }, __VLS_54: {
57
66
  option: FieldOption;
58
67
  on: boolean;
59
- }, __VLS_64: {
68
+ }, __VLS_66: {
60
69
  row: any;
61
70
  index: number;
62
71
  fields: FieldDefinition[];
@@ -72,9 +81,11 @@ type __VLS_Slots = {} & {
72
81
  } & {
73
82
  value?: (props: typeof __VLS_10) => any;
74
83
  } & {
75
- 'switch-list-item'?: (props: typeof __VLS_52) => any;
84
+ value?: (props: typeof __VLS_12) => any;
85
+ } & {
86
+ 'switch-list-item'?: (props: typeof __VLS_54) => any;
76
87
  } & {
77
- 'repeater-row'?: (props: typeof __VLS_64) => any;
88
+ 'repeater-row'?: (props: typeof __VLS_66) => any;
78
89
  };
79
90
  declare const __VLS_component: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
80
91
  declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
@@ -22,6 +22,15 @@ interface Props {
22
22
  submitText?: string;
23
23
  /** Submit button loading text */
24
24
  submitLoadingText?: string;
25
+ /** Submit button label once the form's contents are saved. */
26
+ submitSavedText?: string;
27
+ /**
28
+ * After a successful submission through the form (`form.post/put/…`,
29
+ * read from `form.wasSuccessful`), turn the submit button into a
30
+ * disabled "✓ Saved" until any field changes. On by default; set
31
+ * `false` for forms whose submit is not a save (search, filter).
32
+ */
33
+ savedState?: boolean;
25
34
  /** Show the submit button */
26
35
  showSubmit?: boolean;
27
36
  /** Auto-switch to the first tab containing a validation error. */
@@ -47,7 +56,10 @@ interface Props {
47
56
  * Form-wide field layout:
48
57
  *
49
58
  * - `"vertical"` (default) — label above input, always.
50
- * - `"horizontal"` — label left, input right, always.
59
+ * - `"horizontal"` — label left, input right, whatever the container
60
+ * width. With the default label column that holds from the `sm`
61
+ * viewport breakpoint up (the label stacks on a phone); a numeric
62
+ * `labelCols` keeps the split at every width.
51
63
  * - `"auto"` — horizontal when the form's **own container** is at least
52
64
  * `layoutThreshold` px wide, vertical below that. Container-driven, not
53
65
  * viewport-driven: a page narrowed by the dashboard sidebar, or a form
@@ -62,19 +74,21 @@ interface Props {
62
74
  * Container width (px) at or above which `layout: "auto"` goes horizontal.
63
75
  * Ignored for the explicit `"vertical"`/`"horizontal"` layouts.
64
76
  *
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.
77
+ * Default 640, measured when the default label was 3 columns: that gave
78
+ * the label 142px of text at 640px, just enough for a ~20-character label
79
+ * ("Unit price (ex VAT)" measures 128px at the theme's label font) to stay
80
+ * on one line. With the 45% default label column the same 640px gives the
81
+ * label ~275px and the control ~341px, so the threshold now mostly keeps
82
+ * the control column usable. Raise it if your controls need more room, or
83
+ * set a narrower `labelCols`.
72
84
  */
73
85
  layoutThreshold?: number;
74
86
  /**
75
87
  * Label column width for horizontal layout (mirrors BFormGroup's
76
88
  * `labelCols`/`labelCols*` props). Overridable per-field via
77
- * `field.labelCols`. Ignored when `layout` is "vertical".
89
+ * `field.labelCols`. Ignored when `layout` is "vertical". Omitted (here
90
+ * and on the field): the label takes `--dx-form-label-width` (45%) from
91
+ * `sm` up and stacks above the input below `sm`.
78
92
  */
79
93
  labelCols?: LabelCols;
80
94
  }
@@ -98,6 +112,8 @@ declare const __VLS_component: import("vue").DefineComponent<__VLS_PublicProps,
98
112
  card: boolean;
99
113
  submitText: string;
100
114
  submitLoadingText: string;
115
+ submitSavedText: string;
116
+ savedState: boolean;
101
117
  showSubmit: boolean;
102
118
  autoErrorTab: boolean;
103
119
  cardTabs: boolean;
@@ -0,0 +1,9 @@
1
+ type __VLS_Props = {
2
+ text?: string;
3
+ delay?: number;
4
+ };
5
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
6
+ delay: number;
7
+ text: string;
8
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
9
+ export default _default;
@@ -0,0 +1,12 @@
1
+ declare var __VLS_1: {};
2
+ type __VLS_Slots = {} & {
3
+ default?: (props: typeof __VLS_1) => any;
4
+ };
5
+ declare const __VLS_component: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
6
+ declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
7
+ export default _default;
8
+ type __VLS_WithSlots<T, S> = T & {
9
+ new (): {
10
+ $slots: S;
11
+ };
12
+ };
@@ -0,0 +1,33 @@
1
+ interface Props {
2
+ /** The value (v-model). `null` when the input is empty. */
3
+ modelValue?: number | null;
4
+ /** Lowest value; the decrease button disables here and blur clamps to it. */
5
+ min?: number;
6
+ /** Highest value; the increase button disables here and blur clamps to it. */
7
+ max?: number;
8
+ /** How much one press (or ArrowUp/ArrowDown) changes the value. @default 1 */
9
+ step?: number;
10
+ /** Control size, matching the other inputs. @default 'md' */
11
+ size?: "sm" | "md" | "lg";
12
+ /** Disable the input and both buttons. */
13
+ disabled?: boolean;
14
+ /** Override the decrease button's accessible label. Default: "Decrease", plus the input's aria-label. */
15
+ decreaseLabel?: string;
16
+ /** Override the increase button's accessible label. Default: "Increase", plus the input's aria-label. */
17
+ increaseLabel?: string;
18
+ }
19
+ declare const _default: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
20
+ "update:modelValue": (value: number | null) => any;
21
+ }, string, import("vue").PublicProps, Readonly<Props> & Readonly<{
22
+ "onUpdate:modelValue"?: ((value: number | null) => any) | undefined;
23
+ }>, {
24
+ size: "sm" | "md" | "lg";
25
+ min: number;
26
+ max: number;
27
+ disabled: boolean;
28
+ modelValue: number | null;
29
+ step: number;
30
+ decreaseLabel: string;
31
+ increaseLabel: string;
32
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
33
+ export default _default;
@@ -0,0 +1,38 @@
1
+ import type { ButtonVariant } from "bootstrap-vue-next";
2
+ interface Props {
3
+ /** A save is in flight: busy/spinner state (DButton's `loading`). */
4
+ saving?: boolean;
5
+ /**
6
+ * The form's current contents have been saved: show `savedText` with a
7
+ * tick and disable the button. Clear it when the form changes.
8
+ */
9
+ saved?: boolean;
10
+ /** Label for the saved state. @default 'Saved' */
11
+ savedText?: string;
12
+ /** Label shown beside the spinner while saving (DButton's `loadingText`). */
13
+ savingText?: string;
14
+ /** Variant in every state; the saved state keeps it. @default 'primary' */
15
+ variant?: ButtonVariant | null;
16
+ /** Leading icon when not saved. The saved state shows `check-lg`. */
17
+ icon?: string;
18
+ /** Disable the button regardless of state. */
19
+ disabled?: boolean;
20
+ }
21
+ declare var __VLS_5: {};
22
+ type __VLS_Slots = {} & {
23
+ default?: (props: typeof __VLS_5) => any;
24
+ };
25
+ declare const __VLS_component: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{}>, {
26
+ variant: ButtonVariant | null;
27
+ disabled: boolean;
28
+ saving: boolean;
29
+ saved: boolean;
30
+ savedText: string;
31
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
32
+ declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
33
+ export default _default;
34
+ type __VLS_WithSlots<T, S> = T & {
35
+ new (): {
36
+ $slots: S;
37
+ };
38
+ };
@@ -16,7 +16,7 @@ interface Props {
16
16
  */
17
17
  onVariant?: "success" | "neutral";
18
18
  /**
19
- * `"sm"` renders a compact box (a few px shorter than the default, which
19
+ * `"sm"` renders a compact rounded pill (a few px shorter than the default, which
20
20
  * matches the input height) for dense rows that repeat the switch per item (a
21
21
  * per-line visibility toggle in a modal). Omit for the standard size, which
22
22
  * lines the switch up with the `.form-control`
@@ -64,6 +64,8 @@ export interface TableField {
64
64
  filterAllText?: string;
65
65
  sortable?: boolean;
66
66
  hint?: string;
67
+ /** Help revealed from a focusable info icon beside the column label. */
68
+ info?: string;
67
69
  /**
68
70
  * The inline filter control for this column, or `false`/omit for none. See
69
71
  * `FilterType`. `'select'` (typeahead) and `'select-native'` (plain
@@ -208,7 +210,10 @@ export interface Props<TItem = any> {
208
210
  loadingText?: string;
209
211
  /** Error message */
210
212
  error?: string | null;
211
- /** Pagination data (Inertia mode) */
213
+ /**
214
+ * Pagination data (Inertia mode). Omit it on a plain `items` table and the
215
+ * footer counts the rows given as one page.
216
+ */
212
217
  pagination?: PaginationData;
213
218
  /** Show pagination controls */
214
219
  showPagination?: boolean;
@@ -296,7 +301,8 @@ export interface Props<TItem = any> {
296
301
  /**
297
302
  * Label column width for the edit modal's horizontal layout, forwarded to
298
303
  * `DXForm`'s `labelCols` (a single width or a per-breakpoint object). Only
299
- * meaningful when `editLayout` is "horizontal".
304
+ * meaningful when `editLayout` is "horizontal". Omitted: DXForm's default
305
+ * 45% label column.
300
306
  */
301
307
  editLabelCols?: LabelCols;
302
308
  /**
@@ -21,6 +21,13 @@ export interface FormState<TData extends Record<string, any>> {
21
21
  message: string;
22
22
  touched: Record<string, boolean>;
23
23
  recentlySuccessful: boolean;
24
+ /**
25
+ * Whether the most recent submission succeeded. Unlike
26
+ * `recentlySuccessful` (which clears itself after 1.5s) this stays true
27
+ * until the next submission starts, and is false after a failure — the
28
+ * Inertia `wasSuccessful` semantics. DXForm's saved state reads it.
29
+ */
30
+ wasSuccessful: boolean;
24
31
  shouldShowMessage: boolean;
25
32
  }
26
33
  export interface UseFormReturn<TData extends Record<string, any>> extends FormState<TData> {