@fusion.dev/helix-core 0.176.0 → 0.177.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/custom-elements.json +307 -112
  2. package/dist/controllers/chart-pointer-controller/chart-pointer-controller.d.ts +6 -0
  3. package/dist/controllers/chart-pointer-controller/chart-pointer-controller.js +2 -1
  4. package/dist/controllers/source-controller/source-controller.js +6 -2
  5. package/dist/index.d.ts +1 -0
  6. package/dist/index.js +1 -0
  7. package/dist/mixins/mixin-chart-source/mixin-chart-source.d.ts +6 -0
  8. package/dist/mixins/mixin-chart-source/mixin-chart-source.js +14 -0
  9. package/dist/mixins/mixin-chart-status/mixin-chart-status.d.ts +21 -3
  10. package/dist/mixins/mixin-chart-status/mixin-chart-status.js +17 -0
  11. package/dist/utils/audit-nesting/audit-nesting-off.d.ts +10 -0
  12. package/dist/utils/audit-nesting/audit-nesting-off.js +1 -0
  13. package/dist/utils/audit-nesting/audit-nesting.d.ts +19 -0
  14. package/dist/utils/audit-nesting/audit-nesting.js +19 -0
  15. package/dist/utils/audit-source/audit-source-off.d.ts +3 -1
  16. package/dist/utils/audit-source/audit-source-off.js +1 -0
  17. package/dist/utils/audit-source/audit-source.d.ts +6 -0
  18. package/dist/utils/audit-source/audit-source.js +14 -0
  19. package/dist/utils/read-data/read-data.d.ts +6 -0
  20. package/dist/utils/read-data/read-data.js +8 -0
  21. package/dist/utils/report-runtime/report-runtime.d.ts +7 -0
  22. package/dist/version.d.ts +1 -1
  23. package/dist/version.js +1 -1
  24. package/dist/words/held.d.ts +55 -0
  25. package/dist/words/held.js +29 -0
  26. package/dist/words/registry.d.ts +1 -11
  27. package/dist/words/registry.js +5 -27
  28. package/dist/words/words-locale.js +4 -2
  29. package/dist/words/words.js +4 -4
  30. package/package.json +15 -4
@@ -12842,6 +12842,14 @@
12842
12842
  "package": "#audit-name"
12843
12843
  }
12844
12844
  },
12845
+ {
12846
+ "kind": "js",
12847
+ "name": "auditNesting",
12848
+ "declaration": {
12849
+ "name": "auditNesting",
12850
+ "package": "#audit-nesting"
12851
+ }
12852
+ },
12845
12853
  {
12846
12854
  "kind": "js",
12847
12855
  "name": "auditServed",
@@ -18135,6 +18143,17 @@
18135
18143
  "text": "ChartPointerController"
18136
18144
  },
18137
18145
  "description": "Finds the reading nearest the pointer, so the tooltip can sit on it."
18146
+ },
18147
+ {
18148
+ "kind": "method",
18149
+ "name": "renderTable",
18150
+ "privacy": "protected",
18151
+ "return": {
18152
+ "type": {
18153
+ "text": "TemplateResult | typeof nothing"
18154
+ }
18155
+ },
18156
+ "description": "The readings as a table, for a screen reader, where they were drawn from\n`data`: there is then no table on the page to read. Nothing otherwise."
18138
18157
  }
18139
18158
  ],
18140
18159
  "helix": {}
@@ -18171,13 +18190,6 @@
18171
18190
  "default": "new SourceController(this)",
18172
18191
  "description": "Resolves `for` and re-renders when the table behind it changes."
18173
18192
  },
18174
- {
18175
- "kind": "field",
18176
- "name": "pointer",
18177
- "readonly": true,
18178
- "default": "new ChartPointerController(this)",
18179
- "description": "Finds the reading nearest the pointer, so the tooltip can sit on it.\n\nPublic for the same reason `series` and `named` are: a protected member\ncannot be cast onto across unrelated classes, so it would be invisible to\nevery chart that needs it. Reading where a pointer is resting is a fair\nthing to ask a chart anyway."
18180
- },
18181
18193
  {
18182
18194
  "kind": "field",
18183
18195
  "name": "data",
@@ -18230,46 +18242,6 @@
18230
18242
  "words": true
18231
18243
  }
18232
18244
  },
18233
- {
18234
- "kind": "field",
18235
- "name": "observedAttributes",
18236
- "type": {
18237
- "text": "string[]"
18238
- },
18239
- "static": true,
18240
- "description": "Hears an `aria-label` written on the chart, which no property mirrors,\nso a page that names the chart that way names its plot.\n\nLit observes the attributes of its own properties only, and `ariaLabel`\nbelongs to the platform.\n\nRead through `Reflect.get` with this class as the receiver, which is\nwhat `super.observedAttributes` means: Lit's getter finalizes whichever\nclass it is asked on, and asked on the base it would finalize the base\nand leave a subclass without its styles. A mixin's base has no static\nside TypeScript can see, so `super` cannot be written here.",
18241
- "readonly": true
18242
- },
18243
- {
18244
- "kind": "method",
18245
- "name": "attributeChangedCallback",
18246
- "return": {
18247
- "type": {
18248
- "text": "void"
18249
- }
18250
- },
18251
- "parameters": [
18252
- {
18253
- "name": "name",
18254
- "type": {
18255
- "text": "string"
18256
- }
18257
- },
18258
- {
18259
- "name": "old",
18260
- "type": {
18261
- "text": "string | null"
18262
- }
18263
- },
18264
- {
18265
- "name": "value",
18266
- "type": {
18267
- "text": "string | null"
18268
- }
18269
- }
18270
- ],
18271
- "description": "Called by the browser when an observed attribute changes. A new\n`aria-label` asks for an update, because it names the plot."
18272
- },
18273
18245
  {
18274
18246
  "kind": "field",
18275
18247
  "name": "series",
@@ -18288,24 +18260,6 @@
18288
18260
  "description": "The chart's name: an `aria-label` written on the chart, then `label`,\nthen the table's caption.\n\nThe fallback is the reason this is shared rather than copied: a caption is\nalready written, already the right words, and already the thing a reader\nof the table sees. A chart that made the consumer say it twice would be\nasking for the second one to drift.\n\n**An `aria-label` on the chart comes first**, because it is how a page\nnames an element, and the chart is a generic around its plot: written\non the host, the name reached nobody. The plot takes it, as Heatmap's\nstrip always did.",
18289
18261
  "readonly": true
18290
18262
  },
18291
- {
18292
- "kind": "field",
18293
- "name": "readingsKnown",
18294
- "type": {
18295
- "text": "boolean"
18296
- },
18297
- "description": "Whether the readings have been looked for: false on the server and\nwhile hydrating, where no table is resolved and no data is read. A\nchart says it has no readings only once this is true.",
18298
- "readonly": true
18299
- },
18300
- {
18301
- "kind": "field",
18302
- "name": "sourceRows",
18303
- "type": {
18304
- "text": "Row[]"
18305
- },
18306
- "description": "Every row of the table, with each cell's text and number.\n\nThis is where a tooltip's content comes from, and it is deliberately not\n`series`. A chart draws numbers; a reader resting a pointer on one wants\nwhat the table says, which is `$128k` and not `128000`, and often wants\nthe column beside it that nothing plotted at all.\n\n**Not `rows`.** It is the source table's rows, and `rows` on a chart is a\nname a chart wants for itself: `helix-heatmap` takes `rows=\"7\"` to mean a\ncalendar week, and declaring that against a base class whose `rows` is a\n`Row[]` getter is a type error at best. Name the member for what it holds\nrather than for the shortest word available.",
18307
- "readonly": true
18308
- },
18309
18263
  {
18310
18264
  "kind": "field",
18311
18265
  "name": "plottedColumns",
@@ -18395,6 +18349,17 @@
18395
18349
  },
