@fusion.dev/helix-core 0.194.0 → 0.195.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.
@@ -16707,6 +16707,54 @@
16707
16707
  "module": "./utils/autofill-select/autofill-select.js"
16708
16708
  }
16709
16709
  },
16710
+ {
16711
+ "kind": "js",
16712
+ "name": "BoxAxisText",
16713
+ "declaration": {
16714
+ "name": "BoxAxisText",
16715
+ "module": "./utils/box-labels/box-labels.js"
16716
+ }
16717
+ },
16718
+ {
16719
+ "kind": "js",
16720
+ "name": "BoxLabel",
16721
+ "declaration": {
16722
+ "name": "BoxLabel",
16723
+ "module": "./utils/box-labels/box-labels.js"
16724
+ }
16725
+ },
16726
+ {
16727
+ "kind": "js",
16728
+ "name": "BoxLabelSizes",
16729
+ "declaration": {
16730
+ "name": "BoxLabelSizes",
16731
+ "module": "./utils/box-labels/box-labels.js"
16732
+ }
16733
+ },
16734
+ {
16735
+ "kind": "js",
16736
+ "name": "BoxPlace",
16737
+ "declaration": {
16738
+ "name": "BoxPlace",
16739
+ "module": "./utils/box-labels/box-labels.js"
16740
+ }
16741
+ },
16742
+ {
16743
+ "kind": "js",
16744
+ "name": "boxLabels",
16745
+ "declaration": {
16746
+ "name": "boxLabels",
16747
+ "module": "./utils/box-labels/box-labels.js"
16748
+ }
16749
+ },
16750
+ {
16751
+ "kind": "js",
16752
+ "name": "SeenPlace",
16753
+ "declaration": {
16754
+ "name": "SeenPlace",
16755
+ "module": "./utils/box-labels/box-labels.js"
16756
+ }
16757
+ },
16710
16758
  {
16711
16759
  "kind": "js",
16712
16760
  "name": "addDays",
@@ -18051,6 +18099,14 @@
18051
18099
  "module": "./utils/projection/projection.js"
18052
18100
  }
18053
18101
  },
18102
+ {
18103
+ "kind": "js",
18104
+ "name": "hullOf",
18105
+ "declaration": {
18106
+ "name": "hullOf",
18107
+ "module": "./utils/projection/projection.js"
18108
+ }
18109
+ },
18054
18110
  {
18055
18111
  "kind": "js",
18056
18112
  "name": "ISOMETRIC",
@@ -26883,6 +26939,52 @@
26883
26939
  }
26884
26940
  ]
26885
26941
  },
26942
+ {
26943
+ "kind": "javascript-module",
26944
+ "path": "src/utils/box-labels/box-labels.ts",
26945
+ "declarations": [
26946
+ {
26947
+ "kind": "function",
26948
+ "name": "boxLabels",
26949
+ "return": {
26950
+ "type": {
26951
+ "text": "BoxLabel[]"
26952
+ }
26953
+ },
26954
+ "parameters": [
26955
+ {
26956
+ "name": "seen",
26957
+ "type": {
26958
+ "text": "(place: BoxPlace) => SeenPlace"
26959
+ }
26960
+ },
26961
+ {
26962
+ "name": "axes",
26963
+ "type": {
26964
+ "text": "{ x: BoxAxisText; y: BoxAxisText; z: BoxAxisText }"
26965
+ }
26966
+ },
26967
+ {
26968
+ "name": "sizes",
26969
+ "type": {
26970
+ "text": "BoxLabelSizes"
26971
+ }
26972
+ }
26973
+ ],
26974
+ "description": "The numbers and names of a box's three axes, placed on the page for a\nview `seen` takes each place in the box through."
26975
+ }
26976
+ ],
26977
+ "exports": [
26978
+ {
26979
+ "kind": "js",
26980
+ "name": "boxLabels",
26981
+ "declaration": {
26982
+ "name": "boxLabels",
26983
+ "module": "src/utils/box-labels/box-labels.ts"
26984
+ }
26985
+ }
26986
+ ]
26987
+ },
26886
26988
  {
26887
26989
  "kind": "javascript-module",
26888
26990
  "path": "src/utils/calendar/calendar.ts",
@@ -31960,6 +32062,24 @@
31960
32062
  }
