@fusion.dev/helix-headless 0.110.4 → 0.110.6

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.
@@ -9993,6 +9993,14 @@
9993
9993
  "description": "The card's name as text. A group shows it, and another card's connections\nread it out.\n\nA group needs one because it has no text of its own; a card that has text\nis named by it, and one holding a picture or a figure is named by nothing\nshort enough to read out in another card's connections.",
9994
9994
  "attribute": "label"
9995
9995
  },
9996
+ {
9997
+ "kind": "field",
9998
+ "name": "placement",
9999
+ "privacy": "private",
10000
+ "readonly": true,
10001
+ "default": "new PlacementController(this)",
10002
+ "description": "Where the card was placed, kept in its own shadow root."
10003
+ },
9996
10004
  {
9997
10005
  "kind": "field",
9998
10006
  "name": "name",
@@ -10510,6 +10518,10 @@
10510
10518
  "description": "The card that receives focus when you tab into the board.",
10511
10519
  "name": "current"
10512
10520
  },
10521
+ {
10522
+ "description": "Placed on the board.",
10523
+ "name": "placed"
10524
+ },
10513
10525
  {
10514
10526
  "description": "The card is selected.",
10515
10527
  "name": "selected"
@@ -11386,16 +11398,6 @@
11386
11398
  "description": "A card's geometry by its name.",
11387
11399
  "readonly": true
11388
11400
  },
11389
- {
11390
- "kind": "field",
11391
- "name": "groups",
11392
- "type": {
11393
- "text": "CanvasNode[]"
11394
- },
11395
- "privacy": "private",
11396
- "description": "The groups alone, which is all a card's group is chosen from.",
11397
- "readonly": true
11398
- },
11399
11401
  {
11400
11402
  "kind": "field",
11401
11403
  "name": "cache",
@@ -12196,6 +12198,26 @@
12196
12198
  ],
12197
12199
  "description": "The element behind a connection's key, for what only the element knows."
12198
12200
  },
12201
+ {
12202
+ "kind": "field",
12203
+ "name": "keying",
12204
+ "type": {
12205
+ "text": "| {\n byElement: Map<HelixHeadlessCanvasEdge, string>\n byKey: Map<string, HelixHeadlessCanvasEdge>\n }\n | undefined"
12206
+ },
12207
+ "privacy": "private",
12208
+ "default": "undefined",
12209
+ "description": "Every connection's key and every key's element, worked out once for the\nrender that asks for them.\n\n**One pass, where each asked alone walked them all.** A connection with no\n`name` counts its twins to be told apart, and a render asks every\nconnection's element by key, so four hundred unnamed connections cost 53\nms a render and 4.7 seconds with them all chosen, every step of a drag.\nKept from `willUpdate` to `updated` only: outside a render a handler may\nhave just moved a connection's ends, and its key is worked out afresh."
12210
+ },
12211
+ {
12212
+ "kind": "method",
12213
+ "name": "keyAll",
12214
+ "privacy": "private",
12215
+ "return": {
12216
+ "type": {
12217
+ "text": "{\n byElement: Map<HelixHeadlessCanvasEdge, string>\n byKey: Map<string, HelixHeadlessCanvasEdge>\n }"
12218
+ }
12219
+ }
12220
+ },
12199
12221
  {
12200
12222
  "kind": "field",
12201
12223
  "name": "edgeElements",
@@ -12766,6 +12788,16 @@
12766
12788
  },
12767
12789
  "description": "The board's current view: the document's point at the middle of the view,\nand the zoom.\n\n**Read to save a view and written to restore one**, which a consumer keeping\na minimap in step, or putting a reader back where they were, had no way to\ndo: where the board sat was private, and the events said only which way a\ncontrol had moved it. The middle rather than a corner, so a view saved in\none box shows the same thing in the middle of a box of another size.\nWriting one counts as the reader choosing a view, so a later resize keeps\nit rather than fitting the board again."
12768
12790
  },
12791
+ {
12792
+ "kind": "field",
12793
+ "name": "waiting",
12794
+ "type": {
12795
+ "text": "CanvasView | undefined"
12796
+ },
12797
+ "privacy": "private",
12798
+ "default": "undefined",
12799
+ "description": "A view written before the board could place it, placed once it can."
12800
+ },
12769
12801
  {
12770
12802
  "kind": "field",
12771
12803
  "name": "reported",
@@ -14109,7 +14141,7 @@
14109
14141
  "name": "holding"
14110
14142
  },
14111
14143
  {
14112
- "description": "The user has been still long enough for the controls to hide.",
14144
+ "description": "The user has been still for `idle-delay` milliseconds. The board's own controls stay; select on it to hide chrome of your own.",
14113
14145
  "name": "idle"
14114
14146
  },
14115
14147
  {
@@ -64501,6 +64533,14 @@
64501
64533
  "description": "Longitude, in degrees.",
64502
64534
  "attribute": "lng"
64503
64535
  },
64536
+ {
64537
+ "kind": "field",
64538
+ "name": "placement",
64539
+ "privacy": "private",
64540
+ "readonly": true,
64541
+ "default": "new PlacementController(this)",
64542
+ "description": "Where the marker was placed, kept in its own shadow root."
64543
+ },
64504
64544
  {
64505
64545
  "kind": "field",
64506
64546
  "name": "name",
@@ -65001,6 +65041,10 @@
65001
65041
  "description": "Marked as passed.",
65002
65042
  "name": "passed"
65003
65043
  },