18396
18350
  "description": "The number of readings, which the pointer position maps onto.",
18397
18351
  "readonly": true
18352
+ },
18353
+ {
18354
+ "kind": "method",
18355
+ "name": "renderTable",
18356
+ "privacy": "protected",
18357
+ "return": {
18358
+ "type": {
18359
+ "text": "TemplateResult | typeof nothing"
18360
+ }
18361
+ },
18362
+ "description": "The readings as a table, for a screen reader, where they were drawn from\n`data`: there is then no table on the page to read, and a chart's\nsummary gives only what the readings come to. Nothing where a table on\nthe page was read, which is already the readable version.\n\nThe `table` part, named by the chart, its cells as `data` writes them."
18398
18363
  }
18399
18364
  ],
18400
18365
  "attributes": [
@@ -18555,13 +18520,24 @@
18555
18520
  }
18556
18521
  },
18557
18522
  "description": "What stands in the plot while there is nothing to draw: the `loading`\nline, the `empty` line, or nothing while the table cannot be read yet."
18523
+ },
18524
+ {
18525
+ "kind": "method",
18526
+ "name": "renderEmpty",
18527
+ "privacy": "protected",
18528
+ "return": {
18529
+ "type": {
18530
+ "text": "TemplateResult"
18531
+ }
18532
+ },
18533
+ "description": "The plot while there is nothing to draw: a group named as the chart is,\nholding `renderStatus`."
18558
18534
  }
18559
18535
  ],
18560
18536
  "helix": {}
18561
18537
  },
18562
18538
  {
18563
18539
  "kind": "mixin",
18564
- "description": "What a chart shows in place of its drawing when it has no readings: words\nwhile they load, and words when the table it reads has none.\n\n**A blank box said neither.** A chart pointed at an empty table drew its\nplot at full height with nothing in it, which reads as a chart that failed,\nand one waiting for a request drew the same. `Table` and `Data Table` take\nthe same pair, `empty-text` and `loading-text` with `empty` and `loading`\nslots for markup, such as a link to add the first reading.\n\n**Nothing at all while the readings cannot be looked for**, which is a\nserver render and the hydrating one after it: there is no document to\nresolve `for` in and `data` is drawn on the client only, so \"No readings\"\nthere would be a claim about readings nobody has looked at. `Heatmap` keeps\nthe same rule for its own `empty-text`.\n\nApplied by the four charts that draw on axes. The slots and parts are\ndocumented on each of them, a class-level tag on a mixin reaching no\nmanifest.",
18540
+ "description": "What a chart shows in place of its drawing when it has no readings: words\nwhile they load, and words when the table it reads has none.\n\n**A blank box said neither.** A chart pointed at an empty table drew its\nplot at full height with nothing in it, which reads as a chart that failed,\nand one waiting for a request drew the same. `Table` and `Data Table` take\nthe same pair, `empty-text` and `loading-text` with `empty` and `loading`\nslots for markup, such as a link to add the first reading.\n\n**Nothing at all while the readings cannot be looked for**, which is a\nserver render and the hydrating one after it: there is no document to\nresolve `for` in and `data` is drawn on the client only, so \"No readings\"\nthere would be a claim about readings nobody has looked at. `Heatmap` keeps\nthe same rule for its own `empty-text`.\n\n**The plot keeps the chart's name while it has nothing to draw**, as a\ngroup around the words, so the chart is still found by its name.\n\n**Three custom states follow:** `active` while a reading is shown, under the\npointer or the keys, with its tooltip open; `empty` while the empty words\nstand in the plot; and `loading` while the loading words do.\n\nApplied by the four charts that draw on axes and by Donut Chart. The slots,\nparts and states are documented on each of them, a class-level tag on a\nmixin reaching no manifest.",
18565
18541
  "name": "MixinChartStatus",
18566
18542
  "members": [
18567
18543
  {
@@ -18621,6 +18597,17 @@
18621
18597
  }
18622
18598
  },
18623
18599
  "description": "What stands in the plot while there is nothing to draw: the `loading`\nline, the `empty` line, or nothing while the table cannot be read yet.\n\n`dir=\"auto\"`, so words in a right-to-left script read their own way in\na plot that holds itself left to right, as Scatter Chart's does."
18600
+ },
18601
+ {
18602
+ "kind": "method",
18603
+ "name": "renderEmpty",
18604
+ "privacy": "protected",
18605
+ "return": {
18606
+ "type": {
18607
+ "text": "TemplateResult"
18608
+ }
18609
+ },
18610
+ "description": "The plot while there is nothing to draw: a group named as the chart is,\nholding `renderStatus`. Not focusable, there being no reading to walk."
18624
18611
  }
18625
18612
  ],
18626
18613
  "attributes": [
@@ -24659,6 +24646,70 @@
24659
24646
  }
24660
24647
  ]
24661
24648
  },
