@fusion.dev/helix-headless 0.217.0 → 0.219.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 (74) hide show
  1. package/api.json +1 -1
  2. package/custom-elements.json +1 -1
  3. package/dist/breadcrumbs/breadcrumb/breadcrumb.d.ts +13 -2
  4. package/dist/breadcrumbs/breadcrumb/breadcrumb.js +19 -9
  5. package/dist/breadcrumbs/root/breadcrumbs-root.d.ts +16 -1
  6. package/dist/breadcrumbs/root/breadcrumbs-root.js +6 -0
  7. package/dist/button/root/button-root.d.ts +33 -0
  8. package/dist/button/root/button-root.js +13 -3
  9. package/dist/canvas/root/canvas-root.d.ts +11 -0
  10. package/dist/canvas/root/canvas-root.js +1 -0
  11. package/dist/chat/composer/chat-composer.d.ts +221 -6
  12. package/dist/chat/composer/chat-composer.js +249 -19
  13. package/dist/chat/message/chat-message.d.ts +7 -0
  14. package/dist/chat/message/chat-message.js +2 -1
  15. package/dist/chat/root/chat-root.d.ts +1 -0
  16. package/dist/chat/root/chat-root.js +1 -1
  17. package/dist/copy-button/root/copy-button-root.js +3 -3
  18. package/dist/device-screen/root/device-screen-root.d.ts +11 -0
  19. package/dist/device-screen/root/device-screen-root.js +22 -10
  20. package/dist/dialog/root/dialog-root.js +6 -1
  21. package/dist/drilldown/item/drilldown-item.js +1 -1
  22. package/dist/element-events.generated.js +1 -0
  23. package/dist/filter-input/root/filter-input-root.d.ts +4 -3
  24. package/dist/filter-input/root/filter-input-root.js +1 -20
  25. package/dist/graph/root/graph-root.d.ts +11 -0
  26. package/dist/graph/root/graph-root.js +4 -1
  27. package/dist/input/root/input-root.d.ts +6 -0
  28. package/dist/input/root/input-root.js +7 -0
  29. package/dist/lightbox/root/lightbox-root.d.ts +4 -1
  30. package/dist/lightbox/root/lightbox-root.js +10 -8
  31. package/dist/listbox/root/listbox-root.d.ts +21 -0
  32. package/dist/listbox/root/listbox-root.js +37 -2
  33. package/dist/log/group/log-group.d.ts +7 -1
  34. package/dist/log/group/log-group.js +14 -1
  35. package/dist/log/root/log-root.d.ts +13 -3
  36. package/dist/log/root/log-root.js +5 -2
  37. package/dist/map/root/map-root.d.ts +11 -0
  38. package/dist/map/root/map-root.js +28 -12
  39. package/dist/menu/root/menu-root.d.ts +8 -0
  40. package/dist/menu/root/menu-root.js +22 -1
  41. package/dist/menu/section/menu-section.d.ts +10 -0
  42. package/dist/menu/section/menu-section.js +10 -0
  43. package/dist/meter/index.d.ts +1 -0
  44. package/dist/meter/index.js +1 -0
  45. package/dist/meter/root/meter-root.d.ts +16 -1
  46. package/dist/meter/root/meter-root.js +27 -11
  47. package/dist/meter/segment/meter-segment.d.ts +74 -0
  48. package/dist/meter/segment/meter-segment.js +47 -0
  49. package/dist/scroll-area/root/scroll-area-root.d.ts +20 -3
  50. package/dist/scroll-area/root/scroll-area-root.js +23 -4
  51. package/dist/spatial-bar-chart/bars.d.ts +125 -5
  52. package/dist/spatial-bar-chart/bars.js +194 -17
  53. package/dist/spatial-bar-chart/root/spatial-bar-chart-root.d.ts +148 -6
  54. package/dist/spatial-bar-chart/root/spatial-bar-chart-root.js +285 -16
  55. package/dist/splitter/root/splitter-root.js +1 -0
  56. package/dist/tabs/root/tabs-root.d.ts +4 -0
  57. package/dist/tabs/tab/tab.d.ts +1 -0
  58. package/dist/tabs/tab/tab.js +2 -2
  59. package/dist/tag/root/tag-root.d.ts +1 -1
  60. package/dist/terminal/root/terminal-root.d.ts +11 -0
  61. package/dist/terminal/root/terminal-root.js +23 -10
  62. package/dist/toolbar/root/toolbar-root.d.ts +4 -1
  63. package/dist/tooltip/root/tooltip-root.d.ts +23 -1
  64. package/dist/tooltip/root/tooltip-root.js +28 -5
  65. package/dist/tour/root/tour-root.d.ts +1 -0
  66. package/dist/tour/root/tour-root.js +4 -1
  67. package/dist/tour/stop/tour-stop.js +1 -0
  68. package/examples.json +1 -1
  69. package/lint.json +1 -1
  70. package/package.json +5 -5
  71. package/types/jsx.d.ts +1373 -52
  72. package/types/svelte.d.ts +1 -0
  73. package/types/vue.d.ts +312 -17
  74. package/types/words.d.ts +9 -1