31961
32063
  ]
31962
32064
  },
32065
+ {
32066
+ "kind": "function",
32067
+ "name": "hullOf",
32068
+ "return": {
32069
+ "type": {
32070
+ "text": "PagePoint[]"
32071
+ }
32072
+ },
32073
+ "parameters": [
32074
+ {
32075
+ "name": "points",
32076
+ "type": {
32077
+ "text": "readonly PagePoint[]"
32078
+ }
32079
+ }
32080
+ ],
32081
+ "description": "The outline around a set of points on the page: their convex hull, in order round it."
32082
+ },
31963
32083
  {
31964
32084
  "kind": "function",
31965
32085
  "name": "solid",
@@ -32074,6 +32194,14 @@
32074
32194
  "module": "src/utils/projection/projection.ts"
32075
32195
  }
32076
32196
  },
32197
+ {
32198
+ "kind": "js",
32199
+ "name": "hullOf",
32200
+ "declaration": {
32201
+ "name": "hullOf",
32202
+ "module": "src/utils/projection/projection.ts"
32203
+ }
32204
+ },
32077
32205
  {
32078
32206
  "kind": "js",
32079
32207
  "name": "solid",
@@ -36231,7 +36359,7 @@
36231
36359
  "type": {
36232
36360
  "text": "string"
36233
36361
  },
36234
- "default": "'0.194.0'",
36362
+ "default": "'0.195.0'",
36235
36363
  "description": "The version of the package this file ships in.\n\n**Written by `fusion/scripts/version.mjs`** wherever it writes the package's\n`package.json`, and stamped back into the repository beside it after a\nrelease, so the two never disagree; a spec beside this holds them to it.\nNothing else in the running code knows which release it is."
36236
36364
  }
36237
36365
  ],
@@ -36867,6 +36995,6 @@
36867
36995
  ],
36868
36996
  "helix": {
36869
36997
  "package": "@fusion.dev/helix-core",
36870
- "version": "0.194.0"
36998
+ "version": "0.195.0"
36871
36999
  }
36872
37000
  }
package/dist/index.d.ts CHANGED
@@ -63,6 +63,7 @@ export { anchorElement } from './utils/anchor-element/anchor-element.js';
63
63
  export { arrowReach, sideOf } from './utils/arrow/arrow.js';
64
64
  export { assignSlots } from './utils/assign-slots/assign-slots.js';
65
65
  export { type DrawAutofillOptions, drawAutofill, } from './utils/autofill-select/autofill-select.js';
66
+ export { type BoxAxisText, type BoxLabel, type BoxLabelSizes, type BoxPlace, boxLabels, type SeenPlace, } from './utils/box-labels/box-labels.js';
66
67
  export { addDays, addMonths, type Barring, barredBy, type CalendarParts, type CalendarView, calendarLocale, type DateCalendar, localToday, MONTH_CALENDARS, type MonthCalendar, type MonthSystem, monthOf, monthOut, monthSystem, type PeriodCell, type PeriodPage, periodCells, periodKey, periodMove, sameMonth, todayIn, weekendOf, weekStartOf, YEARS_SHOWN, yearOut, yearPageOf, } from './utils/calendar/calendar.js';
67
68
  export { capturePointer } from './utils/capture-pointer/capture-pointer.js';
68
69
  export { centerInView } from './utils/center-in-view/center-in-view.js';
@@ -120,7 +121,7 @@ export { applyOwnRule, type OwnRule } from './utils/own-rule/own-rule.js';
120
121
  export { plainClick } from './utils/plain-click/plain-click.js';
121
122
  export { appleTouch, onAndroid, onApple, type Platform } from './utils/platform/platform.js';
122
123
  export { pressType, type RecentPress } from './utils/press-type/press-type.js';
123
- export { type Camera, type Drawn, depthOrder, type Face, ISOMETRIC, LOWEST_TILT, type PagePoint, type Projection, pathData, projectionOf, type Solid, shortNumber, solid, } from './utils/projection/projection.js';
124
+ export { type Camera, type Drawn, depthOrder, type Face, hullOf, ISOMETRIC, LOWEST_TILT, type PagePoint, type Projection, pathData, projectionOf, type Solid, shortNumber, solid, } from './utils/projection/projection.js';
124
125
  export { rangeAdd } from './utils/range-add/range-add.js';