24649
+ {
24650
+ "kind": "javascript-module",
24651
+ "path": "src/utils/audit-nesting/audit-nesting-off.ts",
24652
+ "declarations": [
24653
+ {
24654
+ "kind": "function",
24655
+ "name": "auditNesting",
24656
+ "description": "The unslotted-item warning in a production build: nothing.\n\n`#audit-nesting` resolves here unless the bundler or runtime asks for the\n`development` condition, so a production build carries none of the audit's\ncode. The signature is the audit's, so a call site reads the same in both\nbuilds. See `audit-nesting.ts`.",
24657
+ "type": {
24658
+ "text": "typeof audit"
24659
+ }
24660
+ }
24661
+ ],
24662
+ "exports": [
24663
+ {
24664
+ "kind": "js",
24665
+ "name": "auditNesting",
24666
+ "declaration": {
24667
+ "name": "auditNesting",
24668
+ "module": "src/utils/audit-nesting/audit-nesting-off.ts"
24669
+ }
24670
+ }
24671
+ ]
24672
+ },
24673
+ {
24674
+ "kind": "javascript-module",
24675
+ "path": "src/utils/audit-nesting/audit-nesting.ts",
24676
+ "declarations": [
24677
+ {
24678
+ "kind": "function",
24679
+ "name": "auditNesting",
24680
+ "return": {
24681
+ "type": {
24682
+ "text": "void"
24683
+ }
24684
+ },
24685
+ "parameters": [
24686
+ {
24687
+ "name": "host",
24688
+ "type": {
24689
+ "text": "HTMLElement"
24690
+ }
24691
+ },
24692
+ {
24693
+ "name": "slot",
24694
+ "type": {
24695
+ "text": "string"
24696
+ }
24697
+ }
24698
+ ],
24699
+ "description": "Reports an item nested in another item of its own tag without the slot that\nnests it, the one mistake that leaves a tree drawing the child as words.\n\n`<helix-tree-item>src<helix-tree-item>app.ts</helix-tree-item></helix-tree-item>`\nputs the inner item in the outer one's default slot, which is its label: the\nrow reads \"src app.ts\" and is drawn as a leaf. The item has to carry\n`slot=\"children\"`. **Nothing moves it there**, because writing `slot` onto\nan element the page rendered is a change to markup a server sent and a\nframework owns.\n\n**In a development build only**, as `missing-name` is: every item would pay\nfor a sentence meant for the person writing the page. Core imports it as\n`#audit-nesting`, which `package.json` resolves here under the `development`\ncondition and to `audit-nesting-off.ts`, which does nothing, everywhere else.\n\n`slot` is the name of the slot that nests an item."
24700
+ }
24701
+ ],
24702
+ "exports": [
24703
+ {
24704
+ "kind": "js",
24705
+ "name": "auditNesting",
24706
+ "declaration": {
24707
+ "name": "auditNesting",
24708
+ "module": "src/utils/audit-nesting/audit-nesting.ts"
24709
+ }
24710
+ }
24711
+ ]
24712
+ },
24662
24713
  {
24663
24714
  "kind": "javascript-module",
24664
24715
  "path": "src/utils/audit-served/audit-served-off.ts",
@@ -24746,6 +24797,14 @@
24746
24797
  "type": {
24747
24798
  "text": "typeof audit"
24748
24799
  }
24800
+ },
24801
+ {
24802
+ "kind": "function",
24803
+ "name": "auditData",
24804
+ "description": "The malformed-data warning in a production build: nothing.",
24805
+ "type": {
24806
+ "text": "typeof auditShape"
24807
+ }
24749
24808
  }
24750
24809
  ],
24751
24810
  "exports": [
@@ -24756,6 +24815,14 @@
24756
24815
  "name": "auditSource",
24757
24816
  "module": "src/utils/audit-source/audit-source-off.ts"
24758
24817
  }
24818
+ },
24819
+ {
24820
+ "kind": "js",
24821
+ "name": "auditData",
24822
+ "declaration": {
24823
+ "name": "auditData",
24824
+ "module": "src/utils/audit-source/audit-source-off.ts"
24825
+ }
24759
24826
  }
24760
24827
  ]
24761
24828
  },
@@ -24786,6 +24853,24 @@
24786
24853
  }
24787
24854
  ],
24788
24855
  "description": "Reports a chart, a legend or a sparkline whose `for` names no table once the\npage has loaded, the one mistake that leaves it drawing nothing and saying\nnothing.\n\n**Read a task after `load`**, so a table later in the markup, or one a\nscript writes as the page starts, counts. A table a framework renders later\nthan that is reported, and the chart still draws it when it arrives.\n\n**In a development build only**, as `missing-name` is: every chart would pay\nfor a sentence meant for the person writing the page. Core imports it as\n`#audit-source`, which `package.json` resolves here under the `development`\ncondition and to `audit-source-off.ts`, which does nothing, everywhere else.\n\n`table` answers what `for` resolves to now: the table, or null."
24856
+ },
24857
+ {
24858
+ "kind": "function",
24859
+ "name": "auditData",
24860
+ "return": {
24861
+ "type": {
24862
+ "text": "void"
24863
+ }
24864
+ },
24865
+ "parameters": [
24866
+ {
24867
+ "name": "host",
24868
+ "type": {
24869
+ "text": "HTMLElement"
24870
+ }
24871
+ }
24872
+ ],
24873
+ "description": "Reports a chart whose `data` is not `{ columns, rows }`, such as an array of\nrows, once for each chart. The chart draws its empty state. In a development\nbuild only, as the missing-table warning is."
24789
24874
  }
24790
24875
  ],
24791
24876
  "exports": [
@@ -24796,6 +24881,14 @@
24796
24881
  "name": "auditSource",
24797
24882
  "module": "src/utils/audit-source/audit-source.ts"
24798
24883
  }
24884
+ },
24885
+ {
24886
+ "kind": "js",
24887
+ "name": "auditData",
24888
+ "declaration": {
24889
+ "name": "auditData",
24890
+ "module": "src/utils/audit-source/audit-source.ts"
24891
+ }
24799
24892
  }
24800
24893
  ]
24801
24894
  },
@@ -29913,6 +30006,24 @@
29913
30006
  "kind": "javascript-module",
29914
30007
  "path": "src/utils/read-data/read-data.ts",
29915
30008
  "declarations": [
30009
+ {
30010
+ "kind": "function",
30011
+ "name": "isChartData",
30012
+ "return": {
30013
+ "type": {
30014
+ "text": "data is ChartData"
30015
+ }
30016
+ },
30017
+ "parameters": [
30018
+ {
30019
+ "name": "data",
30020
+ "type": {
30021
+ "text": "unknown"
30022
+ }
30023
+ }
30024
+ ],
30025
+ "description": "Whether `data` has the shape a chart reads: an object with an array of\n`columns` and an array of `rows`, each row an array. Any other value draws\nthe empty state, where it would throw from the render."
30026
+ },
29916
30027
  {
29917
30028
  "kind": "function",
29918
30029
  "name": "seriesFromData",
@@ -29959,6 +30070,14 @@
29959
30070
  }
29960
30071
  ],
29961
30072
  "exports": [
30073
+ {
30074
+ "kind": "js",
30075
+ "name": "isChartData",
30076
+ "declaration": {
30077
+ "name": "isChartData",
30078
+ "module": "src/utils/read-data/read-data.ts"
30079
+ }
30080
+ },
29962
30081
  {
29963
30082
  "kind": "js",
29964
30083
  "name": "seriesFromData",
@@ -33810,7 +33929,7 @@
33810
33929
  "type": {
33811
33930
  "text": "string"
33812
33931
  },
33813
- "default": "'0.176.0'",
33932
+ "default": "'0.177.0'",
33814
33933
  "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."
33815
33934
  }
33816
33935
  ],
@@ -33825,6 +33944,110 @@
33825
33944
  }
33826
33945
  ]
33827
33946
  },
