@fusion.dev/helix-core 0.103.2 → 0.103.3

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.
@@ -5997,6 +5997,22 @@
5997
5997
  "module": "./events/events.js"
5998
5998
  }
5999
5999
  },
6000
+ {
6001
+ "kind": "js",
6002
+ "name": "ViewportPanDetail",
6003
+ "declaration": {
6004
+ "name": "ViewportPanDetail",
6005
+ "module": "./events/events.js"
6006
+ }
6007
+ },
6008
+ {
6009
+ "kind": "js",
6010
+ "name": "ViewportZoomDetail",
6011
+ "declaration": {
6012
+ "name": "ViewportZoomDetail",
6013
+ "module": "./events/events.js"
6014
+ }
6015
+ },
6000
6016
  {
6001
6017
  "kind": "js",
6002
6018
  "name": "HelixElement",
@@ -6253,6 +6269,38 @@
6253
6269
  "module": "./mixins/mixin-tokens/mixin-tokens.js"
6254
6270
  }
6255
6271
  },
6272
+ {
6273
+ "kind": "js",
6274
+ "name": "MixinViewport",
6275
+ "declaration": {
6276
+ "name": "MixinViewport",
6277
+ "module": "./mixins/mixin-viewport/mixin-viewport.js"
6278
+ }
6279
+ },
6280
+ {
6281
+ "kind": "js",
6282
+ "name": "ViewportControlsPlacement",
6283
+ "declaration": {
6284
+ "name": "ViewportControlsPlacement",
6285
+ "module": "./mixins/mixin-viewport/mixin-viewport.js"
6286
+ }
6287
+ },
6288
+ {
6289
+ "kind": "js",
6290
+ "name": "ViewportElement",
6291
+ "declaration": {
6292
+ "name": "ViewportElement",
6293
+ "module": "./mixins/mixin-viewport/mixin-viewport.js"
6294
+ }
6295
+ },
6296
+ {
6297
+ "kind": "js",
6298
+ "name": "ViewportWay",
6299
+ "declaration": {
6300
+ "name": "ViewportWay",
6301
+ "module": "./mixins/mixin-viewport/mixin-viewport.js"
6302
+ }
6303
+ },
6256
6304
  {
6257
6305
  "kind": "js",
6258
6306
  "name": "anchorElement",
@@ -10101,6 +10149,605 @@
10101
10149
  }
10102
10150
  ]
10103
10151
  },