@@ -17,6 +17,7 @@ let HelixHeadlessScrollArea = class HelixHeadlessScrollArea extends HelixElement
17
17
  });
18
18
  this.more = [];
19
19
  this.scrolls = false;
20
+ this.gutter = { blockEnd: 0, inlineEnd: 0 };
20
21
  this.measure = () => {
21
22
  const box = this.scroller;
22
23
  if (!box) {
@@ -43,6 +44,11 @@ let HelixHeadlessScrollArea = class HelixHeadlessScrollArea extends HelixElement
43
44
  this.scrolls =
44
45
  (block && box.scrollHeight > box.clientHeight) ||
45
46
  (inline && box.scrollWidth > box.clientWidth);
47
+ const blockEnd = Math.max(0, box.offsetHeight - box.clientHeight);
48
+ const inlineEnd = Math.max(0, box.offsetWidth - box.clientWidth);
49
+ if (blockEnd !== this.gutter.blockEnd || inlineEnd !== this.gutter.inlineEnd) {
50
+ this.gutter = { blockEnd, inlineEnd };
51
+ }
46
52
  if (this.scrollbars) {
47
53
  this.bars.read(box);
48
54
  }
@@ -118,17 +124,27 @@ let HelixHeadlessScrollArea = class HelixHeadlessScrollArea extends HelixElement
118
124
  this.toggleState('scrollbar-horizontal', drawn?.horizontal.drawn === true);
119
125
  this.toggleState('scrollbar-vertical', drawn?.vertical.drawn === true);
120
126
  }
121
- get clipped() {
127
+ get viewportStyle() {
128
+ const rules = [];
122
129
  if (this.orientation === 'vertical') {
123
- return 'overflow-x: hidden';
130
+ rules.push('overflow-x: hidden');
131
+ }
132
+ else if (this.orientation === 'horizontal') {
133
+ rules.push('overflow-y: hidden');
134
+ }
135
+ if (this.gutter.blockEnd > 0) {
136
+ rules.push(`--scroll-area-gutter-block-end: ${this.gutter.blockEnd}px`);
124
137
  }
125
- return this.orientation === 'horizontal' ? 'overflow-y: hidden' : nothing;
138
+ if (this.gutter.inlineEnd > 0) {
139
+ rules.push(`--scroll-area-gutter-inline-end: ${this.gutter.inlineEnd}px`);
140
+ }
141
+ return rules.length > 0 ? rules.join('; ') : nothing;
126
142
  }
127
143
  render() {
128
144
  return html `<div
129
145
  data-scroller
130
146
  part="viewport"
131
- style=${this.clipped}
147
+ style=${this.viewportStyle}
132
148
  role=${this.label ? 'region' : nothing}
133
149
  aria-label=${this.label || nothing}
134
150
  tabindex=${this.scrolls ? '0' : nothing}
@@ -211,6 +227,9 @@ __decorate([
211
227
  __decorate([
212
228
  state()
213
229
  ], HelixHeadlessScrollArea.prototype, "scrolls", void 0);
230
+ __decorate([
231
+ state()
232
+ ], HelixHeadlessScrollArea.prototype, "gutter", void 0);
214
233
  HelixHeadlessScrollArea = __decorate([
215
234
  customElement('helix-headless-scroll-area')
216
235
  ], HelixHeadlessScrollArea);
@@ -15,12 +15,21 @@ export interface BarCell {
15
15
  across: number;
16
16
  /** The column of the row the value was read from, which names its series. */
17
17
  column: number;
18
- /** Which series it stands in, counting from zero. */
18
+ /** Which series it stands in, counting from zero, which colors it. */
19
19
  depth: number;
20
+ /**
21
+ * Where its block starts on the value axis: zero, or under `stacked` the
22
+ * readings of the series before it in its category, on its side of zero.
23
+ */
24
+ from: number;
20
25
  /** The row's own label, which names its category. */
21
26
  label: string;
27
+ /** Which row of the grid in depth it stands in: its series, or the one row of a stacked chart. */
28
+ lane: number;
22
29
  /** The row it was read from, counting from zero. */
23
30
  row: number;
31
+ /** The tone the cell names with `data-tone`, which colors its own bar, where it names one. */
32
+ tone?: string | undefined;
24
33
  /** The value, where the cell holds a number. */
25
34
  value?: number | undefined;
26
35
  }
@@ -44,10 +53,20 @@ export interface BarField {
44
53
  * series `depth` is at `across * series.length + depth`.
45
54
  */
46
55
  cells: BarCell[];
56
+ /** How many rows of bars stand in depth: one a series, or one in all when stacked. */
57
+ lanes: number;
47
58
  /** How many cells hold a value. */
48
59
  readings: number;
60
+ /**
61
+ * How far a `diagonal` row climbs on the page for each step across, where
62
+ * it climbs less than Switchyard's 15 for every 72: a long row in a short
63
+ * plot, whose climb would leave its bars no height.
64
+ */
65
+ climb?: number;
49
66
  /** Every series, in the table's order: each numeric column's heading. */
50
67
  series: string[];
68
+ /** Whether a category's series stand in one column, each on the ones before it. */
69
+ stacked: boolean;
51
70
  }
52
71
  /**
53
72
  * Reads the table as a bar chart does: **each row is a category, named by
@@ -59,10 +78,46 @@ export interface BarField {
59
78
  * **The value axis runs from zero**, whatever the values, so a bar's height
60
79
  * is its value: one twice as tall is twice as much. It widens to round
61
80
  * numbers past the highest value, and below zero where a value is negative.
81
+ *
82
+ * **`stacked` stands a category's series in one column**, each block on the
83
+ * ones before it, so the column is as tall as the category's total and the
84
+ * axis runs to the tallest total. A negative reading hangs below zero on the
85
+ * negatives before it, as a flat chart's stack does.
86
+ *
87
+ * `toneOf` gives the tone a cell names, by its row and its series.
88
+ */
89
+ export declare const barsOf: (rows: readonly FieldRow[], columns: readonly number[], stacked?: boolean, toneOf?: (row: number, depth: number) => string | undefined) => BarField;
90
+ /**
91
+ * How the bars stand: on the box's grid, in a row level across the page, or
92
+ * in a row that climbs to the right as it recedes.
62
93
  */
63
- export declare const barsOf: (rows: readonly FieldRow[], columns: readonly number[]) => BarField;
94
+ export type BarLayout = 'diagonal' | 'grid' | 'row';
95
+ /**
96
+ * How many categories a row has room for across `width` pixels with each bar
97
+ * `size` pixels wide: a bar covers `FOOTPRINT` of its place, so a place is
98
+ * that much wider than its bar. At least one.
99
+ */
100
+ export declare const rowFits: (width: number, size: number) => number;
101
+ /** How wide a bar is on the page, in the drawing's units. */
102
+ export declare const widthOf: (bar: DrawnBar) => number;
103
+ /**
104
+ * The field with its `diagonal` row climbing at most `rise` on the page over
105
+ * `length` across, so a long row keeps the height its bars need.
106
+ */
107
+ export declare const climbing: (field: BarField, rise: number, length: number) => BarField;
108
+ /**
109
+ * The most times taller a row's value axis may stand than it is drawn in
110
+ * `bars`, so the tallest bar is no taller than `ROW_TALL` times the row's
111
+ * length: each of its places is a bar's width over `FOOTPRINT`, the width
112
+ * `widthOf` gives on the page.
113
+ */
114
+ export declare const tallestTimes: (field: BarField, projection: Projection, bars: readonly DrawnBar[]) => number;
115
+ /** The field with its value axis `times` as tall, which stands every bar that much taller. */
116
+ export declare const heightened: (field: BarField, times: number) => BarField;
64
117
  /** A bar as the chart's own drawing paints it. */
65
118
  export interface DrawnBar {
119
+ /** Where the block starts on the floor's way up, in the box's units. */
120
+ base: number;
66
121
  /** The block's eight corners on the page, which a drawing with no box is framed around. */
67
122
  corners: PagePoint[];
68
123
  drawn: Drawn;
@@ -73,7 +128,72 @@ export interface DrawnBar {
73
128
  }
74
129
  /**
75
130
  * Each bar with a value as a block, seen through `projection`: standing on
76
- * its place on the grid, from zero to its value, a little narrower than its
77
- * place so neighbors stand apart. A value below zero hangs from zero.
131
+ * its place on the grid, from where it starts to its value, a little
132
+ * narrower than its place so neighbors stand apart. A value below zero
133
+ * hangs from where it starts.
134
+ *
135
+ * **`row` lays the categories in a straight line across the page**, along
136
+ * the way the camera reads as level, and the series straight back from it,
137
+ * so the row stays level whichever way the camera turns, and each block,
138
+ * which keeps to the floor's grid, shows the turn: its top and two sides.
139
+ * Turned, a block is wider on the page than its footprint, so the footprint
140
+ * narrows by as much, and neighbors still stand apart. **`diagonal` lays the
141
+ * same row climbing to the right**, each place a step further back than the
142
+ * one before, at the same climb on the page from any turn.
143
+ *
144
+ * `grown` says how much of its height each bar has reached, from 0 to 1, as
145
+ * the chart grows its bars in.
146
+ */
147
+ export declare const barsDrawn: (field: BarField, projection: Projection, layout?: BarLayout, grown?: (cell: BarCell) => number) => DrawnBar[];
148
+ /**
149
+ * The bars in the order they are painted, back to front, and **a stacked
150
+ * column from the floor up**: its blocks share a place on the floor, which
151
+ * the order by depth cannot tell apart, and one painted under the block below
152
+ * it would show the lower block's top through the upper one.
153
+ */
154
+ export declare const paintOrder: (projection: Projection, bars: readonly DrawnBar[]) => DrawnBar[];
155
+ /** One place on the floor of a row, under a bar or where a bar would stand. */
156
+ export interface FloorPlate {
157
+ /** Which category it stands under, counting from zero. */
158
+ across: number;
159
+ /** Its four corners on the page. */
160
+ corners: PagePoint[];
161
+ /** Its outline, as path data. */
162
+ d: string;
163
+ /** Its corner lowest on the page, nearest the reader, which its name is written under. */
164
+ front: PagePoint;
165
+ }
166
+ /**
167
+ * The places on the floor of a row, one for each category in each lane,
168
+ * whether a bar stands there or not: a square a little wider than a bar's
169
+ * footprint, turned as the bars are, where zero stands on the value axis,
170
+ * along the row `layout` lays.
171
+ */
172
+ export declare const floorOf: (field: BarField, projection: Projection, layout?: Exclude<BarLayout, 'grid'>) => FloorPlate[];
173
+ /** A category's name written under its place in a row. */
174
+ export interface RowName {
175
+ across: number;
176
+ text: string;
177
+ /** Whether a bar of its category names a tone, which colors the name. */
178
+ tone?: string | undefined;
179
+ x: number;
180
+ /** The middle of the line of text. */
181
+ y: number;
182
+ }
183
+ /**
184
+ * The names written under a row's plates, `size` tall: each category's name
185
+ * under its plate nearest the reader, **and only the names that fit**, so
186
+ * none overlaps another. A name whose bar names a tone is written first,
187
+ * then every other name, or every second, third, and so on, at the smallest
188
+ * even step that fits, leaving out a name the toned one covers.
189
+ */
190
+ export declare const namesOf: (field: BarField, plates: readonly FloorPlate[], size: number) => RowName[];
191
+ /**
192
+ * How much of its height a bar of category `across` has reached `elapsed`
193
+ * milliseconds after the bars began to grow in, from 0 to 1. **In reading
194
+ * order**, each category a little after the one before it, and fast at first
195
+ * and slow to rest, never past its value.
78
196
  */
79
- export declare const barsDrawn: (field: BarField, projection: Projection) => DrawnBar[];
197
+ export declare const grownAt: (across: number, categories: number, elapsed: number) => number;
198
+ /** How long the bars of `categories` take to grow in, the last one included, in milliseconds. */
199
+ export declare const growTime: (categories: number) => number;
@@ -1,4 +1,4 @@
1
- import { solid, ticks } from '@fusion.dev/helix-core';
1
+ import { depthOrder, pathData, solid, ticks, } from '@fusion.dev/helix-core';
2
2
  import { niceRange } from '../spatial-scatter-chart/field.js';
3
3
  import { inBox } from '../spatial-scatter-chart/view.js';
4
4
  const HEIGHT = 0.6;
@@ -12,50 +12,125 @@ const named = (count, labels, nearest = false) => {
12
12
  ticks: labels.map((_, at) => at),
13
13
  };
14
14
  };
15
- export const barsOf = (rows, columns) => {
15
+ export const barsOf = (rows, columns, stacked = false, toneOf = () => undefined) => {
16
16
  const series = columns.map((column) => rows.find((row) => row.cells[column])?.cells[column]?.name ?? '');
17
17
  const cells = [];
18
18
  const values = [];
19
+ const reached = [];
19
20
  rows.forEach((row, at) => {
21
+ let [above, below] = [0, 0];
20
22
  columns.forEach((column, depth) => {
21
23
  const value = row.cells[column]?.value;
22
24
  const read = Number.isFinite(value) ? value : undefined;
25
+ let from = stacked ? above : 0;
23
26
  if (read !== undefined) {
24
27
  values.push(read);
28
+ if (stacked) {
29
+ from = read < 0 ? below : above;
30
+ if (read < 0) {
31
+ below += read;
32
+ }
33
+ else {
34
+ above += read;
35
+ }
36
+ }
37
+ reached.push(from + read);
25
38
  }
26
- cells.push({ across: at, column, depth, label: row.label, row: at, value: read });
39
+ const lane = stacked ? 0 : depth;
40
+ const tone = toneOf(at, depth);
41
+ cells.push({
42
+ across: at,
43
+ column,
44
+ depth,
45
+ from,
46
+ label: row.label,
47
+ lane,
48
+ row: at,
49
+ ...(tone ? { tone } : {}),
50
+ value: read,
51
+ });
27
52
  });
28
53
  });
29
- const [low, high] = [Math.min(0, ...values), Math.max(0, ...values)];
54
+ const lanes = stacked ? 1 : series.length;
55
+ const [low, high] = [Math.min(0, ...reached), Math.max(0, ...reached)];
30
56
  const range = low === high ? [0, 1] : niceRange(low, high);
31
- const longest = Math.max(rows.length, series.length, 1);
57
+ const longest = Math.max(rows.length, lanes, 1);
32
58
  return {
33
59
  axes: {
34
- extent: [Math.max(rows.length, 1) / longest, HEIGHT, Math.max(series.length, 1) / longest],
60
+ extent: [Math.max(rows.length, 1) / longest, HEIGHT, Math.max(lanes, 1) / longest],
35
61
  x: named(rows.length, rows.map((row) => row.label)),
36
62
  y: { name: series.length === 1 ? (series[0] ?? '') : '', range, ticks: ticks(range) },
37
- z: named(series.length, series.length === 1 ? [] : series, true),
63
+ z: named(lanes, lanes === 1 ? [] : series, true),
38
64
  },
39
65
  categories: rows.map((row) => row.label),
40
66
  cells,
67
+ lanes,
41
68
  readings: values.length,
42
69
  series,
70
+ stacked,
43
71
  };
44
72
  };
45
73
  const FOOTPRINT = 0.6;
46
- export const barsDrawn = (field, projection) => {
74
+ const CLIMB = 15 / 72;
75
+ const placesOf = (field, projection, layout) => {
76
+ const [ex, , ez] = field.axes.extent;
77
+ const [cos, sin] = projection.across;
78
+ const lined = layout !== 'grid';
79
+ const spread = lined ? Math.abs(cos) + Math.abs(sin) : 1;
80
+ const spacing = Math.min((2 * ex) / Math.max(field.categories.length, 1), (2 * ez) / Math.max(field.lanes, 1)) /
81
+ spread;
82
+ const origin = projection.at(0, 0, 0);
83
+ const rise = origin.y - projection.at(-sin, cos, 0).y;
84
+ const across = projection.at(cos, sin, 0).x - origin.x;
85
+ const climb = layout === 'diagonal' && rise > 0 ? ((field.climb ?? CLIMB) * across) / rise : 0;
86
+ return {
87
+ place: (cell) => {
88
+ const along = inBox(field.axes.x, cell.across) * ex;
89
+ const back = -inBox(field.axes.z, cell.lane) * ez + along * climb;
90
+ return lined
91
+ ? { x: along * cos - back * sin, y: along * sin + back * cos }
92
+ : { x: along, y: back };
93
+ },
94
+ spacing,
95
+ };
96
+ };
97
+ export const rowFits = (width, size) => size > 0 ? Math.max(1, Math.floor((width * FOOTPRINT) / size)) : 1;
98
+ export const widthOf = (bar) => {
99
+ const xs = bar.corners.map((corner) => corner.x);
100
+ return Math.max(...xs) - Math.min(...xs);
101
+ };
102
+ export const climbing = (field, rise, length) => ({
103
+ ...field,
104
+ climb: length > 0 ? Math.min(CLIMB, rise / length) : CLIMB,
105
+ });
106
+ const ROW_TALL = 1.25;
107
+ export const tallestTimes = (field, projection, bars) => {
108
+ const first = bars[0];
109
+ const fall = Math.abs(projection.at(0, 0, 1).y - projection.at(0, 0, 0).y);
110
+ const tallest = Math.max(0, ...bars.map((bar) => bar.height)) * fall;
111
+ if (!first || tallest <= 0) {
112
+ return Number.POSITIVE_INFINITY;
113
+ }
114
+ const length = (field.categories.length * widthOf(first)) / FOOTPRINT;
115
+ return (ROW_TALL * length) / tallest;
116
+ };
117
+ export const heightened = (field, times) => {
47
118
  const [ex, ey, ez] = field.axes.extent;
48
- const spacing = Math.min((2 * ex) / Math.max(field.categories.length, 1), (2 * ez) / Math.max(field.series.length, 1));
49
- const zero = inBox(field.axes.y, 0) * ey;
119
+ return { ...field, axes: { ...field.axes, extent: [ex, ey * times, ez] } };
120
+ };
121
+ export const barsDrawn = (field, projection, layout = 'grid', grown = () => 1) => {
122
+ const ey = field.axes.extent[1];
123
+ const { place, spacing } = placesOf(field, projection, layout);
50
124
  return field.cells.flatMap((cell, index) => {
51
- if (cell.value === undefined) {
125
+ if (cell.value === undefined || (field.stacked && cell.value === 0)) {
52
126
  return [];
53
127
  }
54
- const x = inBox(field.axes.x, cell.across) * ex;
55
- const y = -inBox(field.axes.z, cell.depth) * ez;
56
- const top = inBox(field.axes.y, cell.value) * ey;
57
- const base = Math.min(zero, top);
58
- const height = Math.abs(top - zero);
128
+ const { x, y } = place(cell);
129
+ const share = grown(cell);
130
+ const start = inBox(field.axes.y, cell.from * share) * ey;
131
+ const top = inBox(field.axes.y, (cell.from + cell.value) * share) * ey;
132
+ const base = Math.min(start, top);
133
+ const height = Math.abs(top - start);
59
134
  const lifted = {
60
135
  ...projection,
61
136
  at: (px, py, pz = 0) => projection.at(px, py, pz + base),
@@ -67,6 +142,108 @@ export const barsDrawn = (field, projection) => {
67
142
  corners.push(lifted.at(cx, cy, 0), lifted.at(cx, cy, height));
68
143
  }
69
144
  }
70
- return [{ corners, drawn: solid(lifted, 'block', x, y, width, height), height, index, x, y }];
145
+ return [
146
+ { base, corners, drawn: solid(lifted, 'block', x, y, width, height), height, index, x, y },
147
+ ];
148
+ });
149
+ };
150
+ export const paintOrder = (projection, bars) => {
151
+ const columns = new Map();
152
+ for (const bar of depthOrder(projection, bars)) {
153
+ const place = `${bar.x},${bar.y}`;
154
+ columns.set(place, [...(columns.get(place) ?? []), bar]);
155
+ }
156
+ return [...columns.values()].flatMap((column) => column.sort((one, two) => one.base - two.base));
157
+ };
158
+ const PLATE = 0.85;
159
+ export const floorOf = (field, projection, layout = 'row') => {
160
+ const { place, spacing } = placesOf(field, projection, layout);
161
+ const floor = inBox(field.axes.y, 0) * field.axes.extent[1];
162
+ const half = (spacing * PLATE) / 2;
163
+ const seen = new Set();
164
+ return field.cells.flatMap((cell) => {
165
+ const key = `${cell.across} ${cell.lane}`;
166
+ if (seen.has(key)) {
167
+ return [];
168
+ }
169
+ seen.add(key);
170
+ const { x, y } = place(cell);
171
+ const corners = [
172
+ projection.at(x - half, y - half, floor),
173
+ projection.at(x + half, y - half, floor),
174
+ projection.at(x + half, y + half, floor),
175
+ projection.at(x - half, y + half, floor),
176
+ ];
177
+ const front = corners.reduce((low, one) => (one.y > low.y ? one : low));
178
+ return [{ across: cell.across, corners, d: pathData(corners), front }];
179
+ });
180
+ };
181
+ const NAME_WIDE = 0.58;
182
+ export const namesOf = (field, plates, size) => {
183
+ const fronts = new Map();
184
+ for (const plate of plates) {
185
+ const kept = fronts.get(plate.across);
186
+ if (!kept || plate.front.y > kept.y) {
187
+ fronts.set(plate.across, plate.front);
188
+ }
189
+ }
190
+ const tones = new Map();
191
+ for (const cell of field.cells) {
192
+ if (cell.tone && cell.value !== undefined && !tones.has(cell.across)) {
193
+ tones.set(cell.across, cell.tone);
194
+ }
195
+ }
196
+ const all = field.categories.flatMap((text, across) => {
197
+ const front = fronts.get(across);
198
+ return front
199
+ ? [{ across, text, tone: tones.get(across), x: front.x, y: front.y + size * 1.1 }]
200
+ : [];
71
201
  });
202
+ const room = (name) => {
203
+ const half = (name.text.length * size * NAME_WIDE) / 2 + size * 0.4;
204
+ return [name.x - half, name.x + half];
205
+ };
206
+ const clear = (one, kept) => {
207
+ const [from, to] = room(one);
208
+ return kept.every((other) => {
209
+ const [left, right] = room(other);
210
+ return to <= left || from >= right;
211
+ });
212
+ };
213
+ const toned = all.filter((name) => name.tone);
214
+ const rest = all.filter((name) => !name.tone);
215
+ for (let step = 1; step <= Math.max(1, all.length); step++) {
216
+ const kept = toned.filter((name, at) => clear(name, toned.slice(0, at)));
217
+ let fits = true;
218
+ for (const name of rest) {
219
+ if (name.across % step !== 0) {
220
+ continue;
221
+ }
222
+ if (clear(name, kept)) {
223
+ kept.push(name);
224
+ }
225
+ else if (!clear(name, kept.filter((one) => one.tone))) {
226
+ }
227
+ else {
228
+ fits = false;
229
+ break;
230
+ }
231
+ }
232
+ if (fits) {
233
+ return kept.sort((one, other) => one.across - other.across);
234
+ }
235
+ }
236
+ return [];
237
+ };
238
+ const GROW = 1000;
239
+ const STAGGER = 110;
240
+ const SPREAD = 660;
241
+ const stepOf = (categories) => categories > 1 ? Math.min(STAGGER, SPREAD / (categories - 1)) : 0;
242
+ export const grownAt = (across, categories, elapsed) => {
243
+ const t = (elapsed - across * stepOf(categories)) / GROW;
244
+ if (t <= 0) {
245
+ return 0;
246
+ }
247
+ return t >= 1 ? 1 : 1 - (1 - t) ** 3;
72
248
  };
249
+ export const growTime = (categories) => GROW + stepOf(categories) * Math.max(0, categories - 1);