@expressivecss/expressive 0.12.0 → 0.13.1

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 (171) hide show
  1. package/CHANGELOG.md +231 -1
  2. package/README.md +37 -1
  3. package/dist/css/expressive.css +5111 -36
  4. package/dist/css/expressive.css.map +1 -1
  5. package/dist/css/expressive.min.css +1 -1
  6. package/dist/css/expressive.min.css.map +1 -1
  7. package/dist/fonts/material-symbols-outlined.woff2 +0 -0
  8. package/dist/fonts/material-symbols-rounded.woff2 +0 -0
  9. package/dist/fonts/material-symbols-sharp.woff2 +0 -0
  10. package/dist/js/chunks/{chunk-HLDXZ7XB.mjs → chunk-3CLXLPYS.mjs} +2 -2
  11. package/dist/js/chunks/chunk-3DHK5CZU.mjs +105 -0
  12. package/dist/js/chunks/chunk-3DHK5CZU.mjs.map +7 -0
  13. package/dist/js/chunks/chunk-4PWAPV6F.mjs +7 -0
  14. package/dist/js/chunks/chunk-4PWAPV6F.mjs.map +7 -0
  15. package/dist/js/chunks/{chunk-HB6IW7QQ.mjs → chunk-55IA2XUC.mjs} +2 -2
  16. package/dist/js/chunks/chunk-CCWDV5CR.mjs +292 -0
  17. package/dist/js/chunks/chunk-CCWDV5CR.mjs.map +7 -0
  18. package/dist/js/chunks/chunk-DTKR54I6.mjs +270 -0
  19. package/dist/js/chunks/chunk-DTKR54I6.mjs.map +7 -0
  20. package/dist/js/chunks/chunk-GVGT6MIU.mjs +47 -0
  21. package/dist/js/chunks/chunk-GVGT6MIU.mjs.map +7 -0
  22. package/dist/js/chunks/chunk-HKT2S7VM.mjs +48 -0
  23. package/dist/js/chunks/chunk-HKT2S7VM.mjs.map +7 -0
  24. package/dist/js/chunks/chunk-K4CJCRGE.mjs +262 -0
  25. package/dist/js/chunks/chunk-K4CJCRGE.mjs.map +7 -0
  26. package/dist/js/chunks/chunk-LNCCYGPC.mjs +163 -0
  27. package/dist/js/chunks/chunk-LNCCYGPC.mjs.map +7 -0
  28. package/dist/js/chunks/chunk-NQLC3YMW.mjs +1 -0
  29. package/dist/js/chunks/chunk-O5XPWXIQ.mjs +406 -0
  30. package/dist/js/chunks/chunk-O5XPWXIQ.mjs.map +7 -0
  31. package/dist/js/chunks/chunk-P3G3QHII.mjs +166 -0
  32. package/dist/js/chunks/chunk-P3G3QHII.mjs.map +7 -0
  33. package/dist/js/chunks/{chunk-H2WA4XHH.mjs → chunk-PLYXLSH3.mjs} +4 -4
  34. package/dist/js/chunks/chunk-QKWPQK5N.mjs +179 -0
  35. package/dist/js/chunks/chunk-QKWPQK5N.mjs.map +7 -0
  36. package/dist/js/chunks/chunk-RHRQKTTX.mjs +139 -0
  37. package/dist/js/chunks/chunk-RHRQKTTX.mjs.map +7 -0
  38. package/dist/js/chunks/chunk-SPETGK25.mjs +59 -0
  39. package/dist/js/chunks/chunk-SPETGK25.mjs.map +7 -0
  40. package/dist/js/chunks/{chunk-YSBCHYXP.mjs → chunk-U2OTHOTI.mjs} +116 -12
  41. package/dist/js/chunks/chunk-U2OTHOTI.mjs.map +7 -0
  42. package/dist/js/chunks/chunk-VTLBW7QS.mjs +124 -0
  43. package/dist/js/chunks/chunk-VTLBW7QS.mjs.map +7 -0
  44. package/dist/js/chunks/{chunk-RFZZCYVU.mjs → chunk-XGHORT7J.mjs} +2 -2
  45. package/dist/js/chunks/{chunk-HNFSMIBY.mjs → chunk-XH6BEEMY.mjs} +69 -16
  46. package/dist/js/chunks/chunk-XH6BEEMY.mjs.map +7 -0
  47. package/dist/js/expressive.cjs +2209 -32
  48. package/dist/js/expressive.cjs.map +4 -4
  49. package/dist/js/expressive.js +2209 -32
  50. package/dist/js/expressive.js.map +4 -4
  51. package/dist/js/expressive.min.js +10 -10
  52. package/dist/js/expressive.min.js.map +4 -4
  53. package/dist/js/expressive.mjs +68 -20
  54. package/dist/js/expressive.mjs.map +1 -1
  55. package/dist/js/internal/behaviors/forms.mjs +1 -1
  56. package/dist/js/internal/components/autocomplete.mjs +2 -2
  57. package/dist/js/internal/components/barChart.mjs +10 -0
  58. package/dist/js/internal/components/barChart.mjs.map +7 -0
  59. package/dist/js/internal/components/chips.mjs +3 -3
  60. package/dist/js/internal/components/columnChart.mjs +9 -0
  61. package/dist/js/internal/components/columnChart.mjs.map +7 -0
  62. package/dist/js/internal/components/commandPalette.mjs +9 -0
  63. package/dist/js/internal/components/commandPalette.mjs.map +7 -0
  64. package/dist/js/internal/components/datepicker.mjs +3 -3
  65. package/dist/js/internal/components/heatmapChart.mjs +9 -0
  66. package/dist/js/internal/components/heatmapChart.mjs.map +7 -0
  67. package/dist/js/internal/components/index.mjs +65 -17
  68. package/dist/js/internal/components/lineChart.mjs +12 -0
  69. package/dist/js/internal/components/lineChart.mjs.map +7 -0
  70. package/dist/js/internal/components/menu.mjs +1 -1
  71. package/dist/js/internal/components/messageScroller.mjs +9 -0
  72. package/dist/js/internal/components/messageScroller.mjs.map +7 -0
  73. package/dist/js/internal/components/mixedChart.mjs +10 -0
  74. package/dist/js/internal/components/mixedChart.mjs.map +7 -0
  75. package/dist/js/internal/components/pieChart.mjs +9 -0
  76. package/dist/js/internal/components/pieChart.mjs.map +7 -0
  77. package/dist/js/internal/components/questionnaire.mjs +9 -0
  78. package/dist/js/internal/components/questionnaire.mjs.map +7 -0
  79. package/dist/js/internal/components/radarChart.mjs +9 -0
  80. package/dist/js/internal/components/radarChart.mjs.map +7 -0
  81. package/dist/js/internal/components/radialChart.mjs +9 -0
  82. package/dist/js/internal/components/radialChart.mjs.map +7 -0
  83. package/dist/js/internal/components/registry.mjs +23 -11
  84. package/dist/js/internal/components/richTextEditor.mjs +9 -0
  85. package/dist/js/internal/components/richTextEditor.mjs.map +7 -0
  86. package/dist/js/internal/components/select.mjs +2 -2
  87. package/dist/js/modular.mjs +68 -20
  88. package/dist/types/behaviors/forms.d.ts +22 -0
  89. package/dist/types/components/barChart.d.ts +24 -0
  90. package/dist/types/components/columnChart.d.ts +21 -0
  91. package/dist/types/components/commandPalette.d.ts +96 -0
  92. package/dist/types/components/heatmapChart.d.ts +49 -0
  93. package/dist/types/components/index.d.ts +12 -0
  94. package/dist/types/components/lineChart.d.ts +134 -0
  95. package/dist/types/components/menu.d.ts +31 -0
  96. package/dist/types/components/messageScroller.d.ts +111 -0
  97. package/dist/types/components/mixedChart.d.ts +25 -0
  98. package/dist/types/components/pieChart.d.ts +40 -0
  99. package/dist/types/components/questionnaire.d.ts +96 -0
  100. package/dist/types/components/radarChart.d.ts +39 -0
  101. package/dist/types/components/radialChart.d.ts +45 -0
  102. package/dist/types/components/registry.d.ts +44 -0
  103. package/dist/types/components/richTextEditor.d.ts +71 -0
  104. package/dist/types/modular.d.ts +10 -1
  105. package/package.json +4 -1
  106. package/src/sass/README.md +3 -6
  107. package/src/sass/abstracts/_index.scss +1 -1
  108. package/src/sass/abstracts/_mixins.scss +226 -0
  109. package/src/sass/abstracts/_variables.scss +14 -0
  110. package/src/sass/base/_typography.scss +45 -0
  111. package/src/sass/components/_accordion.scss +161 -0
  112. package/src/sass/components/_aura.scss +119 -0
  113. package/src/sass/components/_avatar.scss +86 -0
  114. package/src/sass/components/_bar-chart.scss +285 -0
  115. package/src/sass/components/_bottom-sheet.scss +1 -0
  116. package/src/sass/components/_button-groups.scss +13 -3
  117. package/src/sass/components/_cards.scss +16 -13
  118. package/src/sass/components/_column-chart.scss +134 -0
  119. package/src/sass/components/_command-palette.scss +140 -0
  120. package/src/sass/components/_countdown.scss +89 -0
  121. package/src/sass/components/_data-table.scss +188 -0
  122. package/src/sass/components/_dialog.scss +1 -0
  123. package/src/sass/components/_empty-state.scss +78 -0
  124. package/src/sass/components/_expanding-card.scss +3 -0
  125. package/src/sass/components/_heatmap-chart.scss +156 -0
  126. package/src/sass/components/_index.scss +27 -0
  127. package/src/sass/components/_kpi.scss +162 -0
  128. package/src/sass/components/_line-chart.scss +102 -0
  129. package/src/sass/components/_marquee.scss +111 -0
  130. package/src/sass/components/_menu.scss +10 -0
  131. package/src/sass/components/_message-scroller.scss +66 -0
  132. package/src/sass/components/_message.scss +202 -0
  133. package/src/sass/components/_mixed-chart.scss +157 -0
  134. package/src/sass/components/_navigation-bar.scss +1 -0
  135. package/src/sass/components/_navigation-rail.scss +9 -0
  136. package/src/sass/components/_pie-chart.scss +203 -0
  137. package/src/sass/components/_popover.scss +112 -0
  138. package/src/sass/components/_questionnaire.scss +144 -0
  139. package/src/sass/components/_radar-chart.scss +205 -0
  140. package/src/sass/components/_radial-chart.scss +174 -0
  141. package/src/sass/components/_rich-text-editor.scss +99 -0
  142. package/src/sass/components/_search.scss +1 -0
  143. package/src/sass/components/_side-sheet.scss +1 -0
  144. package/src/sass/components/_skeleton.scss +81 -0
  145. package/src/sass/components/_snackbar.scss +1 -0
  146. package/src/sass/components/_stat.scss +86 -0
  147. package/src/sass/components/_stepper.scss +190 -0
  148. package/src/sass/components/_timeline.scss +114 -0
  149. package/src/sass/components/_toolbar.scss +1 -0
  150. package/src/sass/components/_tooltip.scss +2 -0
  151. package/src/sass/components/_tree.scss +139 -0
  152. package/src/sass/components/forms/_drop-zone.scss +128 -0
  153. package/src/sass/components/forms/_index.scss +2 -0
  154. package/src/sass/components/forms/_input-fields.scss +133 -2
  155. package/src/sass/components/forms/_rating.scss +165 -0
  156. package/src/sass/tokens/_reference.scss +3 -15
  157. package/src/sass/utilities/_index.scss +2 -0
  158. package/src/sass/utilities/_scroll-area.scss +35 -0
  159. package/src/sass/utilities/_shapes.scss +164 -0
  160. package/dist/js/chunks/chunk-G3I5H2U3.mjs +0 -7
  161. package/dist/js/chunks/chunk-G3I5H2U3.mjs.map +0 -7
  162. package/dist/js/chunks/chunk-HNFSMIBY.mjs.map +0 -7
  163. package/dist/js/chunks/chunk-PSSHQWCX.mjs +0 -100
  164. package/dist/js/chunks/chunk-PSSHQWCX.mjs.map +0 -7
  165. package/dist/js/chunks/chunk-WWPAPHMA.mjs +0 -1
  166. package/dist/js/chunks/chunk-YSBCHYXP.mjs.map +0 -7
  167. /package/dist/js/chunks/{chunk-HLDXZ7XB.mjs.map → chunk-3CLXLPYS.mjs.map} +0 -0
  168. /package/dist/js/chunks/{chunk-HB6IW7QQ.mjs.map → chunk-55IA2XUC.mjs.map} +0 -0
  169. /package/dist/js/chunks/{chunk-WWPAPHMA.mjs.map → chunk-NQLC3YMW.mjs.map} +0 -0
  170. /package/dist/js/chunks/{chunk-H2WA4XHH.mjs.map → chunk-PLYXLSH3.mjs.map} +0 -0
  171. /package/dist/js/chunks/{chunk-RFZZCYVU.mjs.map → chunk-XGHORT7J.mjs.map} +0 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/ts/components/lineChart.ts"],