10152
+ {
10153
+ "kind": "javascript-module",
10154
+ "path": "src/mixins/mixin-viewport/mixin-viewport.ts",
10155
+ "declarations": [
10156
+ {
10157
+ "kind": "class",
10158
+ "description": "The API a pan-and-zoom surface has, as TypeScript sees it.",
10159
+ "name": "ViewportElement",
10160
+ "members": [
10161
+ {
10162
+ "kind": "field",
10163
+ "name": "controls",
10164
+ "type": {
10165
+ "text": "string | undefined"
10166
+ },
10167
+ "description": "Which of the controls the surface draws, as a list of what to keep."
10168
+ },
10169
+ {
10170
+ "kind": "field",
10171
+ "name": "controlsPlacement",
10172
+ "type": {
10173
+ "text": "'top-start' | 'top-end' | 'bottom-start' | 'bottom-end' | undefined"
10174
+ },
10175
+ "description": "Which corner the controls sit in."
10176
+ },
10177
+ {
10178
+ "kind": "field",
10179
+ "name": "fullscreenLabel",
10180
+ "type": {
10181
+ "text": "string"
10182
+ },
10183
+ "description": "What the control that fills the screen is called."
10184
+ },
10185
+ {
10186
+ "kind": "field",
10187
+ "name": "idleDelay",
10188
+ "type": {
10189
+ "text": "number"
10190
+ },
10191
+ "description": "How long the reader may be still before the chrome hides, in milliseconds."
10192
+ },
10193
+ {
10194
+ "kind": "field",
10195
+ "name": "panLabel",
10196
+ "type": {
10197
+ "text": "string"
10198
+ },
10199
+ "description": "What one pan control is called, with `{way}` filled in."
10200
+ },
10201
+ {
10202
+ "kind": "field",
10203
+ "name": "panStep",
10204
+ "type": {
10205
+ "text": "number"
10206
+ },
10207
+ "description": "How far one press moves the view, as a fraction of what is on screen."
10208
+ },
10209
+ {
10210
+ "kind": "field",
10211
+ "name": "zoomInLabel",
10212
+ "type": {
10213
+ "text": "string"
10214
+ },
10215
+ "description": "What the control that zooms in is called."
10216
+ },
10217
+ {
10218
+ "kind": "field",
10219
+ "name": "zoomOutLabel",
10220
+ "type": {
10221
+ "text": "string"
10222
+ },
10223
+ "description": "What the control that zooms out is called."
10224
+ },
10225
+ {
10226
+ "kind": "method",
10227
+ "name": "pan",
10228
+ "return": {
10229
+ "type": {
10230
+ "text": "void"
10231
+ }
10232
+ },
10233
+ "parameters": [
10234
+ {
10235
+ "name": "to",
10236
+ "type": {
10237
+ "text": "ViewportWay"
10238
+ }
10239
+ }
10240
+ ],
10241
+ "description": "Asks for the view to move, which the consumer answers."
10242
+ },
10243
+ {
10244
+ "kind": "method",
10245
+ "name": "zoom",
10246
+ "return": {
10247
+ "type": {
10248
+ "text": "void"
10249
+ }
10250
+ },
10251
+ "parameters": [
10252
+ {
10253
+ "name": "by",
10254
+ "type": {
10255
+ "text": "number"
10256
+ }
10257
+ }
10258
+ ],
10259
+ "description": "Asks for the view to zoom, `1` in and `-1` out."
10260
+ },
10261
+ {
10262
+ "kind": "method",
10263
+ "name": "toggleFullscreen",
10264
+ "return": {
10265
+ "type": {
10266
+ "text": "void"
10267
+ }
10268
+ },
10269
+ "description": "Fills the screen with the surface, or comes back."
10270
+ },
10271
+ {
10272
+ "kind": "field",
10273
+ "name": "resting",
10274
+ "type": {
10275
+ "text": "boolean"
10276
+ },
10277
+ "privacy": "protected",
10278
+ "description": "Whether the reader has been still long enough to hide the chrome."
10279
+ },
10280
+ {
10281
+ "kind": "field",
10282
+ "name": "filled",
10283
+ "type": {
10284
+ "text": "boolean"
10285
+ },
10286
+ "privacy": "protected",
10287
+ "description": "Whether the surface is filling the screen."
10288
+ },
10289
+ {
10290
+ "kind": "field",
10291
+ "name": "idle",
10292
+ "type": {
10293
+ "text": "IdleController"
10294
+ },
10295
+ "privacy": "protected",
10296
+ "readonly": true,
10297
+ "description": "How long the reader has been still, which the family's own frame stirs."
10298
+ },
10299
+ {
10300
+ "kind": "field",
10301
+ "name": "fullscreen",
10302
+ "type": {
10303
+ "text": "FullscreenController"
10304
+ },
10305
+ "privacy": "protected",
10306
+ "readonly": true,
10307
+ "description": "Into the screen and back, for a family that offers its own way in."
10308
+ },
10309
+ {
10310
+ "kind": "field",
10311
+ "name": "handleKeydown",
10312
+ "type": {
10313
+ "text": "(event: KeyboardEvent) => void"
10314
+ },
10315
+ "privacy": "protected",
10316
+ "description": "The key relay, bound by the family's own frame."
10317
+ },
10318
+ {
10319
+ "kind": "field",
10320
+ "name": "controlsLabel",
10321
+ "type": {
10322
+ "text": "string"
10323
+ },
10324
+ "privacy": "protected",
10325
+ "description": "What the group of controls is called, for a family that is not a map.",
10326
+ "readonly": true
10327
+ },
10328
+ {
10329
+ "kind": "method",
10330
+ "name": "panEvent",
10331
+ "privacy": "protected",
10332
+ "return": {
10333
+ "type": {
10334
+ "text": "Event"
10335
+ }
10336
+ },
10337
+ "parameters": [
10338
+ {
10339
+ "name": "detail",
10340
+ "type": {
10341
+ "text": "ViewportPanDetail"
10342
+ }
10343
+ }
10344
+ ],
10345
+ "description": "The pan event, built and not dispatched, for a family that names its own."
10346
+ },
10347
+ {
10348
+ "kind": "method",
10349
+ "name": "zoomEvent",
10350
+ "privacy": "protected",
10351
+ "return": {
10352
+ "type": {
10353
+ "text": "Event"
10354
+ }
10355
+ },
10356
+ "parameters": [
10357
+ {
10358
+ "name": "detail",
10359
+ "type": {
10360
+ "text": "ViewportZoomDetail"
10361
+ }
10362
+ }
10363
+ ],
10364
+ "description": "The zoom event, built and not dispatched, for a family that names its own."
10365
+ },
10366
+ {
10367
+ "kind": "method",
10368
+ "name": "renderControls",
10369
+ "privacy": "protected",
10370
+ "return": {
10371
+ "type": {
10372
+ "text": "TemplateResult | typeof nothing"
10373
+ }
10374
+ },
10375
+ "description": "The controls, or nothing where the surface keeps none of them."
10376
+ }
10377
+ ],
10378
+ "helix": {}
10379
+ },
10380
+ {
10381
+ "kind": "mixin",
10382
+ "description": "Grafts a pan-and-zoom surface: its controls, its keys, and the way out.\n\n**Nothing here is geographic, which is why it is here.** `Map` was written\nfirst and its frame turned out to be a surface a reader can move, fill the\nscreen with, and stop touching: a canvas of notes and a graph of nodes want\nthe same four hundred lines, and the only thing that made them Map's was\nbeing written there. Extracted before the second consumer rather than after,\nwhich is the opposite of what [#383](queue.md) had to conclude about a\ncontroller promoted ahead of one.\n\n**Nothing here moves anything, either.** Both controls ask, through an event\na family names, and the answer is the consumer's: the surface a reader is\nlooking at belongs to them, whether it is a tile renderer, a canvas or a\nforce layout. A mixin that moved the view would have to know what the view\nwas.\n\n**The controls are buttons, and that is a decision against arrow keys.** They\nwork too, but what a screen reader does with them is not ours to decide:\nmacOS passes them through, Windows readers hold them for their own reading\nmode unless a page takes `role=\"application\"`, which costs the reader their\nnormal way of reading everything else. A button is findable and operable in\nevery screen reader on every platform without that bargain.\n\n**What stays in the family is `render()`, its slots and its part\ndocumentation**, because a part named through an interpolation is one the\nmanifest cannot see: a loop over the four pan buttons would publish `pan-`\nand a consumer's `::part(pan-up)` would match nothing.",
10383
+ "name": "MixinViewport",
10384
+ "members": [
10385
+ {
10386
+ "kind": "field",
10387
+ "name": "controls",
10388
+ "type": {
10389
+ "text": "string | undefined"
10390
+ },
10391
+ "description": "Which of the controls this surface draws, as a list of what to keep.\n\n**A list rather than a switch, because the overlap is not symmetric.** A\nsurface a consumer brings often has zoom controls of its own and almost\nnever has pan buttons, so a single switch would make them choose between\na duplicate and nothing.\n\n`none` draws none of them, which is the right answer where what was\nslotted already has controls a reader can find: what this argues for is\nthat *a* findable control exists, never that it is ours. The methods stay\npublic either way, so a consumer drawing their own chrome can still drive\nthe surface.\n\nAbsent is all three, which is this library's rule that a resting value is\nan attribute's absence, and it is what the stylesheet asks about: the\nlayout rules are written against `[controls]:not([controls~=\"zoom\"])`\nrather than the bare negation, or a surface that named no list at all\nwould be laid out as though it had dropped everything.",
10392
+ "attribute": "controls",
10393
+ "reflects": true
10394
+ },
10395
+ {
10396
+ "kind": "field",
10397
+ "name": "controlsPlacement",
10398
+ "type": {
10399
+ "text": "'top-start' | 'top-end' | 'bottom-start' | 'bottom-end' | undefined"
10400
+ },
10401
+ "description": "Which corner the controls sit in.\n\n**Configurable because there is no safe default**, which is a collision\nrather than a taste: whatever a consumer slots may put its own controls\nin any corner, and every library that draws some made this a setting for\nthe same reason.\n\nLogical corners rather than left and right, so a right-to-left page moves\nthem without a consumer writing a second rule. Absent is the top end, a\nresting value being an attribute's absence: a default that reflected\nwould write an attribute onto every host that no server sent.",
10402
+ "attribute": "controls-placement",
10403
+ "reflects": true
10404
+ },
10405
+ {
10406
+ "kind": "field",
10407
+ "name": "fullscreenLabel",
10408
+ "type": {
10409
+ "text": "string"
10410
+ },
10411
+ "default": "'Fill the screen'",
10412
+ "description": "What the control that fills the screen is called.",
10413
+ "attribute": "fullscreen-label"
10414
+ },
10415
+ {
10416
+ "kind": "field",
10417
+ "name": "idleDelay",
10418
+ "type": {
10419
+ "text": "number"
10420
+ },
10421
+ "default": "0",
10422
+ "description": "How long the reader may be still before the chrome hides, in milliseconds.\n\nZero keeps it up, which is the default: these controls are how a keyboard\nreader operates the surface, and chrome that hid itself by default would\nbe the component undoing its own argument. Set it for a display nobody\ndrives.",
10423
+ "attribute": "idle-delay"
10424
+ },
10425
+ {
10426
+ "kind": "field",
10427
+ "name": "panLabel",
10428
+ "type": {
10429
+ "text": "string"
10430
+ },
10431
+ "default": "'Pan {way}'",
10432
+ "description": "What one pan control is called, with `{way}` filled in.",
10433
+ "attribute": "pan-label"
10434
+ },
10435
+ {
10436
+ "kind": "field",
10437
+ "name": "panStep",
10438
+ "type": {
10439
+ "text": "number"
10440
+ },
10441
+ "default": "0.25",
10442
+ "description": "How far one press moves the view, as a fraction of what is on screen.\n\nA fraction rather than a distance, because this surface does not know the\nscale of what is inside it: a quarter of the viewport is the same gesture\nat any zoom, and a consumer who wants their own units multiplies by them.",
10443
+ "attribute": "pan-step"
10444
+ },
10445
+ {
10446
+ "kind": "field",
10447
+ "name": "zoomInLabel",
10448
+ "type": {
10449
+ "text": "string"
10450
+ },
10451
+ "default": "'Zoom in'",
10452
+ "description": "What the control that zooms in is called.",
10453
+ "attribute": "zoom-in-label"
10454
+ },
10455
+ {
10456
+ "kind": "field",
10457
+ "name": "zoomOutLabel",
10458
+ "type": {
10459
+ "text": "string"
10460
+ },
10461
+ "default": "'Zoom out'",
10462
+ "description": "What the control that zooms out is called.",
10463
+ "attribute": "zoom-out-label"
10464
+ },
10465
+ {
10466
+ "kind": "field",
10467
+ "name": "resting",
10468
+ "type": {
10469
+ "text": "boolean"
10470
+ },
10471
+ "privacy": "protected",
10472
+ "default": "false",
10473
+ "description": "Whether the reader has been still long enough to hide the chrome."
10474
+ },
10475
+ {
10476
+ "kind": "field",
10477
+ "name": "filled",
10478
+ "type": {
10479
+ "text": "boolean"
10480
+ },
10481
+ "privacy": "protected",
10482
+ "default": "false",
10483
+ "description": "Whether the surface is filling the screen."
10484
+ },
10485
+ {
10486
+ "kind": "field",
10487
+ "name": "fullscreen",
10488
+ "privacy": "protected",
10489
+ "readonly": true,
10490
+ "default": "new FullscreenController(this, (full) => { this.filled = full })"
10491
+ },
10492
+ {
10493
+ "kind": "field",
10494
+ "name": "idle",
10495
+ "privacy": "protected",
10496
+ "readonly": true,
10497
+ "default": "new IdleController( this, () => this.idleDelay, (resting) => { this.resting = resting }, )"
10498
+ },
10499
+ {
10500
+ "kind": "field",
10501
+ "name": "controlsLabel",
10502
+ "type": {
10503
+ "text": "string"
10504
+ },
10505
+ "privacy": "protected",
10506
+ "description": "What the group of controls is called.\n\nA getter rather than a string in the template, so a family that is not a\nmap does not announce one. Not a property: what this group is called is a\nfact about the family rather than a choice a consumer makes per instance,\nand the labels a consumer does set are the ones on the controls\nthemselves.",
10507
+ "readonly": true
10508
+ },
10509
+ {
10510
+ "kind": "method",
10511
+ "name": "panEvent",
10512
+ "privacy": "protected",
10513
+ "return": {
10514
+ "type": {
10515
+ "text": "Event"
10516
+ }
10517
+ },
10518
+ "parameters": [
10519
+ {
10520
+ "name": "detail",
10521
+ "type": {
10522
+ "text": "ViewportPanDetail"
10523
+ }
10524
+ }
10525
+ ],
10526
+ "description": "The pan event, built and not dispatched.\n\nA factory for the reason `MixinDismissible` has one: the surface is\nshared and the name is not. `Map` sends `helix-map-pan` because that is\nwhat a consumer of a map listens for, and the next family names its own."
10527
+ },
10528
+ {
10529
+ "kind": "method",
10530
+ "name": "zoomEvent",
10531
+ "privacy": "protected",
10532
+ "return": {
10533
+ "type": {
10534
+ "text": "Event"
10535
+ }
10536
+ },
10537
+ "parameters": [
10538
+ {
10539
+ "name": "detail",
10540
+ "type": {
10541
+ "text": "ViewportZoomDetail"
10542
+ }
10543
+ }
10544
+ ],
10545
+ "description": "The zoom event, built and not dispatched, for the same reason."
10546
+ },
10547
+ {
10548
+ "kind": "method",
10549
+ "name": "pan",
10550
+ "return": {
10551
+ "type": {
10552
+ "text": "void"
10553
+ }
10554
+ },
10555
+ "parameters": [
10556
+ {
10557
+ "name": "to",
10558
+ "type": {
10559
+ "text": "ViewportWay"
10560
+ }
10561
+ }
10562
+ ],
10563
+ "description": "Asks for the view to move, which the consumer answers."
10564
+ },
10565
+ {
10566
+ "kind": "method",
10567
+ "name": "zoom",
10568
+ "return": {
10569
+ "type": {
10570
+ "text": "void"
10571
+ }
10572
+ },
10573
+ "parameters": [
10574
+ {
10575
+ "name": "by",
10576
+ "type": {
10577
+ "text": "number"
10578
+ }
10579
+ }
10580
+ ],
10581
+ "description": "Asks for the view to zoom, `1` in and `-1` out."
10582
+ },
10583
+ {
10584
+ "kind": "method",
10585
+ "name": "toggleFullscreen",
10586
+ "return": {
10587
+ "type": {
10588
+ "text": "void"
10589
+ }
10590
+ },
10591
+ "description": "Fills the screen with the surface, or comes back."
10592
+ },
10593
+ {
10594
+ "kind": "method",
10595
+ "name": "draws",
10596
+ "privacy": "private",
10597
+ "return": {
10598
+ "type": {
10599
+ "text": "boolean"
10600
+ }
10601
+ },
10602
+ "parameters": [
10603
+ {
10604
+ "name": "group",
10605
+ "type": {
10606
+ "text": "'pan' | 'zoom' | 'fullscreen'"
10607
+ }
10608
+ }
10609
+ ],
10610
+ "description": "Whether a named group of controls is one this surface draws."
10611
+ },
10612
+ {
10613
+ "kind": "method",
10614
+ "name": "named",
10615
+ "privacy": "private",
10616
+ "return": {
10617
+ "type": {
10618
+ "text": "string"
10619
+ }
10620
+ },
10621
+ "parameters": [
10622
+ {
10623
+ "name": "way",
10624
+ "type": {
10625
+ "text": "ViewportWay"
10626
+ }
10627
+ }
10628
+ ],
10629
+ "description": "What one pan control is called, with the way it goes filled in.\n\nLeft and right rather than start and end, because the label is read aloud\nand a reader knows which way left is. The event keeps the reading-order\nname, which is the one a right-to-left page needs."
10630
+ },
10631
+ {
10632
+ "kind": "field",
10633
+ "name": "handleKeydown",
10634
+ "privacy": "protected",
10635
+ "readonly": true,
10636
+ "description": "The arrow keys, which are the second way rather than the only one.\n\n`inlineStep` rather than a test against `ArrowLeft`, so the reader on a\nright-to-left page moves the way their text runs, which is what the pan\ncontrols say too.\n\n**Only from the surface's own frame, and that is the difference between\none move and two.** What a consumer slots may answer the arrow keys\nitself, and a press relayed from inside it moves their view once on its\nown and once more on the event this would send. The rule is the region\nand the controls rather than the whole shadow root, because a family with\nits own focusable content in there, a list of notes beside a canvas,\nwould otherwise pan the surface out from under a reader arrowing through\nit."
10637
+ },
10638
+ {
10639
+ "kind": "method",
10640
+ "name": "renderControls",
10641
+ "privacy": "protected",
10642
+ "return": {
10643
+ "type": {
10644
+ "text": "TemplateResult | typeof nothing"
10645
+ }
10646
+ },
10647
+ "description": "The controls, or nothing where a consumer kept none of them.\n\n**Written out rather than generated**, because a part name behind an\ninterpolation is a part the manifest cannot see: a loop would publish\n`pan-` and a consumer's `::part(pan-up)` would match nothing."
10648
+ }
10649
+ ],
10650
+ "attributes": [
10651
+ {
10652
+ "name": "controls",
10653
+ "type": {
10654
+ "text": "string | undefined"
10655
+ },
10656
+ "description": "Which of the controls this surface draws, as a list of what to keep.\n\n**A list rather than a switch, because the overlap is not symmetric.** A\nsurface a consumer brings often has zoom controls of its own and almost\nnever has pan buttons, so a single switch would make them choose between\na duplicate and nothing.\n\n`none` draws none of them, which is the right answer where what was\nslotted already has controls a reader can find: what this argues for is\nthat *a* findable control exists, never that it is ours. The methods stay\npublic either way, so a consumer drawing their own chrome can still drive\nthe surface.\n\nAbsent is all three, which is this library's rule that a resting value is\nan attribute's absence, and it is what the stylesheet asks about: the\nlayout rules are written against `[controls]:not([controls~=\"zoom\"])`\nrather than the bare negation, or a surface that named no list at all\nwould be laid out as though it had dropped everything.",
10657
+ "fieldName": "controls"
10658
+ },
10659
+ {
10660
+ "name": "controls-placement",
10661
+ "type": {
10662
+ "text": "'top-start' | 'top-end' | 'bottom-start' | 'bottom-end' | undefined"
10663
+ },
10664
+ "description": "Which corner the controls sit in.\n\n**Configurable because there is no safe default**, which is a collision\nrather than a taste: whatever a consumer slots may put its own controls\nin any corner, and every library that draws some made this a setting for\nthe same reason.\n\nLogical corners rather than left and right, so a right-to-left page moves\nthem without a consumer writing a second rule. Absent is the top end, a\nresting value being an attribute's absence: a default that reflected\nwould write an attribute onto every host that no server sent.",
10665
+ "fieldName": "controlsPlacement"
10666
+ },
10667
+ {
10668
+ "name": "fullscreen-label",
10669
+ "type": {
10670
+ "text": "string"
10671
+ },
10672
+ "default": "'Fill the screen'",
10673
+ "description": "What the control that fills the screen is called.",
10674
+ "fieldName": "fullscreenLabel"
10675
+ },
10676
+ {
10677
+ "name": "idle-delay",
10678
+ "type": {
10679
+ "text": "number"
10680
+ },
10681
+ "default": "0",
10682
+ "description": "How long the reader may be still before the chrome hides, in milliseconds.\n\nZero keeps it up, which is the default: these controls are how a keyboard\nreader operates the surface, and chrome that hid itself by default would\nbe the component undoing its own argument. Set it for a display nobody\ndrives.",
10683
+ "fieldName": "idleDelay"
10684
+ },
10685
+ {
10686
+ "name": "pan-label",
10687
+ "type": {
10688
+ "text": "string"
10689
+ },
10690
+ "default": "'Pan {way}'",
10691
+ "description": "What one pan control is called, with `{way}` filled in.",
10692
+ "fieldName": "panLabel"
10693
+ },
10694
+ {
10695
+ "name": "pan-step",
10696
+ "type": {
10697
+ "text": "number"
10698
+ },
10699
+ "default": "0.25",
10700
+ "description": "How far one press moves the view, as a fraction of what is on screen.\n\nA fraction rather than a distance, because this surface does not know the\nscale of what is inside it: a quarter of the viewport is the same gesture\nat any zoom, and a consumer who wants their own units multiplies by them.",
10701
+ "fieldName": "panStep"
10702
+ },
10703
+ {
10704
+ "name": "zoom-in-label",
10705
+ "type": {
10706
+ "text": "string"
10707
+ },
10708
+ "default": "'Zoom in'",
10709
+ "description": "What the control that zooms in is called.",
10710
+ "fieldName": "zoomInLabel"
10711
+ },
10712
+ {
10713
+ "name": "zoom-out-label",
10714
+ "type": {
10715
+ "text": "string"
10716
+ },
10717
+ "default": "'Zoom out'",
10718
+ "description": "What the control that zooms out is called.",
10719
+ "fieldName": "zoomOutLabel"
10720
+ }
10721
+ ],
10722
+ "parameters": [
10723
+ {
10724
+ "name": "Base",
10725
+ "type": {
10726
+ "text": "T"
10727
+ }
10728
+ }
10729
+ ]
10730
+ }
10731
+ ],
10732
+ "exports": [
10733
+ {
10734
+ "kind": "js",
10735
+ "name": "ViewportElement",
10736
+ "declaration": {
10737
+ "name": "ViewportElement",
10738
+ "module": "src/mixins/mixin-viewport/mixin-viewport.ts"
10739
+ }
10740
+ },
10741
+ {
10742
+ "kind": "js",
10743
+ "name": "MixinViewport",
10744
+ "declaration": {
10745
+ "name": "MixinViewport",
10746
+ "module": "src/mixins/mixin-viewport/mixin-viewport.ts"
10747
+ }
10748
+ }
10749
+ ]
10750
+ },
10104
10751
  {
10105
10752
  "kind": "javascript-module",
10106
10753
  "path": "src/utils/anchor-element/anchor-element.ts",
@@ -70,18 +70,29 @@ export interface SkipDetail {
70
70
  /** Which way the reader asked to go. */
71
71
  to: 'next' | 'previous';
72
72
  }
73
- /** Detail carried by `helix-map-pan`. */
74
- export interface MapPanDetail {
73
+ /**
74
+ * Detail carried by a pan request, `helix-map-pan` being the one Map sends.
75
+ *
76
+ * Named for the surface rather than for the map, because the surface is shared:
77
+ * `MixinViewport` is what asks, and a canvas or a graph asking the same question
78
+ * should not be importing a type with `Map` in its name. `MapPanDetail` stays as
79
+ * an alias, that being what a consumer of Map wrote before this was split.
80
+ */
81
+ export interface ViewportPanDetail {
75
82
  /** Which way the reader asked to go, in reading order rather than compass order. */
76
83
  to: 'up' | 'down' | 'start' | 'end';
77
84
  /** How far, as a fraction of the viewport the map is drawn in. */
78
85
  by: number;
79
86
  }
80
- /** Detail carried by `helix-map-zoom`. */
81
- export interface MapZoomDetail {
87
+ /** Detail carried by a zoom request, named for the surface for the same reason. */
88
+ export interface ViewportZoomDetail {
82
89
  /** Which way, `1` in and `-1` out, so a consumer can multiply by their own step. */
83
90
  by: number;
84
91
  }
92
+ /** What `ViewportPanDetail` was called when only Map had one. */
93
+ export type MapPanDetail = ViewportPanDetail;
94
+ /** What `ViewportZoomDetail` was called when only Map had one. */
95
+ export type MapZoomDetail = ViewportZoomDetail;
85
96
  /** Detail carried by `helix-navigate`. */
86
97
  export interface NavigateDetail {
87
98
  /** Where the view is moving to, in whatever the component pages by. */
@@ -307,7 +318,7 @@ export declare class HelixActivateEvent extends CustomEvent<ChangeDetail> {
307
318
  * Not cancelable. Nothing here moves on its own, and an event whose
308
319
  * `preventDefault` means nothing is an event that lies about what it offers.
309
320
  */
310
- export declare class HelixMapPanEvent extends CustomEvent<MapPanDetail> {
321
+ export declare class HelixMapPanEvent extends CustomEvent<ViewportPanDetail> {
311
322
  static readonly eventName = "helix-map-pan";
312
323
  constructor(detail: MapPanDetail);
313
324
  }
@@ -319,7 +330,7 @@ export declare class HelixMapPanEvent extends CustomEvent<MapPanDetail> {
319
330
  * asserting a numbering that Leaflet, MapLibre and a static tile server do not
320
331
  * agree on.
321
332
  */
322
- export declare class HelixMapZoomEvent extends CustomEvent<MapZoomDetail> {
333
+ export declare class HelixMapZoomEvent extends CustomEvent<ViewportZoomDetail> {
323
334
  static readonly eventName = "helix-map-zoom";
324
335
  constructor(detail: MapZoomDetail);
325
336
  }
package/dist/index.d.ts CHANGED
@@ -19,7 +19,7 @@ export { SourceController, type SourceHost, } from './controllers/source-control
19
19
  export { type TokenColor, TokenColorController, } from './controllers/token-color-controller/token-color-controller.js';
20
20
  export { type Rendered, VirtualizeController, type VirtualizeOptions, type Window as VirtualizeWindow, } from './controllers/virtualize-controller/virtualize-controller.js';
21
21
  export { eventsForTag, registerElementEvents } from './events/element-events.js';
22
- export { type CaretDetail, type ChangeDetail, type CompleteDetail, type ConsoleDetail, type CopyDetail, type CreateDetail, type DismissDetail, type FacetDetail, type FilterDetail, HelixActivateEvent, HelixBeforeActivateEvent, HelixBeforeChangeEvent, HelixBeforeCloseEvent, HelixBeforeCollapseEvent, HelixBeforeEditEvent, HelixBeforeOpenEvent, HelixBeforePanelResizeEvent, HelixBeforePinEvent, HelixBeforeResizeEvent, HelixBeforeSplitEvent, HelixBeforeToggleEvent, HelixCaretEvent, HelixCompleteEvent, HelixConsoleEvent, HelixCopyEvent, HelixCreateEvent, HelixDismissEvent, HelixFacetEvent, HelixFilterEvent, HelixHistoryEvent, HelixMapPanEvent, HelixMapZoomEvent, HelixNavigateEvent, HelixNextEvent, HelixPreviousEvent, HelixReadEvent, HelixReorderEvent, HelixResizeEvent, HelixSampleEndEvent, HelixSampleStartEvent, HelixScrolledEvent, HelixSelectEvent, HelixSendEvent, HelixShortcutEvent, HelixSortEvent, HelixTourAskEvent, type MapPanDetail, type MapZoomDetail, type NavigateDetail, type OpenDetail, type ReadDetail, type ResizeDetail, type ScrolledDetail, type SelectDetail, type SendDetail, type ShortcutDetail, type SkipDetail, type SortDetail, type ToggleDetail, } from './events/events.js';
22
+ export { type CaretDetail, type ChangeDetail, type CompleteDetail, type ConsoleDetail, type CopyDetail, type CreateDetail, type DismissDetail, type FacetDetail, type FilterDetail, HelixActivateEvent, HelixBeforeActivateEvent, HelixBeforeChangeEvent, HelixBeforeCloseEvent, HelixBeforeCollapseEvent, HelixBeforeEditEvent, HelixBeforeOpenEvent, HelixBeforePanelResizeEvent, HelixBeforePinEvent, HelixBeforeResizeEvent, HelixBeforeSplitEvent, HelixBeforeToggleEvent, HelixCaretEvent, HelixCompleteEvent, HelixConsoleEvent, HelixCopyEvent, HelixCreateEvent, HelixDismissEvent, HelixFacetEvent, HelixFilterEvent, HelixHistoryEvent, HelixMapPanEvent, HelixMapZoomEvent, HelixNavigateEvent, HelixNextEvent, HelixPreviousEvent, HelixReadEvent, HelixReorderEvent, HelixResizeEvent, HelixSampleEndEvent, HelixSampleStartEvent, HelixScrolledEvent, HelixSelectEvent, HelixSendEvent, HelixShortcutEvent, HelixSortEvent, HelixTourAskEvent, type MapPanDetail, type MapZoomDetail, type NavigateDetail, type OpenDetail, type ReadDetail, type ResizeDetail, type ScrolledDetail, type SelectDetail, type SendDetail, type ShortcutDetail, type SkipDetail, type SortDetail, type ToggleDetail, type ViewportPanDetail, type ViewportZoomDetail, } from './events/events.js';
23
23
  export { HelixElement, whenHydrated } from './helix-element/helix-element.js';
24
24
  export { HelixFormElement } from './helix-form-element/helix-form-element.js';
25
25
  export { MixinChartSource, type MixinChartSourceInterface, PALETTE, PLOT_HEIGHT, PLOT_WIDTH, seriesIndex, seriesNames, } from './mixins/mixin-chart-source/mixin-chart-source.js';
@@ -34,6 +34,7 @@ export { MixinPickerEditing, type MixinPickerEditingInterface, } from './mixins/
34
34
  export { MixinPickerField, type MixinPickerFieldInterface, } from './mixins/mixin-picker-field/mixin-picker-field.js';
35
35
  export { MixinSelectableItem, type MixinSelectableItemInterface, } from './mixins/mixin-selectable-item/mixin-selectable-item.js';
36
36
  export { MixinTokens, type MixinTokensInterface } from './mixins/mixin-tokens/mixin-tokens.js';
37
+ export { MixinViewport, type ViewportControlsPlacement, type ViewportElement, type ViewportWay, } from './mixins/mixin-viewport/mixin-viewport.js';
37
38
  export { anchorElement } from './utils/anchor-element/anchor-element.js';
38
39
  export { capturePointer } from './utils/capture-pointer/capture-pointer.js';
39
40
  export { closestInstance } from './utils/closest-instance/closest-instance.js';
package/dist/index.js CHANGED
@@ -34,6 +34,7 @@ export { MixinPickerEditing, } from './mixins/mixin-picker-editing/mixin-picker-
34
34
  export { MixinPickerField, } from './mixins/mixin-picker-field/mixin-picker-field.js';
35
35
  export { MixinSelectableItem, } from './mixins/mixin-selectable-item/mixin-selectable-item.js';
36
36
  export { MixinTokens } from './mixins/mixin-tokens/mixin-tokens.js';
37
+ export { MixinViewport, } from './mixins/mixin-viewport/mixin-viewport.js';
37
38
  export { anchorElement } from './utils/anchor-element/anchor-element.js';
38
39
  export { capturePointer } from './utils/capture-pointer/capture-pointer.js';
39
40
  export { closestInstance } from './utils/closest-instance/closest-instance.js';
@@ -0,0 +1,85 @@
1
+ import { type LitElement, nothing, type TemplateResult } from 'lit';
2
+ import { FullscreenController } from '../../controllers/fullscreen-controller/fullscreen-controller.js';
3
+ import { IdleController } from '../../controllers/idle-controller/idle-controller.js';
4
+ import { type ViewportPanDetail, type ViewportZoomDetail } from '../../events/events.js';
5
+ type Constructor<T> = new (...args: any[]) => T;
6
+ /** Which way a press or a key asked the surface to go. */
7
+ export type ViewportWay = 'up' | 'down' | 'start' | 'end';
8
+ /** Where the controls sit, in the corner words the rest of the library uses. */
9
+ export type ViewportControlsPlacement = 'top-start' | 'top-end' | 'bottom-start' | 'bottom-end';
10
+ /**
11
+ * The API a pan-and-zoom surface has, as TypeScript sees it.
12
+ */
13
+ export declare class ViewportElement {
14
+ /** Which of the controls the surface draws, as a list of what to keep. */
15
+ controls?: string;
16
+ /** Which corner the controls sit in. */
17
+ controlsPlacement?: ViewportControlsPlacement;
18
+ /** What the control that fills the screen is called. */
19
+ fullscreenLabel: string;
20
+ /** How long the reader may be still before the chrome hides, in milliseconds. */
21
+ idleDelay: number;
22
+ /** What one pan control is called, with `{way}` filled in. */
23
+ panLabel: string;
24
+ /** How far one press moves the view, as a fraction of what is on screen. */
25
+ panStep: number;
26
+ /** What the control that zooms in is called. */
27
+ zoomInLabel: string;
28
+ /** What the control that zooms out is called. */
29
+ zoomOutLabel: string;
30
+ /** Asks for the view to move, which the consumer answers. */
31
+ pan(to: ViewportWay): void;
32
+ /** Asks for the view to zoom, `1` in and `-1` out. */
33
+ zoom(by: number): void;
34
+ /** Fills the screen with the surface, or comes back. */
35
+ toggleFullscreen(): void;
36
+ /** Whether the reader has been still long enough to hide the chrome. */
37
+ protected resting: boolean;
38
+ /** Whether the surface is filling the screen. */
39
+ protected filled: boolean;
40
+ /** How long the reader has been still, which the family's own frame stirs. */
41
+ protected readonly idle: IdleController;
42
+ /** Into the screen and back, for a family that offers its own way in. */
43
+ protected readonly fullscreen: FullscreenController;
44
+ /** The key relay, bound by the family's own frame. */
45
+ protected handleKeydown: (event: KeyboardEvent) => void;
46
+ /** What the group of controls is called, for a family that is not a map. */
47
+ protected get controlsLabel(): string;
48
+ /** The pan event, built and not dispatched, for a family that names its own. */
49
+ protected panEvent(detail: ViewportPanDetail): Event;
50
+ /** The zoom event, built and not dispatched, for a family that names its own. */
51
+ protected zoomEvent(detail: ViewportZoomDetail): Event;
52
+ /** The controls, or nothing where the surface keeps none of them. */
53
+ protected renderControls(): TemplateResult | typeof nothing;
54
+ }
55
+ /**
56
+ * Grafts a pan-and-zoom surface: its controls, its keys, and the way out.
57
+ *
58
+ * **Nothing here is geographic, which is why it is here.** `Map` was written
59
+ * first and its frame turned out to be a surface a reader can move, fill the
60
+ * screen with, and stop touching: a canvas of notes and a graph of nodes want
61
+ * the same four hundred lines, and the only thing that made them Map's was
62
+ * being written there. Extracted before the second consumer rather than after,
63
+ * which is the opposite of what [#383](queue.md) had to conclude about a
64
+ * controller promoted ahead of one.
65
+ *
66
+ * **Nothing here moves anything, either.** Both controls ask, through an event
67
+ * a family names, and the answer is the consumer's: the surface a reader is
68
+ * looking at belongs to them, whether it is a tile renderer, a canvas or a
69
+ * force layout. A mixin that moved the view would have to know what the view
70
+ * was.
71
+ *
72
+ * **The controls are buttons, and that is a decision against arrow keys.** They
73
+ * work too, but what a screen reader does with them is not ours to decide:
74
+ * macOS passes them through, Windows readers hold them for their own reading
75
+ * mode unless a page takes `role="application"`, which costs the reader their
76
+ * normal way of reading everything else. A button is findable and operable in
77
+ * every screen reader on every platform without that bargain.
78
+ *
79
+ * **What stays in the family is `render()`, its slots and its part
80
+ * documentation**, because a part named through an interpolation is one the
81
+ * manifest cannot see: a loop over the four pan buttons would publish `pan-`
82
+ * and a consumer's `::part(pan-up)` would match nothing.
83
+ */
84
+ export declare const MixinViewport: <T extends Constructor<LitElement>>(Base: T) => Constructor<ViewportElement> & T;
85
+ export {};
@@ -0,0 +1,168 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
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
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { html, nothing } from 'lit';
8
+ import { property, state } from 'lit/decorators.js';
9
+ import { FullscreenController } from '../../controllers/fullscreen-controller/fullscreen-controller.js';
10
+ import { IdleController } from '../../controllers/idle-controller/idle-controller.js';
11
+ import { HelixMapPanEvent, HelixMapZoomEvent, } from '../../events/events.js';
12
+ import { inlineStep } from '../../utils/inline-step/inline-step.js';
13
+ export const MixinViewport = (Base) => {
14
+ class Viewport extends Base {
15
+ constructor() {
16
+ super(...arguments);
17
+ this.fullscreenLabel = 'Fill the screen';
18
+ this.idleDelay = 0;
19
+ this.panLabel = 'Pan {way}';
20
+ this.panStep = 0.25;
21
+ this.zoomInLabel = 'Zoom in';
22
+ this.zoomOutLabel = 'Zoom out';
23
+ this.resting = false;
24
+ this.filled = false;
25
+ this.fullscreen = new FullscreenController(this, (full) => {
26
+ this.filled = full;
27
+ });
28
+ this.idle = new IdleController(this, () => this.idleDelay, (resting) => {
29
+ this.resting = resting;
30
+ });
31
+ this.handleKeydown = (event) => {
32
+ const target = event.target;
33
+ const own = target?.closest('[part="region"], [part~="controls"]');
34
+ if (!own || !this.renderRoot.contains(own)) {
35
+ return;
36
+ }
37
+ const along = inlineStep(this, event.key);
38
+ if (along === 1) {
39
+ event.preventDefault();
40
+ this.pan('end');
41
+ }
42
+ else if (along === -1) {
43
+ event.preventDefault();
44
+ this.pan('start');
45
+ }
46
+ else if (event.key === 'ArrowUp') {
47
+ event.preventDefault();
48
+ this.pan('up');
49
+ }
50
+ else if (event.key === 'ArrowDown') {
51
+ event.preventDefault();
52
+ this.pan('down');
53
+ }
54
+ else if (event.key === '+' || event.key === '=') {
55
+ event.preventDefault();
56
+ this.zoom(1);
57
+ }
58
+ else if (event.key === '-' || event.key === '_') {
59
+ event.preventDefault();
60
+ this.zoom(-1);
61
+ }
62
+ };
63
+ }
64
+ get controlsLabel() {
65
+ return 'View controls';
66
+ }
67
+ panEvent(detail) {
68
+ return new HelixMapPanEvent(detail);
69
+ }
70
+ zoomEvent(detail) {
71
+ return new HelixMapZoomEvent(detail);
72
+ }
73
+ pan(to) {
74
+ this.dispatchEvent(this.panEvent({ by: this.panStep, to }));
75
+ }
76
+ zoom(by) {
77
+ this.dispatchEvent(this.zoomEvent({ by }));
78
+ }
79
+ toggleFullscreen() {
80
+ void this.fullscreen.toggle();
81
+ }
82
+ draws(group) {
83
+ return this.controls === undefined ? true : this.controls.split(/\s+/).includes(group);
84
+ }
85
+ named(way) {
86
+ const spoken = way === 'start' ? 'left' : way === 'end' ? 'right' : way;
87
+ return this.panLabel.replace('{way}', spoken);
88
+ }
89
+ firstUpdated(changed) {
90
+ super.firstUpdated(changed);
91
+ this.idle.stir();
92
+ }
93
+ renderControls() {
94
+ const pan = this.draws('pan');
95
+ const zoom = this.draws('zoom');
96
+ const fullscreen = this.draws('fullscreen');
97
+ if (!pan && !zoom && !fullscreen) {
98
+ return nothing;
99
+ }
100
+ return html `<div part="controls" role="group" aria-label=${this.controlsLabel}>
101
+ ${pan
102
+ ? html `<button part="pan-up" aria-label=${this.named('up')} type="button" @click=${() => this.pan('up')}>
103
+ ↑
104
+ </button>
105
+ <button part="pan-start" aria-label=${this.named('start')} type="button" @click=${() => this.pan('start')}>
106
+ ←
107
+ </button>
108
+ <button part="pan-end" aria-label=${this.named('end')} type="button" @click=${() => this.pan('end')}>
109
+ →
110
+ </button>
111
+ <button part="pan-down" aria-label=${this.named('down')} type="button" @click=${() => this.pan('down')}>
112
+ ↓
113
+ </button>`
114
+ : nothing}
115
+ ${zoom
116
+ ? html `<button part="zoom-in" aria-label=${this.zoomInLabel} type="button" @click=${() => this.zoom(1)}>
117
+ +
118
+ </button>
119
+ <button part="zoom-out" aria-label=${this.zoomOutLabel} type="button" @click=${() => this.zoom(-1)}>
120
+ −
121
+ </button>`
122
+ : nothing}
123
+ ${fullscreen
124
+ ? html `<button
125
+ part="fullscreen"
126
+ aria-label=${this.fullscreenLabel}
127
+ aria-pressed=${this.filled ? 'true' : 'false'}
128
+ type="button"
129
+ @click=${this.toggleFullscreen}
130
+ >
131
+ ⤢
132
+ </button>`
133
+ : nothing}
134
+ </div>`;
135
+ }
136
+ }
137
+ __decorate([
138
+ property({ reflect: true })
139
+ ], Viewport.prototype, "controls", void 0);
140
+ __decorate([
141
+ property({ attribute: 'controls-placement', reflect: true })
142
+ ], Viewport.prototype, "controlsPlacement", void 0);
143
+ __decorate([
144
+ property({ attribute: 'fullscreen-label' })
145
+ ], Viewport.prototype, "fullscreenLabel", void 0);
146
+ __decorate([
147
+ property({ attribute: 'idle-delay', type: Number })
148
+ ], Viewport.prototype, "idleDelay", void 0);
149
+ __decorate([
150
+ property({ attribute: 'pan-label' })
151
+ ], Viewport.prototype, "panLabel", void 0);
152
+ __decorate([
153
+ property({ attribute: 'pan-step', type: Number })
154
+ ], Viewport.prototype, "panStep", void 0);
155
+ __decorate([
156
+ property({ attribute: 'zoom-in-label' })
157
+ ], Viewport.prototype, "zoomInLabel", void 0);
158
+ __decorate([
159
+ property({ attribute: 'zoom-out-label' })
160
+ ], Viewport.prototype, "zoomOutLabel", void 0);
161
+ __decorate([
162
+ state()
163
+ ], Viewport.prototype, "resting", void 0);
164
+ __decorate([
165
+ state()
166
+ ], Viewport.prototype, "filled", void 0);
167
+ return Viewport;
168
+ };
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.103.2",
4
+ "version": "0.103.3",
5
5
  "license": "MIT",
6
6
  "author": "Ross Sheppard <ross.sheppard@fusion.dev>",
7
7
  "repository": {
@@ -34,11 +34,11 @@
34
34
  "bugs": "https://github.com/project-fusion/fusion/issues",
35
35
  "dependencies": {
36
36
  "@floating-ui/dom": "^1.8.0",
37
- "@fusion.dev/helix-icons": "0.103.2"
37
+ "@fusion.dev/helix-icons": "0.103.3"
38
38
  },
39
39
  "devDependencies": {
40
- "@fusion.dev/helix-cem": "0.103.2",
41
- "@fusion.dev/helix-testing": "0.103.2",
40
+ "@fusion.dev/helix-cem": "0.103.3",
41
+ "@fusion.dev/helix-testing": "0.103.3",
42
42
  "@lit-labs/ssr": "^4.1.0",
43
43
  "lit": "^3.2.1"
44
44
  },