33947
+ {
33948
+ "kind": "javascript-module",
33949
+ "path": "src/words/held.ts",
33950
+ "declarations": [
33951
+ {
33952
+ "kind": "variable",
33953
+ "name": "held",
33954
+ "type": {
33955
+ "text": "Held"
33956
+ },
33957
+ "description": "The page's words, held on `globalThis`.\n\n**One registry for a page that carries two copies of this module**, one per\nbundle that imports it: words registered through one have to reach elements\ndefined through the other, as the icon registry's glyphs do.\n\n**What every element reads, and nothing more.** An element with a word\nproperty reads its English until a page registers words, so the lookup and\nthe locale chain arrive with `registerWords`, which puts its lookup in\n`find`, and the locale with `wordsLocale`, which puts itself in `locale`. A\npage that registers no words and formats no numbers ships neither: measured\non Button, 474 bytes of its 13,437 compressed."
33958
+ },
33959
+ {
33960
+ "kind": "function",
33961
+ "name": "changed",
33962
+ "return": {
33963
+ "type": {
33964
+ "text": "void"
33965
+ }
33966
+ },
33967
+ "description": "Asks every element that reads words to look again, once per task."
33968
+ },
33969
+ {
33970
+ "kind": "function",
33971
+ "name": "wordsRegistered",
33972
+ "return": {
33973
+ "type": {
33974
+ "text": "boolean"
33975
+ }
33976
+ },
33977
+ "description": "Whether any words are registered, for a read that can skip the lookup."
33978
+ },
33979
+ {
33980
+ "kind": "function",
33981
+ "name": "watchLang",
33982
+ "return": {
33983
+ "type": {
33984
+ "text": "void"
33985
+ }
33986
+ },
33987
+ "description": "Starts watching the document's `lang`, once for the page, in a browser.\nCalled by what a `lang` change reaches: registered words, and a locale read."
33988
+ },
33989
+ {
33990
+ "kind": "function",
33991
+ "name": "watchWords",
33992
+ "return": {
33993
+ "type": {
33994
+ "text": "(() => void)"
33995
+ }
33996
+ },
33997
+ "parameters": [
33998
+ {
33999
+ "name": "watcher",
34000
+ "type": {
34001
+ "text": "() => void"
34002
+ }
34003
+ }
34004
+ ],
34005
+ "description": "Calls `watcher` once in the microtask after words are registered or\ncleared, or the document's `lang` changes. Returns the function that stops\nit."
34006
+ }
34007
+ ],
34008
+ "exports": [
34009
+ {
34010
+ "kind": "js",
34011
+ "name": "held",
34012
+ "declaration": {
34013
+ "name": "held",
34014
+ "module": "src/words/held.ts"
34015
+ }
34016
+ },
34017
+ {
34018
+ "kind": "js",
34019
+ "name": "changed",
34020
+ "declaration": {
34021
+ "name": "changed",
34022
+ "module": "src/words/held.ts"
34023
+ }
34024
+ },
34025
+ {
34026
+ "kind": "js",
34027
+ "name": "wordsRegistered",
34028
+ "declaration": {
34029
+ "name": "wordsRegistered",
34030
+ "module": "src/words/held.ts"
34031
+ }
34032
+ },
34033
+ {
34034
+ "kind": "js",
34035
+ "name": "watchLang",
34036
+ "declaration": {
34037
+ "name": "watchLang",
34038
+ "module": "src/words/held.ts"
34039
+ }
34040
+ },
34041
+ {
34042
+ "kind": "js",
34043
+ "name": "watchWords",
34044
+ "declaration": {
34045
+ "name": "watchWords",
34046
+ "module": "src/words/held.ts"
34047
+ }
34048
+ }
34049
+ ]
34050
+ },
33828
34051
  {
33829
34052
  "kind": "javascript-module",
33830
34053
  "path": "src/words/index.ts",
@@ -34094,16 +34317,6 @@
34094
34317
  ],
34095
34318
  "description": "The locales a word is looked for in, most specific first, ending in `en`.\n\n**The script comes before the bare language**, so Hong Kong (`zh-HK`)\nreaches a Traditional Chinese pack (`zh-Hant`) before a `zh` pack, which is\nusually Simplified. Unicode extensions are dropped, and a tag `Intl` refuses\nfalls straight to `en`.\n\n`de-CH` tries `de-ch`, `de-latn-ch`, `de-latn`, `de`, then `en`."
34096
34319
  },
34097
- {
34098
- "kind": "function",
34099
- "name": "wordsRegistered",
34100
- "return": {
34101
- "type": {
34102
- "text": "boolean"
34103
- }
34104
- },
34105
- "description": "Whether any words are registered, for a read that can skip the lookup."
34106
- },
34107
34320
  {
34108
34321
  "kind": "function",
34109
34322
  "name": "lookupWord",
@@ -34133,27 +34346,25 @@
34133
34346
  }
34134
34347
  ],
34135
34348
  "description": "A registered word for a tag's property in a locale, with the locale it was\nfound in, or `undefined`.\n\n**Per key, along the chain.** A `de-CH` pack that holds only the Swiss\nspellings falls through to `de` for every other word. A word table merges\nalong the chain, the more specific entry winning."
34136
- },
34137
- {
34138
- "kind": "function",
34139
- "name": "watchWords",
34140
- "return": {
34141
- "type": {
34142
- "text": "(() => void)"
34143
- }
34144
- },
34145
- "parameters": [
34146
- {
34147
- "name": "watcher",
34148
- "type": {
34149
- "text": "() => void"
34150
- }
34151
- }
34152
- ],
34153
- "description": "Calls `watcher` once in the microtask after words are registered or\ncleared, or the document's `lang` changes. Returns the function that stops\nit.\n\nThe first watcher in a browser starts watching the document's `lang`, once\nfor the page."
34154
34349
  }
34155
34350
  ],
34156
34351
  "exports": [
34352
+ {
34353
+ "kind": "js",
34354
+ "name": "watchWords",
34355
+ "declaration": {
34356
+ "name": "watchWords",
34357
+ "module": "./held.js"
34358
+ }
34359
+ },
34360
+ {
34361
+ "kind": "js",
34362
+ "name": "wordsRegistered",
34363
+ "declaration": {
34364
+ "name": "wordsRegistered",
34365
+ "module": "./held.js"
34366
+ }
34367
+ },
34157
34368
  {
34158
34369
  "kind": "js",
34159
34370
  "name": "registerWords",
@@ -34202,14 +34413,6 @@
34202
34413
  "module": "src/words/registry.ts"
34203
34414
  }
34204
34415
  },
34205
- {
34206
- "kind": "js",
34207
- "name": "wordsRegistered",
34208
- "declaration": {
34209
- "name": "wordsRegistered",
34210
- "module": "src/words/registry.ts"
34211
- }
34212
- },
34213
34416
  {
34214
34417
  "kind": "js",
34215
34418
  "name": "lookupWord",
@@ -34217,14 +34420,6 @@
34217
34420
  "name": "lookupWord",
34218
34421
  "module": "src/words/registry.ts"
34219
34422
  }
34220
- },
34221
- {
34222
- "kind": "js",
34223
- "name": "watchWords",
34224
- "declaration": {
34225
- "name": "watchWords",
34226
- "module": "src/words/registry.ts"
34227
- }
34228
34423
  }
34229
34424
  ]
34230
34425
  },
@@ -34370,6 +34565,6 @@
34370
34565
  ],
34371
34566
  "helix": {
34372
34567
  "package": "@fusion.dev/helix-core",
34373
- "version": "0.176.0"
34568
+ "version": "0.177.0"
34374
34569
  }
34375
34570
  }