125
126
  export { type ColorBytes, type ColorBytesAlpha, readColor, readColorAlpha, rgbOf, } from './utils/read-color/read-color.js';
126
127
  export { type ChartData, type ChartDatum, rowsFromData, seriesFromData, } from './utils/read-data/read-data.js';
package/dist/index.js CHANGED
@@ -63,6 +63,7 @@ export { anchorElement } from './utils/anchor-element/anchor-element.js';
63
63
  export { arrowReach, sideOf } from './utils/arrow/arrow.js';
64
64
  export { assignSlots } from './utils/assign-slots/assign-slots.js';
65
65
  export { drawAutofill, } from './utils/autofill-select/autofill-select.js';
66
+ export { boxLabels, } from './utils/box-labels/box-labels.js';
66
67
  export { addDays, addMonths, barredBy, calendarLocale, localToday, MONTH_CALENDARS, monthOf, monthOut, monthSystem, periodCells, periodKey, periodMove, sameMonth, todayIn, weekendOf, weekStartOf, YEARS_SHOWN, yearOut, yearPageOf, } from './utils/calendar/calendar.js';
67
68
  export { capturePointer } from './utils/capture-pointer/capture-pointer.js';
68
69
  export { centerInView } from './utils/center-in-view/center-in-view.js';
@@ -120,7 +121,7 @@ export { applyOwnRule } from './utils/own-rule/own-rule.js';
120
121
  export { plainClick } from './utils/plain-click/plain-click.js';
121
122
  export { appleTouch, onAndroid, onApple } from './utils/platform/platform.js';
122
123
  export { pressType } from './utils/press-type/press-type.js';
123
- export { depthOrder, ISOMETRIC, LOWEST_TILT, pathData, projectionOf, shortNumber, solid, } from './utils/projection/projection.js';
124
+ export { depthOrder, hullOf, ISOMETRIC, LOWEST_TILT, pathData, projectionOf, shortNumber, solid, } from './utils/projection/projection.js';
124
125
  export { rangeAdd } from './utils/range-add/range-add.js';
125
126
  export { readColor, readColorAlpha, rgbOf, } from './utils/read-color/read-color.js';