65044
+ {
65045
+ "description": "Placed on the map by the connector.",
65046
+ "name": "placed"
65047
+ },
65004
65048
  {
65005
65049
  "description": "Shows its name before the point, because there was no room after it.",
65006
65050
  "name": "start"
@@ -65865,6 +65909,14 @@
65865
65909
  "description": "Longitude, in degrees.",
65866
65910
  "attribute": "lng"
65867
65911
  },
65912
+ {
65913
+ "kind": "field",
65914
+ "name": "placement",
65915
+ "privacy": "private",
65916
+ "readonly": true,
65917
+ "default": "new PlacementController(this)",
65918
+ "description": "Where the position was placed, kept in its own shadow root."
65919
+ },
65868
65920
  {
65869
65921
  "kind": "field",
65870
65922
  "name": "spoken",
@@ -66296,6 +66348,20 @@
66296
66348
  "fieldName": "lng"
66297
66349
  }
66298
66350
  ],
66351
+ "cssStates": [
66352
+ {
66353
+ "description": "Placed on the map by the connector.",
66354
+ "name": "placed"
66355
+ },
66356
+ {
66357
+ "description": "Set one frame after the first render. Transitions wait for it, so an element does not animate its own arrival.",
66358
+ "name": "settled",
66359
+ "inheritedFrom": {
66360
+ "name": "HelixElement",
66361
+ "module": "src/helix-element/helix-element.ts"
66362
+ }
66363
+ }
66364
+ ],
66299
66365
  "superclass": {
66300
66366
  "name": "HelixElement",
66301
66367
  "package": "@fusion.dev/helix-core"
@@ -66318,16 +66384,6 @@
66318
66384
  },
66319
66385
  "tagName": "helix-headless-map-position",
66320
66386
  "customElement": true,
66321
- "cssStates": [
66322
- {
66323
- "description": "Set one frame after the first render. Transitions wait for it, so an element does not animate its own arrival.",
66324
- "name": "settled",
66325
- "inheritedFrom": {
66326
- "name": "HelixElement",
66327
- "module": "src/helix-element/helix-element.ts"
66328
- }
66329
- }
66330
- ],
66331
66387
  "modulePath": "src/map/position/map-position.ts",
66332
66388
  "definitionPath": "src/map/position/map-position.ts"
66333
66389
  }
@@ -57,6 +57,7 @@ import { type PropertyValues, type TemplateResult } from 'lit';
57
57
  *
58
58
  * @cssstate candidate - While a connection is being drawn and this card is somewhere it could arrive.
59
59
  * @cssstate current - The card that receives focus when you tab into the board.
60
+ * @cssstate placed - Placed on the board.
60
61
  * @cssstate selected - The card is selected.
61
62
  * @cssstate target - A connection being drawn would end here if you released it.
62
63
  */