@@ -50,6 +50,12 @@ export interface ChartPointerHost extends ReactiveControllerHost, HTMLElement {
50
50
  * The keys pass over a hidden reading, as they pass over a hidden series.
51
51
  */
52
52
  showsReading?(at: number): boolean;
53
+ /**
54
+ * The readings in the order the keys walk them, by place, for a chart whose
55
+ * drawing does not follow the table: a scatter's points from left to right.
56
+ * Absent, the keys walk the table's order.
57
+ */
58
+ readingOrder?(): number[];
53
59
  /**
54
60
  * Where PageUp or PageDown goes from the reading at `at`, by place among the
55
61
  * readings, for a chart whose page is no fixed count: a calendar's month.
@@ -58,6 +58,7 @@ export class ChartPointerController {
58
58
  if (this.at >= 0) {
59
59
  event.preventDefault();
60
60
  this.clear();
61
+ this.by = 'keyboard';
61
62
  }
62
63
  return;
63
64
  }
@@ -206,7 +207,7 @@ export class ChartPointerController {
206
207
  }
207
208
  shown() {
208
209
  const host = this.host;
209
- const all = Array.from({ length: host.readings }, (_, at) => at);
210
+ const all = host.readingOrder?.() ?? Array.from({ length: host.readings }, (_, at) => at);
210
211
  return host.showsReading === undefined
211
212
  ? all
212
213
  : all.filter((at) => host.showsReading?.(at) !== false);
@@ -1,6 +1,6 @@
1
- import { auditSource } from '@fusion.dev/helix-core/audits/source';
1
+ import { auditData, auditSource } from '@fusion.dev/helix-core/audits/source';
2
2
  import { elementById, searchableById } from '../../utils/element-by-id/element-by-id.js';
3
- import { rowsFromData, seriesFromData } from '../../utils/read-data/read-data.js';
3
+ import { isChartData, rowsFromData, seriesFromData, } from '../../utils/read-data/read-data.js';
4
4
  import { readRows, readSeries, seriesKey, } from '../../utils/read-series/read-series.js';
5
5
  import { wordsLocale } from '../../words/words-locale.js';
6
6
  const HIDDEN = new WeakMap();
@@ -29,6 +29,10 @@ export class SourceController {
29
29
  if (host.data === undefined || typeof host.closest !== 'function' || this.hydrating) {
30
30
  return undefined;
31
31
  }
32
+ if (!isChartData(host.data)) {
33
+ auditData(host);
34
+ return undefined;
35
+ }
32
36
  return host.data;
33
37
  }
34
38
  get known() {
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { auditName } from '@fusion.dev/helix-core/audits/name';
2
+ export { auditNesting } from '@fusion.dev/helix-core/audits/nesting';
2
3
  export { auditServed } from '@fusion.dev/helix-core/audits/served';
3
4
  export { AriaForwardController, type AriaForwardOptions, } from './controllers/aria-forward-controller/aria-forward-controller.js';
4
5
  export { ArrivalController, type ArrivalOptions, } from './controllers/arrival-controller/arrival-controller.js';
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export { auditName } from '@fusion.dev/helix-core/audits/name';
2
+ export { auditNesting } from '@fusion.dev/helix-core/audits/nesting';
2
3
  export { auditServed } from '@fusion.dev/helix-core/audits/served';
3
4
  export { AriaForwardController, } from './controllers/aria-forward-controller/aria-forward-controller.js';
4
5
  export { ArrivalController, } from './controllers/arrival-controller/arrival-controller.js';
@@ -1,3 +1,4 @@
1
+ import { nothing, type TemplateResult } from 'lit';
1
2
  import { ChartPointerController } from '../../controllers/chart-pointer-controller/chart-pointer-controller.js';
2
3
  import type { HelixElement } from '../../helix-element/helix-element.js';
3
4
  import type { ChartData } from '../../utils/read-data/read-data.js';
@@ -202,6 +203,11 @@ export declare class MixinChartSourceInterface {
202
203
  * Finds the reading nearest the pointer, so the tooltip can sit on it.
203
204
  */
204
205
  pointer: ChartPointerController;
206
+ /**
207
+ * The readings as a table, for a screen reader, where they were drawn from
208
+ * `data`: there is then no table on the page to read. Nothing otherwise.
209
+ */
210
+ protected renderTable(): TemplateResult | typeof nothing;
205
211
  }
206
212
  /**
207
213
  * The words a chart says a reading's place in by default, such as "2 of 12":
@@ -4,6 +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 { html, nothing } from 'lit';
7
8
  import { property } from 'lit/decorators.js';
8
9
  import { ChartPointerController } from '../../controllers/chart-pointer-controller/chart-pointer-controller.js';
9
10
  import { SourceController } from '../../controllers/source-controller/source-controller.js';
@@ -84,6 +85,19 @@ export const MixinChartSource = (Base) => {
84
85
  get readings() {
85
86
  return this.sourceRows.length;
86
87
  }
88
+ renderTable() {
89
+ const rows = this.sourceRows;
90
+ if (this.source.element || rows.length === 0) {
91
+ return nothing;
92
+ }
93
+ const width = Math.max(...rows.map((row) => row.cells.length));
94
+ const columns = Array.from({ length: width }, (_, cell) => this.columnName(cell));
95
+ return html `<table part="table">
96
+ ${this.named ? html `<caption>${this.named}</caption>` : nothing}
97
+ <thead><tr><th scope="col">${this.data?.columns[0] ?? ''}</th>${columns.map((name) => html `<th scope="col">${name}</th>`)}</tr></thead>
98
+ <tbody>${rows.map((row) => html `<tr><th scope="row">${row.label}</th>${columns.map((_, cell) => html `<td>${row.cells[cell]?.text ?? ''}</td>`)}</tr>`)}</tbody>
99
+ </table>`;
100
+ }
87
101
  }
88
102
  __decorate([
89
103
  property({ attribute: false })
@@ -5,6 +5,12 @@ type Constructor<T> = new (...args: any[]) => T;
5
5
  interface ChartHost extends HelixElement {
6
6
  /** Whether the readings have been looked for, which a server never does. */
7
7
  readonly readingsKnown: boolean;
8
+ /** The chart's name, which the plot keeps while it has nothing to draw. */
9
+ readonly named: string;
10
+ /** The reading shown, by its place, or -1 for none. */
11
+ readonly pointer: {
12
+ readonly at: number;
13
+ };
8
14
  }
9
15
  /**
10
16
  * The public surface `MixinChartStatus` adds.
@@ -34,6 +40,11 @@ export declare class MixinChartStatusInterface {
34
40
  * line, the `empty` line, or nothing while the table cannot be read yet.
35
41
  */
36
42
  protected renderStatus(): TemplateResult | typeof nothing;
43
+ /**
44
+ * The plot while there is nothing to draw: a group named as the chart is,
45
+ * holding `renderStatus`.
46
+ */
47
+ protected renderEmpty(): TemplateResult;
37
48
  }
38
49
  /**
39
50
  * What a chart shows in place of its drawing when it has no readings: words
@@ -51,9 +62,16 @@ export declare class MixinChartStatusInterface {
51
62
  * there would be a claim about readings nobody has looked at. `Heatmap` keeps
52
63
  * the same rule for its own `empty-text`.
53
64
  *
54
- * Applied by the four charts that draw on axes. The slots and parts are
55
- * documented on each of them, a class-level tag on a mixin reaching no
56
- * manifest.
65
+ * **The plot keeps the chart's name while it has nothing to draw**, as a
66
+ * group around the words, so the chart is still found by its name.
67
+ *
68
+ * **Three custom states follow:** `active` while a reading is shown, under the
69
+ * pointer or the keys, with its tooltip open; `empty` while the empty words
70
+ * stand in the plot; and `loading` while the loading words do.
71
+ *
72
+ * Applied by the four charts that draw on axes and by Donut Chart. The slots,
73
+ * parts and states are documented on each of them, a class-level tag on a
74
+ * mixin reaching no manifest.
57
75
  */