126
127
  export { rowsFromData, seriesFromData, } from './utils/read-data/read-data.js';
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Where a box's numbers and names stand on the page, from whichever side it
3
+ * is seen: the box a spatial chart draws its readings in, in SVG or with
4
+ * three.js.
5
+ *
6
+ * **Each axis is written along the edge that faces the reader**, outside the
7
+ * box. Across and depth are written along the floor's two edges nearest the
8
+ * reader, and up along the upright edge at the left of the box on the page.
9
+ * Each number stands off its edge on the side away from the box, the side of
10
+ * it nearest the edge, and each name stands beyond its numbers. So a turn
11
+ * moves the numbers to the edges that face the reader, and none is written
12
+ * behind the box or inside it.
13
+ *
14
+ * **A number that would cover another label is left out**, which thins an
15
+ * axis seen at a slant, and an axis whose edge is seen end on is left out
16
+ * whole. Every name is kept.
17
+ *
18
+ * Pure, and measured in the page's own units: the SVG drawing passes its
19
+ * drawing's units, and three.js passes pixels.
20
+ */
21
+ import type { PagePoint } from '../projection/projection.js';
22
+ /** A place in the box: across, up, and in depth toward the reader, each from -1 to 1. */
23
+ export type BoxPlace = readonly [number, number, number];
24
+ /** A place on the page, and how near the reader it stands: the larger, the nearer. */
25
+ export interface SeenPlace extends PagePoint {
26
+ depth: number;
27
+ }
28
+ /** One axis' name, and its numbers at their places along it, from -1 to 1. */
29
+ export interface BoxAxisText {
30
+ name: string;
31
+ ticks: readonly {
32
+ at: number;
33
+ text: string;
34
+ }[];
35
+ }
36
+ /** A number or a name, centered on its place on the page. */
37
+ export interface BoxLabel {
38
+ /** Whether it is an axis' name, which is written larger. */
39
+ name: boolean;
40
+ text: string;
41
+ x: number;
42
+ y: number;
43
+ }
44
+ /** How large the labels are, in the page's units. */
45
+ export interface BoxLabelSizes {
46
+ /** The space between an edge and its numbers, and between the numbers and the name. */
47
+ gap: number;
48
+ /** How tall a name is. */
49
+ name: number;
50
+ /** How tall a number is. */
51
+ tick: number;
52
+ /** How wide a character is, as a share of its height, which is near enough to keep labels apart. */
53
+ wide: number;
54
+ }
55
+ /**
56
+ * The numbers and names of a box's three axes, placed on the page for a
57
+ * view `seen` takes each place in the box through.
58
+ */
59
+ export declare const boxLabels: (seen: (place: BoxPlace) => SeenPlace, axes: {
60
+ x: BoxAxisText;
61
+ y: BoxAxisText;
62
+ z: BoxAxisText;
63
+ }, sizes: BoxLabelSizes) => BoxLabel[];
@@ -0,0 +1,89 @@
1
+ const overlaps = (one, other, pad) => Math.abs(one.x - other.x) < one.half.width + other.half.width + pad &&
2
+ Math.abs(one.y - other.y) < one.half.height + other.half.height + pad;
3
+ export const boxLabels = (seen, axes, sizes) => {
4
+ const corners = [];
5
+ for (const x of [-1, 1]) {
6
+ for (const y of [-1, 1]) {
7
+ for (const z of [-1, 1]) {
8
+ corners.push(seen([x, y, z]));
9
+ }
10
+ }
11
+ }
12
+ const middle = {
13
+ x: corners.reduce((sum, corner) => sum + corner.x, 0) / corners.length,
14
+ y: corners.reduce((sum, corner) => sum + corner.y, 0) / corners.length,
15
+ };
16
+ const reach = Math.max(Math.max(...corners.map((corner) => corner.x)) - Math.min(...corners.map((corner) => corner.x)), Math.max(...corners.map((corner) => corner.y)) - Math.min(...corners.map((corner) => corner.y)));
17
+ const back = seen([0, -1, 1]).depth >= seen([0, -1, -1]).depth ? 1 : -1;
18
+ const side = seen([1, -1, 0]).depth >= seen([-1, -1, 0]).depth ? 1 : -1;
19
+ let upright;
20
+ for (const x of [-1, 1]) {
21
+ for (const z of [-1, 1]) {
22
+ const at = seen([x, -1, z]);
23
+ const further = upright === undefined || at.x < upright.at.x - reach * 1e-3;
24
+ const level = upright !== undefined && Math.abs(at.x - upright.at.x) <= reach * 1e-3;
25
+ if (further || (level && upright !== undefined && at.depth > upright.at.depth)) {
26
+ upright = { at, x, z };
27
+ }
28
+ }
29
+ }
30
+ const { x: ux, z: uz } = upright;
31
+ const edges = [
32
+ { along: (t) => [t, -1, back], key: 'x' },
33
+ { along: (t) => [side, -1, t], key: 'z' },
34
+ { along: (t) => [ux, t, uz], key: 'y' },
35
+ ];
36
+ const size = (text, name) => {
37
+ const height = name ? sizes.name : sizes.tick;
38
+ return { height, width: text.length * height * sizes.wide };
39
+ };
40
+ const off = (at, out, box, by) => ({
41
+ half: { height: box.height / 2, width: box.width / 2 },
42
+ x: at.x + out.x * by + (out.x * box.width) / 2,
43
+ y: at.y + out.y * by + (out.y * box.height) / 2,
44
+ });
45
+ const names = [];
46
+ const ticks = [];
47
+ for (const { along, key } of edges) {
48
+ const axis = axes[key];
49
+ const [from, to] = [seen(along(-1)), seen(along(1))];
50
+ const length = Math.hypot(to.x - from.x, to.y - from.y);
51
+ if (length < reach * 0.05) {
52
+ continue;
53
+ }
54
+ let out = { x: -(to.y - from.y) / length, y: (to.x - from.x) / length };
55
+ const away = { x: (from.x + to.x) / 2 - middle.x, y: (from.y + to.y) / 2 - middle.y };
56
+ if (out.x * away.x + out.y * away.y < 0) {
57
+ out = { x: -out.x, y: -out.y };
58
+ }
59
+ let deepest = 0;
60
+ for (const tick of axis.ticks) {
61
+ const box = size(tick.text, false);
62
+ ticks.push({ ...off(seen(along(tick.at)), out, box, sizes.gap), text: tick.text });
63
+ deepest = Math.max(deepest, Math.abs(out.x) * box.width + Math.abs(out.y) * box.height);
64
+ }
65
+ const box = size(axis.name, true);
66
+ names.push({
67
+ ...(key === 'y'
68
+ ?
69
+ off(to, { x: 0, y: -1 }, box, sizes.gap)
70
+ : off(seen(along(0)), out, box, sizes.gap * 2 + deepest)),
71
+ text: axis.name,
72
+ });
73
+ }
74
+ const kept = [...names];
75
+ const labels = names.map((one) => ({
76
+ name: true,
77
+ text: one.text,
78
+ x: one.x,
79
+ y: one.y,
80
+ }));
81
+ for (const tick of ticks) {
82
+ if (kept.some((other) => overlaps(tick, other, sizes.gap / 2))) {
83
+ continue;
84
+ }
85
+ kept.push(tick);
86
+ labels.push({ name: false, text: tick.text, x: tick.x, y: tick.y });
87
+ }
88
+ return labels;
89
+ };
@@ -58,6 +58,8 @@ export type Solid = 'block' | 'cylinder' | 'pyramid';
58
58
  /** A number written short enough for path data, and never as `-0`. */
59
59
  export declare const shortNumber: (value: number) => string;
60
60
  export declare const pathData: (points: readonly PagePoint[]) => string;
61
+ /** The outline around a set of points on the page: their convex hull, in order round it. */
62
+ export declare const hullOf: (points: readonly PagePoint[]) => PagePoint[];
61
63
  /** One face of a solid, as path data, and how far it is darkened toward black. */
62
64
  export interface Face {
63
65
  d: string;
@@ -23,7 +23,7 @@ export const projectionOf = (camera) => {
23
23
  };
24
24
  export const shortNumber = (value) => String(Math.round(value * 1000) / 1000 || 0);
25
25
  export const pathData = (points) => `M${points.map((point) => `${shortNumber(point.x)} ${shortNumber(point.y)}`).join('L')}Z`;
26
- const hullOf = (points) => {
26
+ export const hullOf = (points) => {
27
27
  const sorted = [...points].sort((one, other) => one.x - other.x || one.y - other.y);
28
28
  if (sorted.length < 3) {
29
29
  return sorted;
package/dist/version.d.ts CHANGED
@@ -6,4 +6,4 @@
6
6
  * release, so the two never disagree; a spec beside this holds them to it.
7
7
  * Nothing else in the running code knows which release it is.
8
8
  */
9
- export declare const VERSION = "0.194.0";
9
+ export declare const VERSION = "0.195.0";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.194.0';
1
+ export const VERSION = '0.195.0';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fusion.dev/helix-core",
3
3
  "description": "Helix element primitives: base classes, mixins, decorators, utilities.",
4
- "version": "0.194.0",
4
+ "version": "0.195.0",
5
5
  "license": "MIT",
6
6
  "author": "Ross Sheppard <ross.sheppard@fusion.dev>",
7
7
  "repository": {
@@ -74,12 +74,12 @@
74
74
  "bugs": "https://github.com/project-fusion/fusion/issues",
75
75
  "dependencies": {
76
76
  "@floating-ui/dom": "^1.8.0",
77
- "@fusion.dev/helix-icons": "0.194.0",
77
+ "@fusion.dev/helix-icons": "0.195.0",
78
78
  "tslib": "^2.8.1"
79
79
  },
80
80
  "devDependencies": {
81
- "@fusion.dev/helix-cem": "0.194.0",
82
- "@fusion.dev/helix-testing": "0.194.0",
81
+ "@fusion.dev/helix-cem": "0.195.0",
82
+ "@fusion.dev/helix-testing": "0.195.0",
83
83
  "@lit-labs/ssr": "^4.1.0",
84
84
  "lit": "^3.2.1"
85
85
  },