@@ -97,6 +98,16 @@ export declare class HelixHeadlessCanvasNode extends HelixElement {
97
98
  * short enough to read out in another card's connections.
98
99
  */
99
100
  label: string;
101
+ /** Where the card was placed, kept in its own shadow root. */
102
+ private readonly placement;
103
+ /**
104
+ * @internal Places the card, written by the board: each custom property
105
+ * given, and each one given as `undefined` taken away. Kept in a sheet in the
106
+ * element's own shadow root, since its `style` is the page's.
107
+ */
108
+ place(properties: Readonly<Record<string, string | undefined>>): void;
109
+ /** @internal What a custom property was placed at, or `undefined` where it was not. */
110
+ placed(name: string): string | undefined;
100
111
  /**
101
112
  * The name that connections and events use for this card, unique on its
102
113
  * board.
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { customElement, HelixElement } from '@fusion.dev/helix-core';
7
+ import { customElement, HelixElement, PlacementController } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property, state } from 'lit/decorators.js';
10
10
  const DRAWN = [
@@ -28,6 +28,7 @@ let HelixHeadlessCanvasNode = class HelixHeadlessCanvasNode extends HelixElement
28
28
  this.current = false;
29
29
  this.height = 0;
30
30
  this.label = '';
31
+ this.placement = new PlacementController(this);
31
32
  this.name = '';
32
33
  this.links = '';
33
34
  this.readOnly = false;
@@ -39,6 +40,13 @@ let HelixHeadlessCanvasNode = class HelixHeadlessCanvasNode extends HelixElement
39
40
  this.x = 0;
40
41
  this.y = 0;
41
42
  }
43
+ place(properties) {
44
+ this.placement.set(properties);
45
+ this.toggleState('placed', this.placement.get('--_canvas-card-x') !== undefined);
46
+ }
47
+ placed(name) {
48
+ return this.placement.get(name);
49
+ }
42
50
  willUpdate(changed) {
43
51
  super.willUpdate(changed);
44
52
  this.toggleState('candidate', this.candidate);
@@ -119,7 +119,7 @@ declare const HelixHeadlessCanvas_base: (new (...args: any[]) => import("@fusion
119
119
  * @cssstate empty - While the document has no nodes.
120
120
  * @cssstate fullscreen - While the board fills the screen.
121
121
  * @cssstate holding - An item picked up with a click follows the pointer until the next press puts it down.
122
- * @cssstate idle - The user has been still long enough for the controls to hide.
122
+ * @cssstate idle - The user has been still for `idle-delay` milliseconds. The board's own controls stay; select on it to hide chrome of your own.
123
123
  * @cssstate pressing - While a press on the board, its cards or its connections is under way.
124
124
  */
125
125
  export declare class HelixHeadlessCanvas extends HelixHeadlessCanvas_base {
@@ -320,8 +320,6 @@ export declare class HelixHeadlessCanvas extends HelixHeadlessCanvas_base {
320
320
  private get byName();
321
321
  /** A card's geometry by its name. */
322
322
  private get byId();
323
- /** The groups alone, which is all a card's group is chosen from. */
324
- private get groups();
325
323
  /**
326
324
  * What the board reads from its children, kept for the rest of the turn.
327
325
  *
@@ -549,6 +547,19 @@ export declare class HelixHeadlessCanvas extends HelixHeadlessCanvas_base {
549
547
  private fixed;
550
548
  /** The element behind a connection's key, for what only the element knows. */
551
549
  private edgeElement;
550
+ /**
551
+ * Every connection's key and every key's element, worked out once for the
552
+ * render that asks for them.
553
+ *
554
+ * **One pass, where each asked alone walked them all.** A connection with no
555
+ * `name` counts its twins to be told apart, and a render asks every
556
+ * connection's element by key, so four hundred unnamed connections cost 53
557
+ * ms a render and 4.7 seconds with them all chosen, every step of a drag.
558
+ * Kept from `willUpdate` to `updated` only: outside a render a handler may
559
+ * have just moved a connection's ends, and its key is worked out afresh.
560
+ */
561
+ private keying;
562
+ private keyAll;
552
563
  /** The connections declared, in the order they were written. */
553
564
  private get edgeElements();
554
565
  /**
@@ -784,6 +795,8 @@ export declare class HelixHeadlessCanvas extends HelixHeadlessCanvas_base {
784
795
  */
785
796
  get view(): CanvasView;
786
797
  set view(next: CanvasView);
798
+ /** A view written before the board could place it, placed once it can. */
799
+ private waiting;
787
800
  /** The view last reported, so a render that did not move the board says nothing. */
788
801
  private reported?;
789
802
  /**
@@ -9,7 +9,7 @@ import { html, nothing, svg } from 'lit';
9
9
  import { property, state } from 'lit/decorators.js';
10
10
  import { HelixHeadlessCanvasEdge } from '../edge/canvas-edge.js';
11
11
  import { HelixHeadlessCanvasNode } from '../node/canvas-node.js';
12
- import { colorOf, connector, ends, extent, groupOf, inside, port, reading, sideNear, } from './canvas-document.js';
12
+ import { colorOf, connector, ends, extent, inside, port, reading, sideNear, } from './canvas-document.js';
13
13
  export { fromJsonCanvas } from './canvas-document.js';
14
14
  const MARGIN = 32;
15
15
  const ARROW = 'canvas-arrow';
@@ -33,14 +33,7 @@ const onPlane = (node, origin) => ({
33
33
  const INTERACTIVE = 'input, textarea, select, button, a[href], [contenteditable]:not([contenteditable="false"])';
34
34
  const custom = (element, name) => element?.style.getPropertyValue(name).trim() ?? '';
35
35
  const finite = (value) => (Number.isFinite(value) ? value : 0);
36
- const size = (element, name, value) => {
37
- if (value > 0) {
38
- element.style.setProperty(name, `${value}px`);
39
- }
40
- else {
41
- element.style.removeProperty(name);
42
- }
43
- };
36
+ const size = (value) => (value > 0 ? `${value}px` : undefined);
44
37
  let HelixHeadlessCanvas = class HelixHeadlessCanvas extends MixinViewport(HelixElement) {
45
38
  constructor() {
46
39
  super(...arguments);
@@ -65,6 +58,7 @@ let HelixHeadlessCanvas = class HelixHeadlessCanvas extends MixinViewport(HelixE
65
58
  this.sides = new Map();
66
59
  this.pressing = false;
67
60
  this.pointing = false;
61
+ this.keying = undefined;
68
62
  this.edgeKeys = new WeakMap();
69
63
  this.handlePointerDown = (event) => {
70
64
  const path = event.composedPath();
@@ -379,6 +373,7 @@ let HelixHeadlessCanvas = class HelixHeadlessCanvas extends MixinViewport(HelixE
379
373
  const key = edge ?? card.name;
380
374
  const along = inlineStep(this, event.key);
381
375
  const down = event.key === 'ArrowDown' ? 1 : event.key === 'ArrowUp' ? -1 : 0;
376
+ const plain = !event.metaKey && !event.ctrlKey && !event.altKey;
382
377
  if (card && event.shiftKey && (along !== 0 || down !== 0)) {
383
378
  event.preventDefault();
384
379
  if (this.fixed(card)) {
@@ -432,7 +427,7 @@ let HelixHeadlessCanvas = class HelixHeadlessCanvas extends MixinViewport(HelixE
432
427
  this.partNamed('region')?.focus();
433
428
  return;
434
429
  }
435
- if (!this.linking && (event.key === 'F2' || (card && event.key === 'Enter'))) {
430
+ if (plain && !this.linking && (event.key === 'F2' || (card && event.key === 'Enter'))) {
436
431
  event.preventDefault();
437
432
  this.editStop(key);
438
433
  return;
@@ -450,7 +445,11 @@ let HelixHeadlessCanvas = class HelixHeadlessCanvas extends MixinViewport(HelixE
450
445
  }
451
446
  return;
452
447
  }
453
- if (card && !this.fixed(card) && !this.linking && (event.key === 'c' || event.key === 'C')) {
448
+ if (plain &&
449
+ card &&
450
+ !this.fixed(card) &&
451
+ !this.linking &&
452
+ (event.key === 'c' || event.key === 'C')) {
454
453
  event.preventDefault();
455
454
  this.startLinking(card.name);
456
455
  return;
@@ -459,12 +458,13 @@ let HelixHeadlessCanvas = class HelixHeadlessCanvas extends MixinViewport(HelixE
459
458
  !this.linking &&
460
459
  !this.readOnly &&
461
460
  !this.edgeElement(edge)?.readOnly &&
461
+ plain &&
462
462
  (event.key === 'c' || event.key === 'C')) {
463
463
  event.preventDefault();
464
464
  this.startReconnect(edge, 'to');
465
465
  return;
466
466
  }
467
- if (event.key === 'Delete' || event.key === 'Backspace') {
467
+ if (plain && !this.linking && (event.key === 'Delete' || event.key === 'Backspace')) {
468
468
  const picked = this.picked;
469
469
  const keys = picked.includes(key) ? picked : [key];
470
470
  const loose = keys.filter((one) => one.startsWith('edge:')
@@ -477,7 +477,8 @@ let HelixHeadlessCanvas = class HelixHeadlessCanvas extends MixinViewport(HelixE
477
477
  return;
478
478
  }
479
479
  const walk = this.linking ? this.walk.filter((one) => one.kind === 'node') : this.walk;
480
- const at = walk.findIndex((one) => one.key === key);
480
+ const found = walk.findIndex((one) => one.key === key);
481
+ const at = found === -1 && this.linking ? walk.findIndex((one) => one.key === this.linking?.from) : found;
481
482
  const to = event.key === 'Home'
482
483
  ? 0
483
484
  : event.key === 'End'
@@ -506,7 +507,9 @@ let HelixHeadlessCanvas = class HelixHeadlessCanvas extends MixinViewport(HelixE
506
507
  if (this.linking) {
507
508
  this.linking = { ...this.linking, to: this.arrival(card.name) };
508
509
  }
509
- this.reveal(card.name);
510
+ if (!this.pointing) {
511
+ this.reveal(card.name);
512
+ }
510
513
  }
511
514
  };
512
515
  this.handleFocusOut = (event) => {
@@ -548,6 +551,7 @@ let HelixHeadlessCanvas = class HelixHeadlessCanvas extends MixinViewport(HelixE
548
551
  this.handleEditorBlur = (event) => {
549
552
  this.finishEditing(event.currentTarget, true);
550
553
  };
554
+ this.waiting = undefined;
551
555
  this.handleSlotChange = () => {
552
556
  this.requestUpdate();
553
557
  };
@@ -568,9 +572,6 @@ let HelixHeadlessCanvas = class HelixHeadlessCanvas extends MixinViewport(HelixE
568
572
  get byId() {
569
573
  return this.remember('byId', () => new Map(this.nodes.map((node) => [node.id, node])));
570
574
  }
571
- get groups() {
572
- return this.remember('groups', () => this.nodes.filter((node) => node.type === 'group'));
573
- }
574
575
  remember(key, compute) {
575
576
  const cache = this.cache;
576
577
  if (Object.keys(cache).length === 0) {
@@ -862,6 +863,12 @@ let HelixHeadlessCanvas = class HelixHeadlessCanvas extends MixinViewport(HelixE
862
863
  element.toSide = detail.toSide;
863
864
  this.rekey(linking.edge, this.keyOfElement(element));
864
865
  }
866
+ if (linking.pointer === undefined) {
867
+ this.said = fillTemplate(this.connectedLabel, {
868
+ from: this.nameOf(detail.from),
869
+ to: this.nameOf(detail.to),
870
+ });
871
+ }
865
872
  this.dispatchEvent(new HelixReconnectEvent(detail));
866
873
  }
867
874
  rekey(was, now) {
@@ -967,12 +974,38 @@ let HelixHeadlessCanvas = class HelixHeadlessCanvas extends MixinViewport(HelixE
967
974
  return this.readOnly || card === undefined || card.readOnly;
968
975
  }
969
976
  edgeElement(key) {
977
+ if (this.keying) {
978
+ return this.keying.byKey.get(key);
979
+ }
970
980
  return this.edgeElements.find((child) => this.keyOfElement(child) === key);
971
981
  }
982
+ keyAll() {
983
+ const byElement = new Map();
984
+ const byKey = new Map();
985
+ const twins = new Map();
986
+ for (const element of this.edgeElements) {
987
+ const base = keyBase({ fromNode: element.from, id: element.name, toNode: element.to });
988
+ let key = base;
989
+ if (!element.name) {
990
+ const count = (twins.get(base) ?? 0) + 1;
991
+ twins.set(base, count);
992
+ key = count > 1 ? `${base}#${count}` : base;
993
+ }
994
+ byElement.set(element, key);
995
+ if (!byKey.has(key)) {
996
+ byKey.set(key, element);
997
+ }
998
+ }
999
+ return { byElement, byKey };
1000
+ }
972
1001
  get edgeElements() {
973
1002
  return this.knownChildren.filter((child) => child instanceof HelixHeadlessCanvasEdge);
974
1003
  }
975
1004
  keyOfElement(element) {
1005
+ const known = this.keying?.byElement.get(element);
1006
+ if (known !== undefined) {
1007
+ return known;
1008
+ }
976
1009
  const base = keyBase({ fromNode: element.from, id: element.name, toNode: element.to });
977
1010
  if (element.name) {
978
1011
  return base;
@@ -1302,6 +1335,9 @@ let HelixHeadlessCanvas = class HelixHeadlessCanvas extends MixinViewport(HelixE
1302
1335
  return whole;
1303
1336
  }
1304
1337
  get view() {
1338
+ if (this.waiting) {
1339
+ return this.waiting;
1340
+ }
1305
1341
  const origin = this.origin ?? { x: 0, y: 0 };
1306
1342
  const { height, width } = this.box;
1307
1343
  return {
@@ -1311,10 +1347,18 @@ let HelixHeadlessCanvas = class HelixHeadlessCanvas extends MixinViewport(HelixE
1311
1347
  };
1312
1348
  }
1313
1349
  set view(next) {
1314
- const origin = this.origin ?? { x: 0, y: 0 };
1350
+ if (![next.x, next.y, next.zoom].every(Number.isFinite)) {
1351
+ return;
1352
+ }
1353
+ this.touched = true;
1354
+ if (!this.hasUpdated || !this.origin) {
1355
+ this.waiting = next;
1356
+ return;
1357
+ }
1358
+ this.waiting = undefined;
1315
1359
  const { height, width } = this.box;
1360
+ const origin = this.origin;
1316
1361
  const zoom = Math.min(Math.max(next.zoom, ZOOM_MIN), ZOOM_MAX);
1317
- this.touched = true;
1318
1362
  this.direct = false;
1319
1363
  this.at = {
1320
1364
  x: width / 2 - (next.x - origin.x) * zoom,
@@ -1324,11 +1368,19 @@ let HelixHeadlessCanvas = class HelixHeadlessCanvas extends MixinViewport(HelixE
1324
1368
  }
1325
1369
  toJsonCanvas() {
1326
1370
  const taken = new Set(this.edges.map((edge) => edge.id).filter(Boolean));
1327
- const edges = this.edges.map((edge) => {
1371
+ const edges = this.edges.map((read) => {
1372
+ const { fromSide: _from, toSide: _to, ...rest } = read;
1373
+ const key = this.keyOf(read);
1374
+ const element = this.edgeElement(key);
1375
+ const edge = {
1376
+ ...rest,
1377
+ ...(element?.fromSide ? { fromSide: element.fromSide } : {}),
1378
+ ...(element?.toSide ? { toSide: element.toSide } : {}),
1379
+ };
1328
1380
  if (edge.id) {
1329
- return { ...edge };
1381
+ return edge;
1330
1382
  }
1331
- const base = this.publicName(this.keyOf(edge));
1383
+ const base = this.publicName(key);
1332
1384
  let id = base;
1333
1385
  for (let count = 2; taken.has(id); count++) {
1334
1386
  id = `${base}-${count}`;
@@ -1366,8 +1418,13 @@ let HelixHeadlessCanvas = class HelixHeadlessCanvas extends MixinViewport(HelixE
1366
1418
  firstUpdated(changed) {
1367
1419
  super.firstUpdated(changed);
1368
1420
  this.finishHydration();
1421
+ if (this.waiting) {
1422
+ this.measured = this.box;
1423
+ this.view = this.waiting;
1424
+ }
1369
1425
  }
1370
1426
  updated(changed) {
1427
+ this.keying = undefined;
1371
1428
  super.updated(changed);
1372
1429
  if (this.holding && !this.held) {
1373
1430
  this.letGo();
@@ -1431,12 +1488,6 @@ let HelixHeadlessCanvas = class HelixHeadlessCanvas extends MixinViewport(HelixE
1431
1488
  return true;
1432
1489
  }
1433
1490
  const target = record.target;
1434
- if (target instanceof HelixHeadlessCanvasNode &&
1435
- this.placed.has(target) &&
1436
- custom(target, '--_canvas-card-x') === '') {
1437
- this.placed.delete(target);
1438
- return true;
1439
- }
1440
1491
  const name = target instanceof HelixHeadlessCanvasNode
1441
1492
  ? '--canvas-node-color'
1442
1493
  : target instanceof HelixHeadlessCanvasEdge
@@ -1502,6 +1553,7 @@ let HelixHeadlessCanvas = class HelixHeadlessCanvas extends MixinViewport(HelixE
1502
1553
  this.removeEventListener('pointerup', this.handlePointerUp);
1503
1554
  }
1504
1555
  willUpdate(changed) {
1556
+ this.keying = this.keyAll();
1505
1557
  super.willUpdate(changed);
1506
1558
  const drawn = extent(this.nodes);
1507
1559
  const origin = { x: drawn.x - MARGIN, y: drawn.y - MARGIN };
@@ -1528,33 +1580,18 @@ let HelixHeadlessCanvas = class HelixHeadlessCanvas extends MixinViewport(HelixE
1528
1580
  const stop = this.stop !== undefined && this.keyNamed(this.stop) === this.stop
1529
1581
  ? this.stop
1530
1582
  : reading(all, this.edges, this.order ?? 'spatial')[0]?.id;
1531
- const groups = this.groups;
1532
1583
  for (const card of this.cards) {
1533
- const group = card.group
1534
- ? undefined
1535
- : groupOf({
1536
- height: card.height,
1537
- id: card.name,
1538
- type: 'text',
1539
- width: card.width,
1540
- x: card.x,
1541
- y: card.y,
1542
- }, groups);
1543
1584
  const x = finite(card.x) - origin.x;
1544
1585
  const y = finite(card.y) - origin.y;
1545
1586
  const placed = `${x} ${y} ${finite(card.width)} ${finite(card.height)}`;
1546
1587
  if (this.placed.get(card) !== placed) {
1547
1588
  this.placed.set(card, placed);
1548
- card.style.setProperty('--_canvas-card-x', `${x}px`);
1549
- card.style.setProperty('--_canvas-card-y', `${y}px`);
1550
- size(card, '--_canvas-card-inline-size', finite(card.width));
1551
- size(card, '--_canvas-card-block-size', finite(card.height));
1552
- }
1553
- if (group) {
1554
- card.setAttribute('data-group', group.id);
1555
- }
1556
- else {
1557
- card.removeAttribute('data-group');
1589
+ card.place({
1590
+ '--_canvas-card-block-size': size(finite(card.height)),
1591
+ '--_canvas-card-inline-size': size(finite(card.width)),
1592
+ '--_canvas-card-x': `${x}px`,
1593
+ '--_canvas-card-y': `${y}px`,
1594
+ });
1558
1595
  }
1559
1596
  const node = this.byId.get(card.name);
1560
1597
  card.links = node ? this.linksOf(node) : '';
@@ -1616,7 +1653,8 @@ let HelixHeadlessCanvas = class HelixHeadlessCanvas extends MixinViewport(HelixE
1616
1653
  const key = this.keyOf(edge);
1617
1654
  const element = elements.get(key);
1618
1655
  const drawn = this.geometryOf(edge, origin);
1619
- if (!drawn || (this.linking?.edge === key && !this.linking.pressed)) {
1656
+ const lifted = this.linking?.edge === key && !this.linking.pressed;
1657
+ if (!drawn || (lifted && this.linking?.pointer !== undefined)) {
1620
1658
  return nothing;
1621
1659
  }
1622
1660
  const { from, to } = drawn;
@@ -1632,7 +1670,9 @@ let HelixHeadlessCanvas = class HelixHeadlessCanvas extends MixinViewport(HelixE
1632
1670
  role="button"
1633
1671
  tabindex=${key === stop ? '0' : '-1'}
1634
1672
  />
1635
- <path
1673
+ ${lifted
1674
+ ? nothing
1675
+ : svg `<path
1636
1676
  part="edge"
1637
1677
  aria-hidden="true"
1638
1678
  d=${drawn.d}
@@ -1643,7 +1683,7 @@ let HelixHeadlessCanvas = class HelixHeadlessCanvas extends MixinViewport(HelixE
1643
1683
  marker-end=${ends.end ? `url(#${ARROW})` : nothing}
1644
1684
  marker-start=${ends.start ? `url(#${ARROW})` : nothing}
1645
1685
  style=${custom(element, '--canvas-edge-color') ? `--_canvas-edge-color: ${custom(element, '--canvas-edge-color')}` : nothing}
1646
- />
1686
+ />`}
1647
1687
  `;
1648
1688
  })}
1649
1689
  ${this.renderDraft(origin)}
@@ -36,6 +36,7 @@ import { type PropertyValues, type TemplateResult } from 'lit';
36
36
  * @cssstate fanned - Spread out from a location it shares with other markers, after their cluster was pressed.
37
37
  * @cssstate icon - Has content in the icon slot.
38
38
  * @cssstate passed - Marked as passed.
39
+ * @cssstate placed - Placed on the map by the connector.
39
40
  * @cssstate start - Shows its name before the point, because there was no room after it.
40
41
  */
41
42
  export declare class HelixHeadlessMapMarker extends HelixElement {
@@ -86,6 +87,16 @@ export declare class HelixHeadlessMapMarker extends HelixElement {
86
87
  * the point unless the view's edge would cut the name off there.
87
88
  */
88
89
  side: 'end' | 'start';
90
+ /** Where the marker was placed, kept in its own shadow root. */
91
+ private readonly placement;
92
+ /**
93
+ * @internal Places the marker, written by the connector: each custom property
94
+ * given, and each one given as `undefined` taken away. Kept in a sheet in the
95
+ * element's own shadow root, since its `style` is the page's.
96
+ */
97
+ place(properties: Readonly<Record<string, string | undefined>>): void;
98
+ /** @internal What a custom property was placed at, or `undefined` where it was not. */
99
+ placed(name: string): string | undefined;
89
100
  /** The value `helix-activate` reports. Defaults to the label. */
90
101
  name: string;
91
102
  /**
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { customElement, HelixElement, HelixMapPartChangeEvent } from '@fusion.dev/helix-core';
7
+ import { customElement, HelixElement, HelixMapPartChangeEvent, PlacementController, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property, state } from 'lit/decorators.js';
10
10
  const BOOKKEEPING = new Set([
@@ -25,11 +25,19 @@ let HelixHeadlessMapMarker = class HelixHeadlessMapMarker extends HelixElement {
25
25
  this.gatheredBy = undefined;
26
26
  this.isMapMarker = true;
27
27
  this.side = 'end';
28
+ this.placement = new PlacementController(this);
28
29
  this.name = '';
29
30
  this.passed = false;
30
31
  this.passedLabel = 'passed';
31
32
  this.handingOver = false;
32
33
  }
34
+ place(properties) {
35
+ this.placement.set(properties);
36
+ this.toggleState('placed', this.placement.get('--_map-marker-x') !== undefined);
37
+ }
38
+ placed(name) {
39
+ return this.placement.get(name);
40
+ }
33
41
  willUpdate(changed) {
34
42
  super.willUpdate(changed);
35
43
  this.handingOver =
@@ -28,6 +28,8 @@ import { type PropertyValues, type TemplateResult } from 'lit';
28
28
  * @csspart position - The image of the position, named by its label.
29
29
  * @csspart pulse - A ring that spreads out from the point, showing that the position is live.
30
30
  * @csspart status - The description, announced when it changes.
31
+ *
32
+ * @cssstate placed - Placed on the map by the connector.
31
33
  */
32
34
  export declare class HelixHeadlessMapPosition extends HelixElement {
33
35
  /** How accurate the position is, in meters. Drawn as a ring with this radius. */
@@ -49,6 +51,16 @@ export declare class HelixHeadlessMapPosition extends HelixElement {
49
51
  lat?: number;
50
52
  /** Longitude, in degrees. */
51
53
  lng?: number;
54
+ /** Where the position was placed, kept in its own shadow root. */
55
+ private readonly placement;
56
+ /**
57
+ * @internal Places the position, written by the connector: each custom property
58
+ * given, and each one given as `undefined` taken away. Kept in a sheet in the
59
+ * element's own shadow root, since its `style` is the page's.
60
+ */
61
+ place(properties: Readonly<Record<string, string | undefined>>): void;
62
+ /** @internal What a custom property was placed at, or `undefined` where it was not. */
63
+ placed(name: string): string | undefined;
52
64
  /** The words last spoken, which is what the status holds. */
53
65
  private spoken;
54
66
  /** When they were spoken, for the interval. */
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { customElement, HelixElement, HelixMapPartChangeEvent } from '@fusion.dev/helix-core';
7
+ import { customElement, HelixElement, HelixMapPartChangeEvent, PlacementController, } from '@fusion.dev/helix-core';
8
8
  import { html } from 'lit';
9
9
  import { property, state } from 'lit/decorators.js';
10
10
  let HelixHeadlessMapPosition = class HelixHeadlessMapPosition extends HelixElement {
@@ -13,9 +13,17 @@ let HelixHeadlessMapPosition = class HelixHeadlessMapPosition extends HelixEleme
13
13
  this.announceEvery = 5000;
14
14
  this.isMapPosition = true;
15
15
  this.label = 'Live position';
16
+ this.placement = new PlacementController(this);
16
17
  this.spoken = '';
17
18
  this.spokenAt = Number.NEGATIVE_INFINITY;
18
19
  }
20
+ place(properties) {
21
+ this.placement.set(properties);
22
+ this.toggleState('placed', this.placement.get('--_map-position-x') !== undefined);
23
+ }
24
+ placed(name) {
25
+ return this.placement.get(name);
26
+ }
19
27
  willUpdate(changed) {
20
28
  super.willUpdate(changed);
21
29
  if (!changed.has('description') || !this.hasUpdated) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fusion.dev/helix-headless",
3
3
  "description": "Headless component tier. Structure, accessibility, state, and form participation. No styling.",
4
- "version": "0.110.4",
4
+ "version": "0.110.6",
5
5
  "license": "MIT",
6
6
  "author": "Ross Sheppard <ross.sheppard@fusion.dev>",
7
7
  "repository": {
@@ -46,13 +46,13 @@
46
46
  },
47
47
  "bugs": "https://github.com/project-fusion/fusion/issues",
48
48
  "dependencies": {
49
- "@fusion.dev/helix-core": "0.110.4",
50
- "@fusion.dev/helix-icons": "0.110.4",
49
+ "@fusion.dev/helix-core": "0.110.6",
50
+ "@fusion.dev/helix-icons": "0.110.6",
51
51
  "uqr": "^0.1.3"
52
52
  },
53
53
  "devDependencies": {
54
- "@fusion.dev/helix-cem": "0.110.4",
55
- "@fusion.dev/helix-testing": "0.110.4",
54
+ "@fusion.dev/helix-cem": "0.110.6",
55
+ "@fusion.dev/helix-testing": "0.110.6",
56
56
  "@fusion.dev/scripts": "0.0.0-dev",
57
57
  "@happy-dom/global-registrator": "^20.0.0",
58
58
  "@lit-labs/ssr": "^4.1.0",
package/types/jsx.d.ts CHANGED
@@ -24517,6 +24517,7 @@ export type CustomElements = {
24517
24517
  *
24518
24518
  * - `candidate`: While a connection is being drawn and this card is somewhere it could arrive.
24519
24519
  * - `current`: The card that receives focus when you tab into the board.
24520
+ * - `placed`: Placed on the board.
24520
24521
  * - `selected`: The card is selected.
24521
24522
  * - `target`: A connection being drawn would end here if you released it.
24522
24523
  * - `settled`: Set one frame after the first render. Transitions wait for it, so an element does not animate its own arrival.
@@ -24856,7 +24857,7 @@ export type CustomElements = {
24856
24857
  * - `empty`: While the document has no nodes.
24857
24858
  * - `fullscreen`: While the board fills the screen.
24858
24859
  * - `holding`: An item picked up with a click follows the pointer until the next press puts it down.
24859
- * - `idle`: The user has been still long enough for the controls to hide.
24860
+ * - `idle`: The user has been still for `idle-delay` milliseconds. The board's own controls stay; select on it to hide chrome of your own.
24860
24861
  * - `pressing`: While a press on the board, its cards or its connections is under way.
24861
24862
  * - `settled`: Set one frame after the first render. Transitions wait for it, so an element does not animate its own arrival.
24862
24863
  */
@@ -32754,6 +32755,7 @@ export type CustomElements = {
32754
32755
  * - `fanned`: Spread out from a location it shares with other markers, after their cluster was pressed.
32755
32756
  * - `icon`: Has content in the icon slot.
32756
32757
  * - `passed`: Marked as passed.
32758
+ * - `placed`: Placed on the map by the connector.
32757
32759
  * - `start`: Shows its name before the point, because there was no room after it.
32758
32760
  * - `settled`: Set one frame after the first render. Transitions wait for it, so an element does not animate its own arrival.
32759
32761
  */
@@ -32908,6 +32910,7 @@ export type CustomElements = {
32908
32910
  *
32909
32911
  * These can be used to apply styling when a component is in a given state.
32910
32912
  *
32913
+ * - `placed`: Placed on the map by the connector.
32911
32914
  * - `settled`: Set one frame after the first render. Transitions wait for it, so an element does not animate its own arrival.
32912
32915
  */
32913
32916
  "helix-headless-map-position": Partial<
@@ -44449,6 +44452,7 @@ export type CustomElementsSolidJs = {
44449
44452
  *
44450
44453
  * - `candidate`: While a connection is being drawn and this card is somewhere it could arrive.
44451
44454
  * - `current`: The card that receives focus when you tab into the board.
44455
+ * - `placed`: Placed on the board.
44452
44456
  * - `selected`: The card is selected.
44453
44457
  * - `target`: A connection being drawn would end here if you released it.
44454
44458
  * - `settled`: Set one frame after the first render. Transitions wait for it, so an element does not animate its own arrival.
@@ -44789,7 +44793,7 @@ export type CustomElementsSolidJs = {
44789
44793
  * - `empty`: While the document has no nodes.
44790
44794
  * - `fullscreen`: While the board fills the screen.
44791
44795
  * - `holding`: An item picked up with a click follows the pointer until the next press puts it down.
44792
- * - `idle`: The user has been still long enough for the controls to hide.
44796
+ * - `idle`: The user has been still for `idle-delay` milliseconds. The board's own controls stay; select on it to hide chrome of your own.
44793
44797
  * - `pressing`: While a press on the board, its cards or its connections is under way.
44794
44798
  * - `settled`: Set one frame after the first render. Transitions wait for it, so an element does not animate its own arrival.
44795
44799
  */
@@ -52799,6 +52803,7 @@ export type CustomElementsSolidJs = {
52799
52803
  * - `fanned`: Spread out from a location it shares with other markers, after their cluster was pressed.
52800
52804
  * - `icon`: Has content in the icon slot.
52801
52805
  * - `passed`: Marked as passed.
52806
+ * - `placed`: Placed on the map by the connector.
52802
52807
  * - `start`: Shows its name before the point, because there was no room after it.
52803
52808
  * - `settled`: Set one frame after the first render. Transitions wait for it, so an element does not animate its own arrival.
52804
52809
  */
@@ -52959,6 +52964,7 @@ export type CustomElementsSolidJs = {
52959
52964
  *
52960
52965
  * These can be used to apply styling when a component is in a given state.
52961
52966
  *
52967
+ * - `placed`: Placed on the map by the connector.
52962
52968
  * - `settled`: Set one frame after the first render. Transitions wait for it, so an element does not animate its own arrival.
52963
52969
  */
52964
52970
  "helix-headless-map-position": Partial<