58
76
  export declare const MixinChartStatus: <T extends Constructor<ChartHost>>(Base: T) => T & Constructor<MixinChartStatusInterface>;
59
77
  export {};
@@ -6,6 +6,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
6
6
  };
7
7
  import { html, nothing } from 'lit';
8
8
  import { property } from 'lit/decorators.js';
9
+ import { CHART_PLOT } from '../../controllers/chart-pointer-controller/chart-pointer-controller.js';
9
10
  import { words } from '../../words/words.js';
10
11
  export const MixinChartStatus = (Base) => {
11
12
  class ChartStatusElement extends Base {
@@ -24,6 +25,22 @@ export const MixinChartStatus = (Base) => {
24
25
  ? html `<p dir="auto" part="empty"><slot name="empty">${this.emptyText}</slot></p>`
25
26
  : nothing;
26
27
  }
28
+ renderEmpty() {
29
+ const named = this.named;
30
+ return html `<div
31
+ aria-label=${named || nothing}
32
+ id=${CHART_PLOT}
33
+ part="plot"
34
+ role=${named ? 'group' : nothing}
35
+ >${this.renderStatus()}</div>`;
36
+ }
37
+ updated(changed) {
38
+ super.updated(changed);
39
+ const root = this.renderRoot;
40
+ this.toggleState('active', this.pointer.at >= 0);
41
+ this.toggleState('empty', root.querySelector('[part="empty"]') !== null);
42
+ this.toggleState('loading', root.querySelector('[part="loading"]') !== null);
43
+ }
27
44
  }
28
45
  __decorate([
29
46
  words,
@@ -0,0 +1,10 @@
1
+ import type { auditNesting as audit } from './audit-nesting.js';
2
+ /**
3
+ * The unslotted-item warning in a production build: nothing.
4
+ *
5
+ * `#audit-nesting` resolves here unless the bundler or runtime asks for the
6
+ * `development` condition, so a production build carries none of the audit's
7
+ * code. The signature is the audit's, so a call site reads the same in both
8
+ * builds. See `audit-nesting.ts`.
9
+ */
10
+ export declare const auditNesting: typeof audit;
@@ -0,0 +1 @@
1
+ export const auditNesting = () => { };
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Reports an item nested in another item of its own tag without the slot that
3
+ * nests it, the one mistake that leaves a tree drawing the child as words.
4
+ *
5
+ * `<helix-tree-item>src<helix-tree-item>app.ts</helix-tree-item></helix-tree-item>`
6
+ * puts the inner item in the outer one's default slot, which is its label: the
7
+ * row reads "src app.ts" and is drawn as a leaf. The item has to carry
8
+ * `slot="children"`. **Nothing moves it there**, because writing `slot` onto
9
+ * an element the page rendered is a change to markup a server sent and a
10
+ * framework owns.
11
+ *
12
+ * **In a development build only**, as `missing-name` is: every item would pay
13
+ * for a sentence meant for the person writing the page. Core imports it as
14
+ * `#audit-nesting`, which `package.json` resolves here under the `development`
15
+ * condition and to `audit-nesting-off.ts`, which does nothing, everywhere else.
16
+ *
17
+ * `slot` is the name of the slot that nests an item.
18
+ */
19
+ export declare const auditNesting: (host: HTMLElement, slot: string) => void;
@@ -0,0 +1,19 @@
1
+ import { reportRuntime } from '../report-runtime/report-runtime.js';
2
+ const audited = new WeakSet();
3
+ export const auditNesting = (host, slot) => {
4
+ if (audited.has(host)) {
5
+ return;
6
+ }
7
+ const stray = [...host.children].find((child) => child.localName === host.localName && !child.hasAttribute('slot'));
8
+ if (!stray) {
9
+ return;
10
+ }
11
+ audited.add(host);
12
+ reportRuntime({
13
+ kind: 'unslotted-item',
14
+ message: `<${host.localName}> holds a <${stray.localName}> in its default slot, which is its ` +
15
+ `label, so the inner item is read as words in the row and drawn as no branch. Add ` +
16
+ `slot="${slot}" to each nested <${stray.localName}>.`,
17
+ tag: host.localName,
18
+ });
19
+ };
@@ -1,4 +1,4 @@
1
- import type { auditSource as audit } from './audit-source.js';
1
+ import type { auditSource as audit, auditData as auditShape } from './audit-source.js';
2
2
  /**
3
3
  * The missing-table warning in a production build: nothing.
4
4
  *
@@ -8,3 +8,5 @@ import type { auditSource as audit } from './audit-source.js';
8
8
  * builds. See `audit-source.ts`.
9
9
  */
10
10
  export declare const auditSource: typeof audit;
11
+ /** The malformed-data warning in a production build: nothing. */
12
+ export declare const auditData: typeof auditShape;
@@ -1 +1,2 @@
1
1
  export const auditSource = () => { };
2
+ export const auditData = () => { };
@@ -17,3 +17,9 @@
17
17
  export declare const auditSource: (host: HTMLElement & {
18
18
  for: string;
19
19
  }, table: () => Element | null) => void;
20
+ /**
21
+ * Reports a chart whose `data` is not `{ columns, rows }`, such as an array of
22
+ * rows, once for each chart. The chart draws its empty state. In a development
23
+ * build only, as the missing-table warning is.
24
+ */
25
+ export declare const auditData: (host: HTMLElement) => void;
@@ -1,5 +1,6 @@
1
1
  import { reportRuntime } from '../report-runtime/report-runtime.js';
2
2
  const audited = new WeakSet();
3
+ const misshapen = new WeakSet();
3
4
  export const auditSource = (host, table) => {
4
5
  if (host.for === '' || audited.has(host) || typeof document === 'undefined') {
5
6
  return;
@@ -25,3 +26,16 @@ export const auditSource = (host, table) => {
25
26
  window.addEventListener('load', read, { once: true });
26
27
  }
27
28
  };
29
+ export const auditData = (host) => {
30
+ if (misshapen.has(host)) {
31
+ return;
32
+ }
33
+ misshapen.add(host);
34
+ reportRuntime({
35
+ kind: 'malformed-data',
36
+ message: `<${host.localName}> was given \`data\` that is not { columns, rows }, so it draws nothing. ` +
37
+ "Pass an object such as { columns: ['Month', 'Sales'], rows: [['Jan', 10]] }, each row " +
38
+ 'an array.',
39
+ tag: host.localName,
40
+ });
41
+ };
@@ -37,6 +37,12 @@ export interface ChartData {
37
37
  */
38
38
  rows: ReadonlyArray<readonly [label: string, ...cells: ChartDatum[]]>;
39
39
  }
40
+ /**
41
+ * Whether `data` has the shape a chart reads: an object with an array of
42
+ * `columns` and an array of `rows`, each row an array. Any other value draws
43
+ * the empty state, where it would throw from the render.
44
+ */
45
+ export declare const isChartData: (data: unknown) => data is ChartData;
40
46
  /**
41
47
  * Reads `data` as series, one per column after the first, as `readSeries`
42
48
  * reads a table: a column with no numbers in it is not a series, and a blank
@@ -1,3 +1,11 @@
1
+ export const isChartData = (data) => {
2
+ const shape = data;
3
+ return (typeof shape === 'object' &&
4
+ shape !== null &&
5
+ Array.isArray(shape.columns) &&
6
+ Array.isArray(shape.rows) &&
7
+ shape.rows.every((row) => Array.isArray(row)));
8
+ };
1
9
  const cellOf = (datum, locale) => {
2
10
  if (datum === null || datum === undefined) {
3
11
  return { text: '' };
@@ -30,6 +30,8 @@ export type RuntimeKind =
30
30
  | 'fullscreen-refused'
31
31
  /** A rating's `min` is no score between 0 and its `max`. */
32
32
  | 'min-out-of-range'
33
+ /** A chart's `data` is not `{ columns, rows }`, so it draws its empty state. Development builds only. */
34
+ | 'malformed-data'
33
35
  /** A Stat was given one of Trend's directions, `up`, `down` or `neutral`, which lay nothing out. */
34
36
  | 'misread-direction'
35
37
  /** A React handler prop names an event the element never dispatches itself. Development builds only. */
@@ -99,6 +101,11 @@ export type RuntimeKind =
99
101
  | 'unscannable-logo'
100
102
  /** A virtualized list's box has no height of its own, so its window never settles. */
101
103
  | 'unsettled-window'
104
+ /**
105
+ * An item is nested in another of its tag without the slot that nests it, so it is read as
106
+ * the label. Development builds only.
107
+ */
108
+ | 'unslotted-item'
102
109
  /** A native button in a Button Group with `selection-mode`, which cannot be told it is pressed. */
103
110
  | 'untold-toggle'
104
111
  /** A slotted trigger is a plain element, which cannot be told it opens something. */
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.176.0";
9
+ export declare const VERSION = "0.177.0";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.176.0';
1
+ export const VERSION = '0.177.0';
@@ -0,0 +1,55 @@
1
+ import type { Message, MessageTable } from './message.js';
2
+ /** One locale's words, by tag, then by property. */
3
+ export type Pack = Record<string, Record<string, Message | MessageTable>>;
4
+ /** An element that reads words, perhaps with a `locale`. */
5
+ export type Speaker = Element & {
6
+ locale?: string | undefined;
7
+ };
8
+ /** What the page has registered, kept on `globalThis`. */
9
+ interface Held {
10
+ /** Each locale's words, under its lowercase tag. */
11
+ packs: Map<string, Pack>;
12
+ /** The locale a server render and a hydrating render speak. */
13
+ fallback: string;
14
+ /** What updates the elements that read words. */
15
+ watchers: Set<() => void>;
16
+ /** Whether an update is already queued for this task. */
17
+ queued?: boolean;
18
+ /** Whether the document's `lang` is watched. */
19
+ observed?: boolean;
20
+ /** Finds a registered word for an element, set by the first `registerWords`. */
21
+ find?: (host: Speaker, tag: string, name: string) => [Message | MessageTable, string] | undefined;
22
+ /** Reads the locale an element speaks, set where `wordsLocale` is loaded. */
23
+ locale?: (host: Speaker) => string;
24
+ }
25
+ /**
26
+ * The page's words, held on `globalThis`.
27
+ *
28
+ * **One registry for a page that carries two copies of this module**, one per
29
+ * bundle that imports it: words registered through one have to reach elements
30
+ * defined through the other, as the icon registry's glyphs do.
31
+ *
32
+ * **What every element reads, and nothing more.** An element with a word
33
+ * property reads its English until a page registers words, so the lookup and
34
+ * the locale chain arrive with `registerWords`, which puts its lookup in
35
+ * `find`, and the locale with `wordsLocale`, which puts itself in `locale`. A
36
+ * page that registers no words and formats no numbers ships neither: measured
37
+ * on Button, 474 bytes of its 13,437 compressed.
38
+ */
39
+ export declare const held: Held;
40
+ /** Asks every element that reads words to look again, once per task. */
41
+ export declare const changed: () => void;
42
+ /** Whether any words are registered, for a read that can skip the lookup. */
43
+ export declare const wordsRegistered: () => boolean;
44
+ /**
45
+ * Starts watching the document's `lang`, once for the page, in a browser.
46
+ * Called by what a `lang` change reaches: registered words, and a locale read.
47
+ */
48
+ export declare const watchLang: () => void;
49
+ /**
50
+ * Calls `watcher` once in the microtask after words are registered or
51
+ * cleared, or the document's `lang` changes. Returns the function that stops
52
+ * it.
53
+ */
54
+ export declare const watchWords: (watcher: () => void) => (() => void);
55
+ export {};
@@ -0,0 +1,29 @@
1
+ export const held = (globalThis[Symbol.for('helix.words')] ??= {
2
+ fallback: 'en',
3
+ packs: new Map(),
4
+ watchers: new Set(),
5
+ });
6
+ export const changed = () => {
7
+ if (!held.queued) {
8
+ held.queued = true;
9
+ queueMicrotask(() => {
10
+ held.queued = false;
11
+ for (const watcher of held.watchers) {
12
+ watcher();
13
+ }
14
+ });
15
+ }
16
+ };
17
+ export const wordsRegistered = () => held.packs.size > 0;
18
+ export const watchLang = () => {
19
+ if (!held.observed && typeof MutationObserver === 'function' && globalThis.document) {
20
+ held.observed = true;
21
+ new MutationObserver(changed).observe(document.documentElement, {
22
+ attributeFilter: ['lang'],
23
+ });
24
+ }
25
+ };
26
+ export const watchWords = (watcher) => {
27
+ held.watchers.add(watcher);
28
+ return () => held.watchers.delete(watcher);
29
+ };
@@ -1,4 +1,5 @@
1
1
  import type { Message, MessageTable } from './message.js';
2
+ export { watchWords, wordsRegistered } from './held.js';
2
3
  /**
3
4
  * The words of every family, keyed by the family's styled tag, each with its
4
5
  * word properties.
@@ -67,8 +68,6 @@ export declare const refreshWords: () => void;
67
68
  * `de-CH` tries `de-ch`, `de-latn-ch`, `de-latn`, `de`, then `en`.
68
69
  */
69
70
  export declare const localeChain: (locale: string) => string[];
70
- /** Whether any words are registered, for a read that can skip the lookup. */
71
- export declare const wordsRegistered: () => boolean;
72
71
  /**
73
72
  * A registered word for a tag's property in a locale, with the locale it was
74
73
  * found in, or `undefined`.
@@ -78,12 +77,3 @@ export declare const wordsRegistered: () => boolean;
78
77
  * along the chain, the more specific entry winning.
79
78
  */
80
79
  export declare const lookupWord: (locale: string, tag: string, name: string) => [Message | MessageTable, string] | undefined;
81
- /**
82
- * Calls `watcher` once in the microtask after words are registered or
83
- * cleared, or the document's `lang` changes. Returns the function that stops
84
- * it.
85
- *
86
- * The first watcher in a browser starts watching the document's `lang`, once
87
- * for the page.
88
- */
89
- export declare const watchWords: (watcher: () => void) => (() => void);
@@ -1,20 +1,9 @@
1
- const held = (globalThis[Symbol.for('helix.words')] ??= {
2
- fallback: 'en',
3
- packs: new Map(),
4
- watchers: new Set(),
5
- });
6
- const changed = () => {
7
- if (!held.queued) {
8
- held.queued = true;
9
- queueMicrotask(() => {
10
- held.queued = false;
11
- for (const watcher of held.watchers) {
12
- watcher();
13
- }
14
- });
15
- }
16
- };
1
+ import { changed, held, watchLang } from './held.js';
2
+ import { wordsLocale } from './words-locale.js';
3
+ export { watchWords, wordsRegistered } from './held.js';
17
4
  export const registerWords = (locale, ...words) => {
5
+ held.find ??= (host, tag, name) => lookupWord(wordsLocale(host), tag, name);
6
+ watchLang();
18
7
  const key = locale.toLowerCase();
19
8
  const pack = held.packs.get(key) ?? {};
20
9
  held.packs.set(key, pack);
@@ -74,7 +63,6 @@ export const localeChain = (locale) => {
74
63
  chains.set(locale, found);
75
64
  return found;
76
65
  };
77
- export const wordsRegistered = () => held.packs.size > 0;
78
66
  export const lookupWord = (locale, tag, name) => {
79
67
  let found;
80
68
  for (const one of localeChain(locale)) {
@@ -89,13 +77,3 @@ export const lookupWord = (locale, tag, name) => {
89
77
  }
90
78
  return found;
91
79
  };
92
- export const watchWords = (watcher) => {
93
- held.watchers.add(watcher);
94
- if (!held.observed && typeof MutationObserver === 'function' && globalThis.document) {
95
- held.observed = true;
96
- new MutationObserver(changed).observe(document.documentElement, {
97
- attributeFilter: ['lang'],
98
- });
99
- }
100
- return () => held.watchers.delete(watcher);
101
- };
@@ -1,5 +1,5 @@
1
1
  import { localeOf, runtimeLocale } from '../utils/locale-of/locale-of.js';
2
- import { defaultWordsLocale } from './registry.js';
2
+ import { held, watchLang } from './held.js';
3
3
  const checked = new Map();
4
4
  const tagOf = (written) => {
5
5
  let found = checked.get(written);
@@ -15,15 +15,17 @@ const tagOf = (written) => {
15
15
  return found;
16
16
  };
17
17
  export const wordsLocale = (element) => {
18
+ watchLang();
18
19
  const own = tagOf(element.locale || '');
19
20
  if (own) {
20
21
  return own;
21
22
  }
22
23
  if (typeof element.closest !== 'function' || element.hydrating) {
23
- return tagOf(element.getAttribute?.('lang') ?? '') || defaultWordsLocale();
24
+ return tagOf(element.getAttribute?.('lang') ?? '') || held.fallback;
24
25
  }
25
26
  return localeOf(element) || runtimeLocale();
26
27
  };
28
+ held.locale = wordsLocale;
27
29
  export const handedLocale = (element) => {
28
30
  const own = typeof element.locale === 'string' ? element.locale : '';
29
31
  return element.hydrating ? own : localeOf(element, own);
@@ -1,5 +1,5 @@
1
+ import { held, watchWords, wordsRegistered } from './held.js';
1
2
  import { formatMessage } from './message.js';
2
- import { lookupWord, watchWords, wordsRegistered } from './registry.js';
3
3
  import { wordsLocale } from './words-locale.js';
4
4
  const HELD = Symbol();
5
5
  const heldBy = (host) => host[HELD] ?? {};
@@ -21,7 +21,7 @@ const resolve = (host, name) => {
21
21
  let found = [own, ''];
22
22
  if (own == null || table) {
23
23
  const registered = wordsRegistered()
24
- ? lookupWord(wordsLocale(host), host.constructor.words || host.localName, name)
24
+ ? held.find?.(host, host.constructor.words || host.localName, name)
25
25
  : undefined;
26
26
  found = table
27
27
  ? [{ ...table, ...registered?.[0], ...own }, '']
@@ -44,10 +44,10 @@ const watcher = (host) => {
44
44
  let locale = '';
45
45
  return {
46
46
  hostConnected: () => {
47
- locale = wordsLocale(host);
47
+ locale = held.locale?.(host) ?? '';
48
48
  stop = watchWords(() => {
49
49
  recheck(host);
50
- const now = wordsLocale(host);
50
+ const now = held.locale?.(host) ?? '';
51
51
  if (now !== locale) {
52
52
  locale = now;
53
53
  host.requestUpdate();
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.176.0",
4
+ "version": "0.177.0",
5
5
  "license": "MIT",
6
6
  "author": "Ross Sheppard <ross.sheppard@fusion.dev>",
7
7
  "repository": {
@@ -23,6 +23,12 @@
23
23
  "helix-development": "./dist/utils/audit-name/audit-name.js",
24
24
  "default": "./dist/utils/audit-name/audit-name-off.js"
25
25
  },
26
+ "./audits/nesting": {
27
+ "types": "./dist/utils/audit-nesting/audit-nesting.d.ts",
28
+ "development": "./dist/utils/audit-nesting/audit-nesting.js",
29
+ "helix-development": "./dist/utils/audit-nesting/audit-nesting.js",
30
+ "default": "./dist/utils/audit-nesting/audit-nesting-off.js"
31
+ },
26
32
  "./audits/react": {
27
33
  "types": "./dist/helix-element/react-audit.d.ts",
28
34
  "development": "./dist/helix-element/react-audit.js",
@@ -62,11 +68,11 @@
62
68
  "bugs": "https://github.com/project-fusion/fusion/issues",
63
69
  "dependencies": {
64
70
  "@floating-ui/dom": "^1.8.0",
65
- "@fusion.dev/helix-icons": "0.176.0"
71
+ "@fusion.dev/helix-icons": "0.177.0"
66
72
  },
67
73
  "devDependencies": {
68
- "@fusion.dev/helix-cem": "0.176.0",
69
- "@fusion.dev/helix-testing": "0.176.0",
74
+ "@fusion.dev/helix-cem": "0.177.0",
75
+ "@fusion.dev/helix-testing": "0.177.0",
70
76
  "@lit-labs/ssr": "^4.1.0",
71
77
  "lit": "^3.2.1"
72
78
  },
@@ -77,6 +83,11 @@
77
83
  "helix-development": "./src/utils/audit-name/audit-name.ts",
78
84
  "default": "./src/utils/audit-name/audit-name-off.ts"
79
85
  },
86
+ "#audit-nesting": {
87
+ "development": "./src/utils/audit-nesting/audit-nesting.ts",
88
+ "helix-development": "./src/utils/audit-nesting/audit-nesting.ts",
89
+ "default": "./src/utils/audit-nesting/audit-nesting-off.ts"
90
+ },
80
91
  "#audit-served": {
81
92
  "development": "./src/utils/audit-served/audit-served.ts",
82
93
  "helix-development": "./src/utils/audit-served/audit-served.ts",