4
+ "sourcesContent": ["import { Component, BaseOptions, InitElements, InitElement } from '../core/component';\n\nexport interface LineChartOptions extends BaseOptions {\n /**\n * Lowest value on the y axis. Defaults to the data minimum less 10% of\n * the range; a stacked chart starts at zero or its lowest total, and a\n * column chart always includes zero. Overridden by `data-min` on the chart.\n */\n min: number | null;\n /**\n * Highest value on the y axis. Defaults to the data maximum plus 10% of\n * the range. Overridden by `data-max` on the chart.\n */\n max: number | null;\n}\n\nconst _defaults: LineChartOptions = {\n min: null,\n max: null\n};\n\nconst SVG = 'http://www.w3.org/2000/svg';\nexport const COLORS = 4;\n\n/** An SVG element with its attributes. */\nexport function svgNode(name: string, attributes: Record<string, string> = {}): SVGElement {\n const element = document.createElementNS(SVG, name);\n for (const key in attributes) element.setAttribute(key, attributes[key]);\n return element;\n}\nlet _gradientId = 0;\n\nexport interface Series {\n name: string;\n /** Header cell classes, such as `dashed`. */\n className: string;\n /** Cell text, shown in the tooltip as the author formatted it. */\n text: string[];\n values: (number | null)[];\n /**\n * Where each value is drawn: the value, or in a stacked chart the running\n * total, which is unknown (null) above a gap.\n */\n tops: (number | null)[];\n /** In a stacked chart, the total of the series below, which this area fills down to. */\n base?: (number | null)[];\n /** The series' y scale: a value's distance down the plot, in percent. */\n y: (value: number) => number;\n}\n\n/**\n * Reads a cell as a number. `data-value` wins over the text, which may carry\n * currency or percent signs, grouping commas and a typographic minus around\n * one number. Anything else, such as \"(1,200)\" or \"3\u20135\", is a gap rather\n * than a guess.\n */\nfunction cellValue(cell: Element | undefined): number | null {\n if (!cell) return null;\n const raw = (cell as HTMLElement).dataset.value ?? cell.textContent;\n const text = raw.replace(/\\u2212/g, '-').replace(/[\\s%\\p{Sc}]/gu, '');\n if (!/^[+-]?(\\d{1,3}(,\\d{3})+|\\d+)?(\\.\\d+)?(e[+-]?\\d+)?$/i.test(text) || !/\\d/.test(text)) return null;\n return parseFloat(text.replace(/,/g, ''));\n}\n\n/**\n * A monotone cubic through the points (Fritsch-Butland tangents), so the\n * curve never overshoots a data point. Bezier curves keep their shape under\n * the plot's non-uniform scaling, so this can be drawn in a 0-100 box.\n */\nfunction monotonePath(points: [number, number][]): string {\n const n = points.length;\n if (n === 0) return '';\n if (n === 1) return `M${points[0][0]},${points[0][1]}h0`;\n const slopes: number[] = [];\n for (let i = 0; i < n - 1; i++) {\n slopes.push((points[i + 1][1] - points[i][1]) / (points[i + 1][0] - points[i][0]));\n }\n const tangents = points.map((_, i) => {\n if (i === 0) return slopes[0];\n if (i === n - 1) return slopes[n - 2];\n const [a, b] = [slopes[i - 1], slopes[i]];\n return a * b <= 0 ? 0 : 2 / (1 / a + 1 / b);\n });\n let d = `M${points[0][0]},${points[0][1]}`;\n for (let i = 0; i < n - 1; i++) {\n const [[x0, y0], [x1, y1]] = [points[i], points[i + 1]];\n const h = (x1 - x0) / 3;\n d += `C${x0 + h},${y0 + tangents[i] * h} ${x1 - h},${y1 - tangents[i + 1] * h} ${x1},${y1}`;\n }\n return d;\n}\n\n/**\n * Line chart. The data is the chart's <table>; this class draws it as an SVG\n * with x labels, a legend for more than one series, and a cursor and tooltip\n * that follow the pointer or the arrow keys. Styling is CSS.\n *\n * Re-run `LineChart.init(el)` after changing the table.\n */\nexport class LineChart extends Component<LineChartOptions> {\n /** The values' x positions and series, read from the table. */\n labels: string[] = [];\n series: Series[] = [];\n /** The highlighted row, or -1. */\n activeIndex = -1;\n private _generated: Element[] = [];\n /** The table's header cells and body rows, as read. */\n protected _header: HTMLTableCellElement[] = [];\n protected _rows: HTMLTableRowElement[] = [];\n protected _plot: HTMLElement;\n protected _cursor: HTMLElement;\n protected _tooltip: HTMLElement;\n protected _y: (value: number) => number;\n /**\n * Whether the rows run right to left: a horizontal row axis in a\n * right-to-left context, like the labels. Bar chart rows keep their order.\n */\n protected _reversed = false;\n\n /** The class prefix of the generated parts; ColumnChart reuses this class. */\n protected get _type() {\n return 'line-chart';\n }\n\n /** Whether the y axis always includes zero, as columns grow from it. */\n protected get _fromZero() {\n return false;\n }\n\n /**\n * Whether the rows run down the plot and the values across it, as in a\n * bar chart. The rows then keep their order in a right-to-left context.\n */\n protected get _horizontal() {\n return false;\n }\n\n /**\n * Whether the rows go round a circle, as in a pie or radar chart. The\n * arrows then step round it, wrapping from the last row to the first.\n */\n protected get _circular() {\n return false;\n }\n\n /** Whether the cursor marks each series with a point. */\n protected get _points() {\n return true;\n }\n\n protected get _key() {\n return 'Expressive_LineChart';\n }\n\n constructor(el: HTMLElement, options: Partial<LineChartOptions>) {\n super(el, options, new.target);\n this.el[this._key] = this;\n\n this.options = {\n ...new.target.defaults,\n ...options\n };\n\n this._read();\n if (!this.labels.length || !this.series.length) return;\n this._reversed = !this._horizontal && getComputedStyle(this.el).direction === 'rtl';\n this._render();\n if (!this.el.classList.contains('sparkline')) {\n this._plot.tabIndex = 0;\n // A tap has no move before it, so the press picks the row too.\n this._plot.addEventListener('pointerdown', this._onPointerMove);\n this._plot.addEventListener('pointermove', this._onPointerMove);\n this._plot.addEventListener('pointerleave', this._onLeave);\n this._plot.addEventListener('pointercancel', this._onCancel);\n this._plot.addEventListener('keydown', this._onKeyDown);\n this._plot.addEventListener('focus', this._onFocus);\n this._plot.addEventListener('blur', this._onLeave);\n }\n }\n\n static get defaults(): LineChartOptions {\n return _defaults;\n }\n\n static init(el: HTMLElement, options?: Partial<LineChartOptions>): LineChart;\n static init(els: InitElements<InitElement>, options?: Partial<LineChartOptions>): LineChart[];\n static init(\n els: HTMLElement | InitElements<InitElement>,\n options: Partial<LineChartOptions> = {}\n ): LineChart | LineChart[] {\n return super.init(els, options, this);\n }\n\n static getInstance(el: HTMLElement): LineChart {\n return el['Expressive_LineChart'];\n }\n\n destroy() {\n // The listeners live on the plot, which goes with the generated nodes.\n this._generated.forEach((node) => node.remove());\n this._generated = [];\n this.el[this._key] = undefined;\n }\n\n /**\n * Highlights one row: moves the cursor and fills the tooltip.\n * Pass -1 to hide them. Does nothing for a chart without data.\n */\n show(index: number) {\n // Skipping a repeat also keeps the live tooltip from re-announcing.\n if (!this._tooltip || index === this.activeIndex) return;\n this.activeIndex = index;\n const hidden = index < 0 || index >= this.labels.length;\n this._cursor.hidden = this._tooltip.hidden = hidden;\n if (hidden) return;\n\n this._cursor.replaceChildren();\n const title = document.createElement('strong');\n title.textContent = this.labels[index];\n this._tooltip.replaceChildren(title);\n\n this.series.forEach((series, i) => {\n const value = series.values[index];\n if (value === null) return;\n const color = String((i % COLORS) + 1);\n const top = series.tops[index];\n if (top !== null && this._points) {\n const dot = document.createElement('span');\n dot.className = series.className;\n dot.dataset.series = color;\n dot.style.top = `${series.y(top)}%`;\n this._cursor.append(dot);\n }\n const name = document.createElement('span');\n name.dataset.series = color;\n name.textContent = series.name;\n const text = document.createElement('span');\n text.textContent = series.text[index];\n this._tooltip.append(name, text);\n });\n this._place(index);\n }\n\n /**\n * Moves the filled cursor and tooltip to a row: beside its band, with the\n * tooltip opening back over the plot past the middle.\n */\n protected _place(index: number) {\n const x = this._x(index);\n const side = this._horizontal ? 'top' : 'left';\n this._cursor.style[side] = this._tooltip.style[side] = `${x}%`;\n this._tooltip.classList.toggle('end', x > 50);\n }\n\n private _read() {\n const table = this.el.querySelector<HTMLTableElement>(':scope > table');\n if (!table) return;\n // Without a <thead> the parser puts the header row in the first <tbody>.\n const headerRow = table.tHead?.rows[0] ?? table.rows[0];\n const header = Array.from(headerRow?.cells ?? []);\n const rows = Array.from(table.tBodies)\n .flatMap((body) => Array.from(body.rows))\n .filter((row) => row !== headerRow);\n [this._header, this._rows] = [header, rows];\n this.labels = rows.map((row) => row.cells[0]?.textContent.trim() ?? '');\n this.series = header.slice(1).map((cell, i) => {\n const values = rows.map((row) => cellValue(row.cells[i + 1]));\n return {\n name: cell.textContent.trim(),\n className: cell.className,\n text: rows.map((row) => row.cells[i + 1]?.textContent.trim() ?? ''),\n values,\n tops: values,\n y: undefined\n };\n });\n const stacked = this.el.classList.contains('stacked');\n if (stacked) {\n // A gap leaves the total unknown, so the series above break there too.\n // ponytail: negative values overlap the band below; diverging stacks if needed.\n const totals: (number | null)[] = this.labels.map(() => 0);\n for (const series of this.series) {\n series.base = [...totals];\n series.tops = series.values.map((value, i) =>\n (totals[i] = value === null || totals[i] === null ? null : totals[i] + value));\n }\n }\n\n if (this.series.every((series) => series.tops.every((value) => value === null))) this.series = [];\n const option = (name: 'min' | 'max') => this._number(name) ?? this.options[name];\n this._y = this._scale(this.series, option('min'), option('max'));\n }\n\n /** A number from one of the chart's data attributes, or null. */\n protected _number(name: string) {\n const value = parseFloat(this.el.dataset[name] ?? '');\n return Number.isFinite(value) ? value : null;\n }\n\n /**\n * Fits a y scale to the series' tops, between `min` and `max` where they\n * are set, and gives it to each series.\n */\n protected _scale(series: Series[], min: number | null, max: number | null, fromZero = this._fromZero) {\n const stacked = this.el.classList.contains('stacked');\n let [low, high] = [Infinity, -Infinity];\n for (const { tops } of series) {\n for (const value of tops) {\n if (value === null) continue;\n low = Math.min(low, value);\n high = Math.max(high, value);\n }\n }\n if (low > high) low = high = 0;\n if (fromZero) {\n // Zero is on the scale, with 10% of the span to spare on each side\n // that has data.\n const [bottom, top] = [Math.min(0, low), Math.max(0, high)];\n const room = (top - (min ?? bottom)) * 0.1 || 1;\n min ??= bottom < 0 ? bottom - room : 0;\n max ??= top + room;\n } else {\n const pad = (high - low) * 0.1 || 1;\n // Stacked areas are read against zero, with the headroom of that span.\n min ??= stacked ? Math.min(0, low) : low - pad;\n max ??= high + (stacked ? (high - min) * 0.1 || 1 : pad);\n }\n const y = (value: number) => 100 - ((value - min) / (max - min || 1)) * 100;\n for (const each of series) each.y = y;\n return y;\n }\n\n /** Each row sits in the middle of an equal band, beside its label. */\n private _x(index: number) {\n const x = ((index + 0.5) / this.labels.length) * 100;\n return this._reversed ? 100 - x : x;\n }\n\n private _render() {\n const type = this._type;\n this._plot = document.createElement('div');\n this._plot.className = `${type}-plot`;\n const caption = this.el.querySelector(':scope > figcaption')?.textContent.trim();\n this._plot.setAttribute('role', 'group');\n this._plot.setAttribute('aria-roledescription', type.replace(/-/g, ' '));\n if (caption) this._plot.setAttribute('aria-label', caption);\n\n this._cursor = document.createElement('div');\n this._cursor.className = `${type}-cursor`;\n this._cursor.hidden = true;\n this._tooltip = document.createElement('div');\n this._tooltip.className = `${type}-tooltip`;\n this._tooltip.hidden = true;\n this._tooltip.setAttribute('aria-live', 'polite');\n this._plot.append(this._draw(), this._cursor, this._tooltip);\n\n this._generated = [this._plot, ...this._guides()];\n this.el.append(...this._generated);\n }\n\n /**\n * The <tfoot> row as a total to show over the plot, its value over its\n * label, or null without one.\n */\n protected _total(className: string): HTMLElement | null {\n const footer = this.el.querySelector<HTMLTableElement>(':scope > table')?.tFoot?.rows[0];\n if (!footer) return null;\n const total = document.createElement('div');\n total.className = className;\n const [label, value] = [document.createElement('span'), document.createElement('strong')];\n label.textContent = footer.cells[0]?.textContent.trim() ?? '';\n value.textContent = footer.cells[1]?.textContent.trim() ?? '';\n total.append(value, label);\n return total;\n }\n\n /** The parts below the plot: the row labels and the legend. */\n protected _guides(): Element[] {\n const labels = document.createElement('ol');\n labels.className = `${this._type}-labels`;\n labels.setAttribute('aria-hidden', 'true');\n // A loop rather than a spread: a spread of every row overflows the stack.\n for (const text of this.labels) {\n const li = document.createElement('li');\n li.textContent = text;\n labels.append(li);\n }\n return [labels, ...this._legend()];\n }\n\n /** The series legend, for more than one series. */\n protected _legend(): Element[] {\n if (this.series.length < 2) return [];\n const legend = document.createElement('ul');\n legend.className = `${this._type}-legend`;\n legend.setAttribute('aria-hidden', 'true');\n legend.append(...this.series.map((series, i) => {\n const li = document.createElement('li');\n li.dataset.series = String((i % COLORS) + 1);\n li.textContent = series.name;\n return li;\n }));\n return [legend];\n }\n\n /** Draws the series, which fill the plot. */\n protected _draw(drawn = this.series): Element {\n const svg = document.createElementNS(SVG, 'svg');\n svg.setAttribute('viewBox', '0 0 100 100');\n svg.setAttribute('preserveAspectRatio', 'none');\n svg.setAttribute('aria-hidden', 'true');\n svg.setAttribute('focusable', 'false');\n\n for (const series of drawn) {\n const group = document.createElementNS(SVG, 'g');\n group.setAttribute('class', `${this._type}-series ${series.className}`.trim());\n group.dataset.series = String((this.series.indexOf(series) % COLORS) + 1);\n\n // A gap in the data breaks the line into runs of row indexes.\n const runs: number[][] = [[]];\n series.tops.forEach((value, index) => {\n if (value === null) runs.push([]);\n else runs[runs.length - 1].push(index);\n });\n const filled = runs.filter((run) => run.length);\n const points = (run: number[], values: (number | null)[]) =>\n run.map((index): [number, number] => [this._x(index), series.y(values[index]!)]);\n const tops = filled.map((run) => monotonePath(points(run, series.tops)));\n\n const id = `line-chart-gradient-${++_gradientId}`;\n const gradient = document.createElementNS(SVG, 'linearGradient');\n gradient.id = id;\n gradient.setAttribute('x2', '0');\n gradient.setAttribute('y2', '1');\n gradient.append(document.createElementNS(SVG, 'stop'), document.createElementNS(SVG, 'stop'));\n gradient.lastElementChild.setAttribute('offset', '1');\n\n const area = document.createElementNS(SVG, 'path');\n area.setAttribute('class', `${this._type}-area`);\n area.setAttribute('fill', `url(#${id})`);\n // A stacked area runs back along the series below; the reversed curve\n // is the same curve. Its base stops at the plot bottom when the y axis\n // starts above zero.\n area.setAttribute('d', filled\n .map((run, r) => {\n if (!series.base) return `${tops[r]}V100H${this._x(run[0])}Z`;\n const base = points(run, series.base).map(([x, y]): [number, number] => [x, Math.min(100, y)]);\n return `${tops[r]}L${monotonePath(base.reverse()).slice(1)}Z`;\n })\n .join(''));\n\n const line = document.createElementNS(SVG, 'path');\n line.setAttribute('class', `${this._type}-line`);\n line.setAttribute('d', tops.join(''));\n\n group.append(gradient, area, line);\n svg.append(group);\n }\n return svg;\n }\n\n private _onPointerMove = (e: PointerEvent) => {\n this.show(this._indexAt(e));\n };\n\n /** The row under the pointer, or -1. */\n protected _indexAt(e: PointerEvent) {\n const box = this._plot.getBoundingClientRect();\n const fraction = this._horizontal\n ? (e.clientY - box.top) / box.height\n : (e.clientX - box.left) / box.width;\n const index = Math.floor((this._reversed ? 1 - fraction : fraction) * this.labels.length);\n return Math.min(this.labels.length - 1, Math.max(0, index));\n }\n\n /**\n * For a circular chart, the pointer's position from the plot's middle: a\n * fraction of a turn clockwise from the top, and its distance as a share\n * of the plot's width.\n */\n protected _polarAt(e: PointerEvent): [turn: number, distance: number] {\n const box = this._plot.getBoundingClientRect();\n const [x, y] = [e.clientX - box.left - box.width / 2, e.clientY - box.top - box.height / 2];\n return [(Math.atan2(x, -y) / (2 * Math.PI) + 1) % 1, Math.hypot(x, y) / box.width];\n }\n\n /** The keys that move to the next row and to the one before. */\n protected get _arrows(): [string[], string[]] {\n // Round a circle both pairs work. The rows run clockwise in RTL too, so\n // right still moves the way the top row turns.\n if (this._circular) return [['ArrowRight', 'ArrowDown'], ['ArrowLeft', 'ArrowUp']];\n // Arrows move the cursor the way they point, so they swap in RTL.\n if (this._horizontal) return [['ArrowDown'], ['ArrowUp']];\n return this._reversed ? [['ArrowLeft'], ['ArrowRight']] : [['ArrowRight'], ['ArrowLeft']];\n }\n\n private _onFocus = () => {\n // Only keyboard focus picks the first row; a press off the marks, which\n // focuses the plot too, leaves the tooltip hidden.\n if (this.activeIndex < 0 && this._plot.matches(':focus-visible')) this.show(0);\n };\n\n private _onLeave = (e: Event) => {\n // A touch leaves as it lifts; the tapped row stays until the plot blurs.\n if ((e as PointerEvent).pointerType === 'touch' || this._plot.matches(':focus-visible')) return;\n this.show(-1);\n };\n\n /** The browser took the touch to scroll the page. */\n private _onCancel = () => {\n this.show(-1);\n };\n\n /** The index a key moves to, or undefined for a key the chart ignores. */\n protected _keyTarget(key: string): number | undefined {\n const last = this.labels.length - 1;\n const [forward, back] = this._arrows;\n const step = (by: number) => {\n if (!this._circular) return Math.min(last, Math.max(0, this.activeIndex + by));\n // Round a circle the ends meet; from no row, back starts at the last.\n if (this.activeIndex < 0) return by > 0 ? 0 : last;\n return (this.activeIndex + by + last + 1) % (last + 1);\n };\n // With nothing shown, Escape is left to a dialog or sheet around the chart.\n if (key === 'Escape') return this.activeIndex < 0 ? undefined : -1;\n return forward.includes(key) ? step(1)\n : back.includes(key) ? step(-1)\n : { Home: 0, End: last }[key];\n }\n\n private _onKeyDown = (e: KeyboardEvent) => {\n const next = this._keyTarget(e.key);\n if (next === undefined) return;\n e.preventDefault();\n this.show(next);\n };\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,166 @@
1
+ import {
2
+ LineChart,
3
+ svgNode
4
+ } from "./chunk-O5XPWXIQ.mjs";
5
+
6
+ // src/ts/components/pieChart.ts
7
+ var COLORS = 6;
8
+ var RADIUS = 46;
9
+ var GROWN = 1.06;
10
+ var LABEL_SHARE = 0.05;
11
+ var _maskId = 0;
12
+ var PieChart = class extends LineChart {
13
+ get _type() {
14
+ return "pie-chart";
15
+ }
16
+ get _key() {
17
+ return "Expressive_PieChart";
18
+ }
19
+ get _points() {
20
+ return false;
21
+ }
22
+ get _circular() {
23
+ return true;
24
+ }
25
+ static init(els, options = {}) {
26
+ return super.init(els, options);
27
+ }
28
+ static getInstance(el) {
29
+ return el["Expressive_PieChart"];
30
+ }
31
+ show(index) {
32
+ if (!this._tooltip || index === this.activeIndex) return;
33
+ super.show(index);
34
+ this._slices.forEach((slice, i) => slice?.classList.toggle("active", i === index));
35
+ if (this._tooltip.hidden) return;
36
+ const color = this._colors[index];
37
+ this._tooltip.querySelectorAll("[data-series]").forEach((span) => {
38
+ if (color) span.dataset.series = color;
39
+ else delete span.dataset.series;
40
+ });
41
+ const share = this._share(index);
42
+ const value = this._tooltip.querySelector("span:nth-of-type(2)");
43
+ if (share && value) value.textContent += ` (${share})`;
44
+ }
45
+ /** CSS places the tooltip on the slice's middle from --turn. */
46
+ _place(index) {
47
+ const middle = (this._turns[index][0] + this._turns[index][1]) / 2;
48
+ this._tooltip.style.setProperty("--turn", String(middle));
49
+ this._tooltip.classList.toggle("end", middle < 0.5);
50
+ }
51
+ /** The row's share of the whole as a percentage, or '' for none. */
52
+ _share(index) {
53
+ const [start, end] = this._turns[index];
54
+ if (end <= start) return "";
55
+ const share = end - start;
56
+ const options = { style: "percent", maximumFractionDigits: share < 0.1 ? 1 : 0 };
57
+ try {
58
+ return share.toLocaleString(this.el.closest("[lang]")?.lang || void 0, options);
59
+ } catch {
60
+ return share.toLocaleString(void 0, options);
61
+ }
62
+ }
63
+ /** The legend: every row with its color, its cell and its share. */
64
+ _guides() {
65
+ const legend = document.createElement("ul");
66
+ legend.className = "pie-chart-legend";
67
+ legend.setAttribute("aria-hidden", "true");
68
+ const text = this.series[0].text;
69
+ legend.append(...this.labels.map((label, i) => {
70
+ const li = document.createElement("li");
71
+ if (this._colors[i]) li.dataset.series = this._colors[i];
72
+ const [name, value, share] = [label, text[i], this._share(i)].map((content) => {
73
+ const span = document.createElement("span");
74
+ span.textContent = content;
75
+ return span;
76
+ });
77
+ li.append(name, value, share);
78
+ return li;
79
+ }));
80
+ return [legend];
81
+ }
82
+ /** The row under the pointer by its angle from the middle, or -1 off the slices. */
83
+ _indexAt(e) {
84
+ const [turn, share] = this._polarAt(e);
85
+ const distance = share * 100;
86
+ const hole = this.el.classList.contains("donut") ? parseFloat(getComputedStyle(this.el).getPropertyValue("--md-comp-pie-chart-hole")) || 0 : 0;
87
+ if (distance < RADIUS * hole) return -1;
88
+ const index = this._turns.findIndex(([start, end]) => start <= turn && turn < end);
89
+ return distance > RADIUS * (index === this.activeIndex ? GROWN : 1) ? -1 : index;
90
+ }
91
+ /**
92
+ * An SVG of slices in a -50 to 50 box, under a mask that cuts the gaps
93
+ * between them and the donut's hole, which CSS sizes. The values and the
94
+ * total are HTML over it.
95
+ */
96
+ _draw() {
97
+ const values = this.series[0].values.map((value) => Math.max(0, value ?? 0));
98
+ const total = values.reduce((sum, value) => sum + value, 0);
99
+ let turn = 0;
100
+ this._turns = values.map((value) => [turn, turn += total ? value / total : 0]);
101
+ const drawn = this._turns.filter(([start, end]) => end > start).length;
102
+ let slice = 0;
103
+ this._colors = this._turns.map(([start, end]) => {
104
+ if (end <= start) return void 0;
105
+ const index = slice++;
106
+ return String(index === drawn - 1 && index > 0 && index % COLORS === 0 ? 2 : index % COLORS + 1);
107
+ });
108
+ const at = (t, r = RADIUS) => {
109
+ const angle = t * 2 * Math.PI;
110
+ return `${(r * Math.sin(angle)).toFixed(3)},${(-r * Math.cos(angle)).toFixed(3)}`;
111
+ };
112
+ const pie = document.createElement("div");
113
+ pie.className = "pie-chart-pie";
114
+ pie.setAttribute("aria-hidden", "true");
115
+ const svg = svgNode("svg", { viewBox: "-50 -50 100 100", focusable: "false" });
116
+ const id = `pie-chart-mask-${++_maskId}`;
117
+ const area = { x: "-50", y: "-50", width: "100", height: "100" };
118
+ const mask = svgNode("mask", { id, maskUnits: "userSpaceOnUse", ...area });
119
+ const edges = this._turns.filter(([start, end]) => end > start);
120
+ mask.append(
121
+ svgNode("rect", { ...area, fill: "white" }),
122
+ svgNode("path", {
123
+ class: "pie-chart-gaps",
124
+ stroke: "black",
125
+ d: edges.length > 1 ? edges.map(([start]) => `M0,0L${at(start, 50)}`).join("") : ""
126
+ }),
127
+ svgNode("circle", { class: "pie-chart-hole", r: String(RADIUS), fill: "black" })
128
+ );
129
+ const slices = svgNode("g", { mask: `url(#${id})` });
130
+ const arc = `A${RADIUS},${RADIUS} 0`;
131
+ this._slices = this._turns.map(([start, end], i) => {
132
+ if (end <= start) return null;
133
+ const slice2 = svgNode("path", {
134
+ class: "pie-chart-slice",
135
+ "data-series": this._colors[i],
136
+ // A whole circle is two half arcs; one arc cannot end where it starts.
137
+ d: end - start >= 1 ? `M0,${-RADIUS}${arc} 1 1 0,${RADIUS}${arc} 1 1 0,${-RADIUS}Z` : `M0,0L${at(start)}${arc} ${end - start > 0.5 ? 1 : 0} 1 ${at(end)}Z`
138
+ });
139
+ slices.append(slice2);
140
+ return slice2;
141
+ });
142
+ svg.append(mask, slices);
143
+ pie.append(svg);
144
+ if (this.el.classList.contains("values")) {
145
+ const labels = document.createElement("div");
146
+ labels.className = "pie-chart-values";
147
+ this._turns.forEach(([start, end], i) => {
148
+ if (end - start < LABEL_SHARE) return;
149
+ const label = document.createElement("span");
150
+ label.dataset.series = this._colors[i];
151
+ label.style.setProperty("--turn", String((start + end) / 2));
152
+ label.textContent = this._share(i);
153
+ labels.append(label);
154
+ });
155
+ pie.append(labels);
156
+ }
157
+ const middle = this._total("pie-chart-total");
158
+ if (middle) pie.append(middle);
159
+ return pie;
160
+ }
161
+ };
162
+
163
+ export {
164
+ PieChart
165
+ };
166
+ //# sourceMappingURL=chunk-P3G3QHII.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/ts/components/pieChart.ts"],
4
+ "sourcesContent": ["import { InitElements, InitElement } from '../core/component';\nimport { LineChart, LineChartOptions, svgNode as node } from './lineChart';\n\nexport type PieChartOptions = LineChartOptions;\n\n// ponytail: rows past six repeat the colors; group small parts as \"Other\".\nconst COLORS = 6;\n/** The pie's radius in a 100-unit box, leaving room for the active slice to grow. */\nconst RADIUS = 46;\n/** The active slice's scale, as in _pie-chart.scss. */\nconst GROWN = 1.06;\n/** Slices smaller than this share get no .values label. */\nconst LABEL_SHARE = 0.05;\nlet _maskId = 0;\n\n/**\n * Pie chart. It reads its <table> like LineChart, but draws each row of the\n * first value column as a slice of the whole, clockwise from the top, with\n * a legend of every row and its share. With `.donut`, a <tfoot> row is the\n * total in the middle. Further columns show in the tooltip.\n *\n * Re-run `PieChart.init(el)` after changing the table.\n */\nexport class PieChart extends LineChart {\n // No initializers: LineChart's constructor draws before they would run.\n /** Each row's start and end, as fractions of a turn clockwise from the top. */\n private _turns: [number, number][];\n /** Each row's color, or undefined for a row with no slice. */\n private _colors: (string | undefined)[];\n private _slices: (SVGPathElement | null)[];\n\n protected get _type() {\n return 'pie-chart';\n }\n\n protected get _key() {\n return 'Expressive_PieChart';\n }\n\n protected get _points() {\n return false;\n }\n\n protected get _circular() {\n return true;\n }\n\n static init(el: HTMLElement, options?: Partial<PieChartOptions>): PieChart;\n static init(els: InitElements<InitElement>, options?: Partial<PieChartOptions>): PieChart[];\n static init(\n els: HTMLElement | InitElements<InitElement>,\n options: Partial<PieChartOptions> = {}\n ): PieChart | PieChart[] {\n return super.init(els as HTMLElement, options) as PieChart | PieChart[];\n }\n\n static getInstance(el: HTMLElement): PieChart {\n return el['Expressive_PieChart'];\n }\n\n show(index: number) {\n if (!this._tooltip || index === this.activeIndex) return;\n super.show(index);\n this._slices.forEach((slice, i) => slice?.classList.toggle('active', i === index));\n // LineChart hides the tooltip for -1 and for an index past the last row.\n if (this._tooltip.hidden) return;\n\n const color = this._colors[index];\n this._tooltip.querySelectorAll<HTMLElement>('[data-series]').forEach((span) => {\n if (color) span.dataset.series = color;\n else delete span.dataset.series;\n });\n const share = this._share(index);\n const value = this._tooltip.querySelector('span:nth-of-type(2)');\n if (share && value) value.textContent += ` (${share})`;\n }\n\n /** CSS places the tooltip on the slice's middle from --turn. */\n protected _place(index: number) {\n const middle = (this._turns[index][0] + this._turns[index][1]) / 2;\n this._tooltip.style.setProperty('--turn', String(middle));\n // On the right half it opens leftwards, over the pie.\n this._tooltip.classList.toggle('end', middle < 0.5);\n }\n\n /** The row's share of the whole as a percentage, or '' for none. */\n private _share(index: number) {\n const [start, end] = this._turns[index];\n if (end <= start) return '';\n const share = end - start;\n // Whole percentages, with a tenth below 10% so a small part is not 0%.\n const options = { style: 'percent', maximumFractionDigits: share < 0.1 ? 1 : 0 } as const;\n try {\n return share.toLocaleString(this.el.closest<HTMLElement>('[lang]')?.lang || undefined, options);\n } catch {\n // An invalid lang attribute.\n return share.toLocaleString(undefined, options);\n }\n }\n\n /** The legend: every row with its color, its cell and its share. */\n protected _guides(): Element[] {\n const legend = document.createElement('ul');\n legend.className = 'pie-chart-legend';\n legend.setAttribute('aria-hidden', 'true');\n const text = this.series[0].text;\n legend.append(...this.labels.map((label, i) => {\n const li = document.createElement('li');\n if (this._colors[i]) li.dataset.series = this._colors[i];\n const [name, value, share] = [label, text[i], this._share(i)].map((content) => {\n const span = document.createElement('span');\n span.textContent = content;\n return span;\n });\n li.append(name, value, share);\n return li;\n }));\n return [legend];\n }\n\n /** The row under the pointer by its angle from the middle, or -1 off the slices. */\n protected _indexAt(e: PointerEvent) {\n const [turn, share] = this._polarAt(e);\n // The distance in the SVG's 100-unit box.\n const distance = share * 100;\n const hole = this.el.classList.contains('donut')\n ? parseFloat(getComputedStyle(this.el).getPropertyValue('--md-comp-pie-chart-hole')) || 0\n : 0;\n if (distance < RADIUS * hole) return -1;\n const index = this._turns.findIndex(([start, end]) => start <= turn && turn < end);\n // The active slice is drawn larger, so the pointer stays on it to its edge.\n return distance > RADIUS * (index === this.activeIndex ? GROWN : 1) ? -1 : index;\n }\n\n /**\n * An SVG of slices in a -50 to 50 box, under a mask that cuts the gaps\n * between them and the donut's hole, which CSS sizes. The values and the\n * total are HTML over it.\n */\n protected _draw(): Element {\n const values = this.series[0].values.map((value) => Math.max(0, value ?? 0));\n const total = values.reduce((sum, value) => sum + value, 0);\n let turn = 0;\n this._turns = values.map((value) => [turn, (turn += total ? value / total : 0)]);\n // Colors go round the drawn slices; the last skips the first's color beside it.\n const drawn = this._turns.filter(([start, end]) => end > start).length;\n let slice = 0;\n this._colors = this._turns.map(([start, end]) => {\n if (end <= start) return undefined;\n const index = slice++;\n return String(index === drawn - 1 && index > 0 && index % COLORS === 0 ? 2 : (index % COLORS) + 1);\n });\n const at = (t: number, r = RADIUS) => {\n const angle = t * 2 * Math.PI;\n return `${(r * Math.sin(angle)).toFixed(3)},${(-r * Math.cos(angle)).toFixed(3)}`;\n };\n\n const pie = document.createElement('div');\n pie.className = 'pie-chart-pie';\n pie.setAttribute('aria-hidden', 'true');\n const svg = node('svg', { viewBox: '-50 -50 100 100', focusable: 'false' });\n const id = `pie-chart-mask-${++_maskId}`;\n const area = { x: '-50', y: '-50', width: '100', height: '100' };\n const mask = node('mask', { id, maskUnits: 'userSpaceOnUse', ...area });\n const edges = this._turns.filter(([start, end]) => end > start);\n mask.append(\n node('rect', { ...area, fill: 'white' }),\n node('path', {\n class: 'pie-chart-gaps',\n stroke: 'black',\n d: edges.length > 1 ? edges.map(([start]) => `M0,0L${at(start, 50)}`).join('') : ''\n }),\n node('circle', { class: 'pie-chart-hole', r: String(RADIUS), fill: 'black' })\n );\n\n const slices = node('g', { mask: `url(#${id})` });\n const arc = `A${RADIUS},${RADIUS} 0`;\n this._slices = this._turns.map(([start, end], i) => {\n if (end <= start) return null;\n const slice = node('path', {\n class: 'pie-chart-slice',\n 'data-series': this._colors[i],\n // A whole circle is two half arcs; one arc cannot end where it starts.\n d: end - start >= 1\n ? `M0,${-RADIUS}${arc} 1 1 0,${RADIUS}${arc} 1 1 0,${-RADIUS}Z`\n : `M0,0L${at(start)}${arc} ${end - start > 0.5 ? 1 : 0} 1 ${at(end)}Z`\n }) as SVGPathElement;\n slices.append(slice);\n return slice;\n });\n svg.append(mask, slices);\n pie.append(svg);\n\n if (this.el.classList.contains('values')) {\n const labels = document.createElement('div');\n labels.className = 'pie-chart-values';\n this._turns.forEach(([start, end], i) => {\n if (end - start < LABEL_SHARE) return;\n const label = document.createElement('span');\n label.dataset.series = this._colors[i];\n label.style.setProperty('--turn', String((start + end) / 2));\n label.textContent = this._share(i);\n labels.append(label);\n });\n pie.append(labels);\n }\n\n const middle = this._total('pie-chart-total');\n if (middle) pie.append(middle);\n return pie;\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["slice"]
7
+ }
@@ -1,9 +1,9 @@
1
+ import {
2
+ FormSelect
3
+ } from "./chunk-55IA2XUC.mjs";
1
4
  import {
2
5
  DockedDisplayPlugin
3
6
  } from "./chunk-EC63CHXU.mjs";
4
- import {
5
- FormSelect
6
- } from "./chunk-HB6IW7QQ.mjs";
7
7
  import {
8
8
  Utils
9
9
  } from "./chunk-6OMRGML6.mjs";
@@ -1115,4 +1115,4 @@ var Datepicker = _Datepicker;
1115
1115
  export {
1116
1116
  Datepicker
1117
1117
  };
1118
- //# sourceMappingURL=chunk-H2WA4XHH.mjs.map
1118
+ //# sourceMappingURL=chunk-PLYXLSH3.mjs.map
@@ -0,0 +1,179 @@
1
+ import {
2
+ Utils
3
+ } from "./chunk-6OMRGML6.mjs";
4
+
5
+ // src/ts/behaviors/forms.ts
6
+ var Forms = class _Forms {
7
+ /**
8
+ * Checks if the label has validation and apply
9
+ * the correct class and styles
10
+ * @param textfield
11
+ */
12
+ static validateField(textfield) {
13
+ if (!textfield) {
14
+ console.error("No text field element found");
15
+ return;
16
+ }
17
+ const hasLength = textfield.getAttribute("data-length") !== null;
18
+ const lenAttr = parseInt(textfield.getAttribute("data-length"));
19
+ const len = textfield.value.length;
20
+ if (len === 0 && textfield.validity.badInput === false && !textfield.required && textfield.classList.contains("validate")) {
21
+ textfield.classList.remove("invalid");
22
+ } else if (textfield.classList.contains("validate")) {
23
+ if (textfield.validity.valid && hasLength && len <= lenAttr || textfield.validity.valid && !hasLength) {
24
+ textfield.classList.remove("invalid");
25
+ } else {
26
+ textfield.classList.add("invalid");
27
+ }
28
+ }
29
+ }
30
+ /**
31
+ * Resizes the given TextArea after updating the
32
+ * value content dynamically.
33
+ * @param e EventTarget
34
+ */
35
+ static textareaAutoResize(e) {
36
+ const textarea = e;
37
+ if (!textarea) return;
38
+ if (!textarea.hasAttribute("original-height")) {
39
+ textarea.setAttribute("original-height", textarea.getBoundingClientRect().height.toString());
40
+ }
41
+ const originalHeight = parseInt(textarea.getAttribute("original-height"));
42
+ if (isNaN(originalHeight)) return;
43
+ textarea.style.height = "auto";
44
+ const borders = textarea.offsetHeight - textarea.clientHeight;
45
+ const contentHeight = textarea.scrollHeight + borders;
46
+ textarea.style.height = Math.max(originalHeight, contentHeight) + "px";
47
+ }
48
+ static Init() {
49
+ Utils.onDocumentReady(() => {
50
+ document.addEventListener("change", (e) => {
51
+ const target = e.target;
52
+ if (target instanceof HTMLInputElement && target.type === "file") {
53
+ const zone = target.closest(".drop-zone");
54
+ if (zone) _Forms.listDropZoneFiles(zone, target);
55
+ }
56
+ if (target instanceof HTMLInputElement) {
57
+ _Forms.validateField(target);
58
+ }
59
+ });
60
+ document.addEventListener("keyup", (e) => {
61
+ const target = e.target;
62
+ if (target instanceof HTMLInputElement && ["radio", "checkbox"].includes(target.type)) {
63
+ if (e.key === Utils.keys.TAB) {
64
+ target.classList.add("tabbed");
65
+ target.addEventListener("blur", () => target.classList.remove("tabbed"), {
66
+ once: true
67
+ });
68
+ }
69
+ }
70
+ });
71
+ document.addEventListener("click", _Forms.togglePassword);
72
+ document.addEventListener("submit", _Forms.resetFormExtras, true);
73
+ document.addEventListener("reset", _Forms.resetFormExtras, true);
74
+ for (const type of ["dragenter", "dragover", "dragleave", "drop"]) {
75
+ document.addEventListener(type, _Forms.markDropZone);
76
+ }
77
+ document.querySelectorAll(".expressive-textarea").forEach((textArea) => {
78
+ _Forms.InitTextarea(textArea);
79
+ });
80
+ document.querySelectorAll('.file-field input[type="file"]').forEach((fileInput) => {
81
+ _Forms.InitFileInputPath(fileInput);
82
+ });
83
+ });
84
+ }
85
+ /**
86
+ * A .password-toggle button shows or hides the password in its field. The
87
+ * button's own name stays put ("Show password"); aria-pressed carries the
88
+ * state, so there is no generated text to translate.
89
+ */
90
+ static togglePassword(e) {
91
+ const button = e.target.closest?.(".password-toggle");
92
+ const input = button?.closest(".field")?.querySelector(
93
+ 'input[type="password"], input[data-password]'
94
+ );
95
+ if (!input) return;
96
+ const show = input.type === "password";
97
+ input.toggleAttribute("data-password", show);
98
+ input.type = show ? "text" : "password";
99
+ button.setAttribute("aria-pressed", String(show));
100
+ }
101
+ /**
102
+ * A revealed password goes back to type="password" when its form is
103
+ * submitted or reset, so it is never sent or autofilled as plain text and
104
+ * a reset field does not stay revealed. A reset form also drops the file
105
+ * lists it would otherwise leave showing: reset fires no change event.
106
+ */
107
+ static resetFormExtras(e) {
108
+ const form = e.target;
109
+ if (!(form instanceof HTMLFormElement)) return;
110
+ form.querySelectorAll("input[data-password]").forEach((input) => {
111
+ input.removeAttribute("data-password");
112
+ input.type = "password";
113
+ input.closest(".field")?.querySelector(".password-toggle")?.setAttribute("aria-pressed", "false");
114
+ });
115
+ if (e.type === "reset") {
116
+ form.querySelectorAll(".drop-zone-files").forEach((list) => list.replaceChildren());
117
+ form.querySelectorAll(".drop-zone.dragover").forEach((zone) => zone.classList.remove("dragover"));
118
+ }
119
+ }
120
+ /**
121
+ * The native file input covers its .drop-zone, so the browser already takes
122
+ * a dropped file. This only shows the drag: the input is the topmost
123
+ * element, so every drag event over the zone targets it and leaving it is
124
+ * leaving the zone.
125
+ */
126
+ static markDropZone(e) {
127
+ const input = e.target;
128
+ if (!(input instanceof HTMLInputElement) || input.type !== "file") return;
129
+ const zone = input.closest(".drop-zone");
130
+ if (!zone) return;
131
+ const over = e.type === "dragenter" || e.type === "dragover";
132
+ zone.classList.toggle("dragover", over && !input.disabled);
133
+ }
134
+ /** Lists the chosen files, name and size, in the zone's .drop-zone-files. */
135
+ static listDropZoneFiles(zone, input) {
136
+ const list = zone.querySelector(".drop-zone-files");
137
+ if (!list) return;
138
+ const size = (bytes) => {
139
+ const [value, unit] = bytes >= 1e6 ? [bytes / 1e6, "megabyte"] : bytes >= 1e3 ? [bytes / 1e3, "kilobyte"] : [bytes, "byte"];
140
+ return new Intl.NumberFormat(void 0, { style: "unit", unit, maximumFractionDigits: 1 }).format(value);
141
+ };
142
+ list.replaceChildren(
143
+ ...Array.from(input.files ?? [], (file) => {
144
+ const item = document.createElement("li");
145
+ const name = document.createElement("span");
146
+ const meta = document.createElement("span");
147
+ name.textContent = file.name;
148
+ meta.textContent = size(file.size);
149
+ item.append(name, meta);
150
+ return item;
151
+ })
152
+ );
153
+ }
154
+ static InitTextarea(textarea) {
155
+ textarea.setAttribute("original-height", textarea.getBoundingClientRect().height.toString());
156
+ _Forms.textareaAutoResize(textarea);
157
+ textarea.addEventListener("input", (e) => _Forms.textareaAutoResize(e.target));
158
+ }
159
+ static InitFileInputPath(fileInput) {
160
+ fileInput.addEventListener("change", () => {
161
+ const fileField = fileInput.closest(".file-field");
162
+ const pathInput = fileField.querySelector("input.file-path");
163
+ const files = fileInput.files;
164
+ const filenames = [];
165
+ for (let i = 0; i < files.length; i++) {
166
+ filenames.push(files[i].name);
167
+ }
168
+ pathInput.value = filenames.join(", ");
169
+ pathInput.dispatchEvent(
170
+ new Event("change", { bubbles: true, cancelable: true, composed: true })
171
+ );
172
+ });
173
+ }
174
+ };
175
+
176
+ export {
177
+ Forms
178
+ };
179
+ //# sourceMappingURL=chunk-QKWPQK5N.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/ts/behaviors/forms.ts"],
4
+ "sourcesContent": ["import { Utils } from '../core/utils';\n\nexport class Forms {\n /**\n * Checks if the label has validation and apply\n * the correct class and styles\n * @param textfield\n */\n static validateField(textfield: HTMLInputElement) {\n if (!textfield) {\n console.error('No text field element found');\n return;\n }\n\n const hasLength = textfield.getAttribute('data-length') !== null;\n const lenAttr = parseInt(textfield.getAttribute('data-length'));\n const len = textfield.value.length;\n\n if (\n len === 0 &&\n textfield.validity.badInput === false &&\n !textfield.required &&\n textfield.classList.contains('validate')\n ) {\n textfield.classList.remove('invalid');\n } else if (textfield.classList.contains('validate')) {\n // Check for character counter attributes\n if (\n (textfield.validity.valid && hasLength && len <= lenAttr) ||\n (textfield.validity.valid && !hasLength)\n ) {\n textfield.classList.remove('invalid');\n } else {\n textfield.classList.add('invalid');\n }\n }\n }\n\n /**\n * Resizes the given TextArea after updating the\n * value content dynamically.\n * @param e EventTarget\n */\n static textareaAutoResize(e: EventTarget) {\n const textarea = e as HTMLTextAreaElement;\n if (!textarea) return;\n\n // Measure the textarea against itself: collapse it to its natural height,\n // then read the content height back. `.expressive-textarea` is\n // `overflow-y: hidden`, so scrollHeight is the full content height and\n // offsetHeight - clientHeight is exactly the borders (no scrollbar to\n // account for) - which border-box sizing needs added back.\n //\n // This replaces a mirror div that the value was copied into via\n // `innerHTML = innerHTML.replace(...)` - a full serialize and reparse of\n // the text on every keystroke, plus getComputedStyle and seven style\n // writes, run twice per keystroke because it was bound to keydown and\n // keyup both.\n if (!textarea.hasAttribute('original-height')) {\n textarea.setAttribute('original-height', textarea.getBoundingClientRect().height.toString());\n }\n const originalHeight = parseInt(textarea.getAttribute('original-height'));\n if (isNaN(originalHeight)) return;\n\n textarea.style.height = 'auto';\n // Three reads after a single write: one layout flush for the whole resize.\n const borders = textarea.offsetHeight - textarea.clientHeight;\n const contentHeight = textarea.scrollHeight + borders;\n textarea.style.height = Math.max(originalHeight, contentHeight) + 'px';\n }\n\n static Init() {\n Utils.onDocumentReady(() => {\n document.addEventListener('change', (e: KeyboardEvent) => {\n const target = <HTMLInputElement>e.target;\n if (target instanceof HTMLInputElement && target.type === 'file') {\n const zone = target.closest('.drop-zone');\n if (zone) Forms.listDropZoneFiles(zone, target);\n }\n if (target instanceof HTMLInputElement) {\n // The sibling-label loop that used to live here compared\n // `tagName == 'label'` (tagName is uppercase) so it never matched,\n // and no rule in the Sass styles `label.active` anyway - the\n // floating label is driven by CSS, not by a class from here.\n Forms.validateField(target);\n }\n });\n\n document.addEventListener('keyup', (e: KeyboardEvent) => {\n const target = <HTMLInputElement>e.target;\n // Radio and Checkbox focus class\n if (target instanceof HTMLInputElement && ['radio', 'checkbox'].includes(target.type)) {\n // TAB, check if tabbing to radio or checkbox.\n if (e.key === Utils.keys.TAB) {\n target.classList.add('tabbed');\n target.addEventListener('blur', () => target.classList.remove('tabbed'), {\n once: true\n });\n }\n }\n });\n\n document.addEventListener('click', Forms.togglePassword);\n // Capture, so the password is hidden again before any submit handler\n // reads the form or the browser offers to save it.\n document.addEventListener('submit', Forms.resetFormExtras, true);\n document.addEventListener('reset', Forms.resetFormExtras, true);\n for (const type of ['dragenter', 'dragover', 'dragleave', 'drop']) {\n document.addEventListener(type, Forms.markDropZone);\n }\n\n document.querySelectorAll('.expressive-textarea').forEach((textArea: HTMLTextAreaElement) => {\n Forms.InitTextarea(textArea);\n });\n\n // File Input Path\n document\n .querySelectorAll('.file-field input[type=\"file\"]')\n .forEach((fileInput: HTMLInputElement) => {\n Forms.InitFileInputPath(fileInput);\n });\n });\n }\n\n /**\n * A .password-toggle button shows or hides the password in its field. The\n * button's own name stays put (\"Show password\"); aria-pressed carries the\n * state, so there is no generated text to translate.\n */\n static togglePassword(e: MouseEvent) {\n const button = (<Element>e.target).closest?.('.password-toggle');\n const input = button?.closest('.field')?.querySelector<HTMLInputElement>(\n 'input[type=\"password\"], input[data-password]'\n );\n if (!input) return;\n const show = input.type === 'password';\n // Marked so the input is still found once it has become type=\"text\".\n input.toggleAttribute('data-password', show);\n input.type = show ? 'text' : 'password';\n button.setAttribute('aria-pressed', String(show));\n }\n\n /**\n * A revealed password goes back to type=\"password\" when its form is\n * submitted or reset, so it is never sent or autofilled as plain text and\n * a reset field does not stay revealed. A reset form also drops the file\n * lists it would otherwise leave showing: reset fires no change event.\n */\n static resetFormExtras(e: Event) {\n const form = e.target;\n if (!(form instanceof HTMLFormElement)) return;\n form.querySelectorAll<HTMLInputElement>('input[data-password]').forEach((input) => {\n input.removeAttribute('data-password');\n input.type = 'password';\n input.closest('.field')?.querySelector('.password-toggle')?.setAttribute('aria-pressed', 'false');\n });\n if (e.type === 'reset') {\n form.querySelectorAll('.drop-zone-files').forEach((list) => list.replaceChildren());\n form.querySelectorAll('.drop-zone.dragover').forEach((zone) => zone.classList.remove('dragover'));\n }\n }\n\n /**\n * The native file input covers its .drop-zone, so the browser already takes\n * a dropped file. This only shows the drag: the input is the topmost\n * element, so every drag event over the zone targets it and leaving it is\n * leaving the zone.\n */\n static markDropZone(e: DragEvent) {\n const input = <Element>e.target;\n if (!(input instanceof HTMLInputElement) || input.type !== 'file') return;\n const zone = input.closest('.drop-zone');\n if (!zone) return;\n const over = e.type === 'dragenter' || e.type === 'dragover';\n zone.classList.toggle('dragover', over && !input.disabled);\n }\n\n /** Lists the chosen files, name and size, in the zone's .drop-zone-files. */\n static listDropZoneFiles(zone: Element, input: HTMLInputElement) {\n const list = zone.querySelector('.drop-zone-files');\n if (!list) return;\n const size = (bytes: number) => {\n const [value, unit] =\n bytes >= 1e6 ? [bytes / 1e6, 'megabyte'] : bytes >= 1e3 ? [bytes / 1e3, 'kilobyte'] : [bytes, 'byte'];\n return new Intl.NumberFormat(undefined, { style: 'unit', unit, maximumFractionDigits: 1 }).format(value);\n };\n list.replaceChildren(\n ...Array.from(input.files ?? [], (file) => {\n const item = document.createElement('li');\n const name = document.createElement('span');\n const meta = document.createElement('span');\n name.textContent = file.name;\n meta.textContent = size(file.size);\n item.append(name, meta);\n return item;\n })\n );\n }\n\n static InitTextarea(textarea: HTMLTextAreaElement) {\n // Save Data in Element\n textarea.setAttribute('original-height', textarea.getBoundingClientRect().height.toString());\n Forms.textareaAutoResize(textarea);\n // `input` rather than keydown + keyup: it fires once per actual value\n // change and also covers paste, drag-drop and IME composition, which the\n // key events missed.\n textarea.addEventListener('input', (e) => Forms.textareaAutoResize(e.target));\n }\n\n static InitFileInputPath(fileInput: HTMLInputElement) {\n fileInput.addEventListener('change', () => {\n const fileField = fileInput.closest('.file-field');\n const pathInput = <HTMLInputElement>fileField.querySelector('input.file-path');\n const files = fileInput.files;\n const filenames = [];\n for (let i = 0; i < files.length; i++) {\n filenames.push(files[i].name);\n }\n pathInput.value = filenames.join(', ');\n pathInput.dispatchEvent(\n new Event('change', { bubbles: true, cancelable: true, composed: true })\n );\n });\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }