@fusion.dev/helix-core 0.152.0 → 0.154.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.
@@ -4920,6 +4920,35 @@
4920
4920
  "description": "The element `for` names, or null where there is nothing to name it in.\n\nNull on the server as well, since resolving an id wants a document. Every\nchart inherits that: the table is served whole and the drawing arrives on\nhydration.\n\n**And null while hydrating what a server sent**, which drew nothing, because\nthe render that hydrates has to be the server's: a drawing that differs is\na template Lit throws on. The update after it, which this asks for, draws.",
4921
4921
  "readonly": true
4922
4922
  },
4923
+ {
4924
+ "kind": "field",
4925
+ "name": "hydrating",
4926
+ "type": {
4927
+ "text": "boolean"
4928
+ },
4929
+ "privacy": "private",
4930
+ "description": "Whether the host is hydrating what a server sent.",
4931
+ "readonly": true
4932
+ },
4933
+ {
4934
+ "kind": "field",
4935
+ "name": "data",
4936
+ "type": {
4937
+ "text": "ChartData | undefined"
4938
+ },
4939
+ "privacy": "private",
4940
+ "description": "The host's `data`, where it is read: on the client, after hydration, and\nwhere `for` names no table.\n\n**Never on the server**, so a chart given data serves the frame, as one\nreading a table does, and the drawing arrives on hydration. A server's\nelement shim has no `closest`, which is how one is told apart, Lit's\n`isServer` being true in any test runner that resolves its `node` build.",
4941
+ "readonly": true
4942
+ },
4943
+ {
4944
+ "kind": "field",
4945
+ "name": "known",
4946
+ "type": {
4947
+ "text": "boolean"
4948
+ },
4949
+ "description": "Whether what the chart reads has been looked at: a table resolved or\nlooked for, or data read. False on the server and while hydrating, where\nneither is, so a chart there says nothing about its readings.",
4950
+ "readonly": true
4951
+ },
4923
4952
  {
4924
4953
  "kind": "method",
4925
4954
  "name": "resolve",
@@ -4968,11 +4997,11 @@
4968
4997
  {
4969
4998
  "name": "read",
4970
4999
  "type": {
4971
- "text": "(table: Element | null) => T"
5000
+ "text": "(table: Element | null, data: ChartData | undefined) => T"
4972
5001
  }
4973
5002
  }
4974
5003
  ],
4975
- "description": "A held parse where it still describes the table, or a fresh one."
5004
+ "description": "A held parse where it still describes the table or the data, or a fresh\none. `data` is held by identity, so a new object is read again and an\nobject changed in place is not, as a Lit property is."
4976
5005
  },
4977
5006
  {
4978
5007
  "kind": "field",
@@ -14212,6 +14241,22 @@
14212
14241
  "module": "./mixins/mixin-chart-source/mixin-chart-source.js"
14213
14242
  }
14214
14243
  },
14244
+ {
14245
+ "kind": "js",
14246
+ "name": "MixinChartStatus",
14247
+ "declaration": {
14248
+ "name": "MixinChartStatus",
14249
+ "module": "./mixins/mixin-chart-status/mixin-chart-status.js"
14250
+ }
14251
+ },
14252
+ {
14253
+ "kind": "js",
14254
+ "name": "MixinChartStatusInterface",
14255
+ "declaration": {
14256
+ "name": "MixinChartStatusInterface",
14257
+ "module": "./mixins/mixin-chart-status/mixin-chart-status.js"
14258
+ }
14259
+ },
14215
14260
  {
14216
14261
  "kind": "js",
14217
14262
  "name": "CollectionElement",
@@ -15732,6 +15777,38 @@
15732
15777
  "module": "./utils/read-color/read-color.js"
15733
15778
  }
15734
15779
  },
15780
+ {
15781
+ "kind": "js",
15782
+ "name": "ChartData",
15783
+ "declaration": {
15784
+ "name": "ChartData",
15785
+ "module": "./utils/read-data/read-data.js"
15786
+ }
15787
+ },
15788
+ {
15789
+ "kind": "js",
15790
+ "name": "ChartDatum",
15791
+ "declaration": {
15792
+ "name": "ChartDatum",
15793
+ "module": "./utils/read-data/read-data.js"
15794
+ }
15795
+ },
15796
+ {
15797
+ "kind": "js",
15798
+ "name": "rowsFromData",
15799
+ "declaration": {
15800
+ "name": "rowsFromData",
15801
+ "module": "./utils/read-data/read-data.js"
15802
+ }
15803
+ },
15804
+ {
15805
+ "kind": "js",
15806
+ "name": "seriesFromData",
15807
+ "declaration": {
15808
+ "name": "seriesFromData",
15809
+ "module": "./utils/read-data/read-data.js"
15810
+ }
15811
+ },
15735
15812
  {
15736
15813
  "kind": "js",
15737
15814
  "name": "byRow",
@@ -17180,6 +17257,14 @@
17180
17257
  }
17181
17258
  ],
17182
17259
  "members": [
17260
+ {
17261
+ "kind": "field",
17262
+ "name": "data",
17263
+ "type": {
17264
+ "text": "ChartData | undefined"
17265
+ },
17266
+ "description": "The readings as data, in the shape of a table, where the page has no\ntable to point `for` at. `for` wins where both are set."
17267
+ },
17183
17268
  {
17184
17269
  "kind": "field",
17185
17270
  "name": "for",
@@ -17212,6 +17297,15 @@
17212
17297
  },
17213
17298
  "description": "Where a reading the keys moved to stands among the readings, said after\nit. `{position}` and `{count}` are replaced with the numbers, written in\n`locale`.\n\nSet it to the words of the page's language, such as `{position} von\n{count}`, or to nothing to say no position."
17214
17299
  },
17300
+ {
17301
+ "kind": "field",
17302
+ "name": "readingsKnown",
17303
+ "type": {
17304
+ "text": "boolean"
17305
+ },
17306
+ "description": "Whether the readings have been looked for: false on the server and while\nhydrating, where no table is resolved and no data is read.",
17307
+ "readonly": true
17308
+ },
17215
17309
  {
17216
17310
  "kind": "field",
17217
17311
  "name": "series",
@@ -17379,6 +17473,15 @@
17379
17473
  "default": "new ChartPointerController(this)",
17380
17474
  "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."
17381
17475
  },
17476
+ {
17477
+ "kind": "field",
17478
+ "name": "data",
17479
+ "type": {
17480
+ "text": "ChartData | undefined"
17481
+ },
17482
+ "default": "undefined",
17483
+ "description": "The readings as data, in the shape of a table, where the page has no\ntable to point `for` at. `for` wins where both are set.\n\n```js\nchart.data = {\n caption: 'Sessions by channel',\n columns: ['Month', 'Direct', 'Referral'],\n rows: [['Jan', 10, 4], ['Feb', 20, 8]],\n}\n```\n\nThe first column holds each row's label and every column after it a\nseries, as in a table. A cell is a number, words, `null` for a blank,\nor `{ value, text }` for a number shown as words, such as `$128k`. A\nnumber is shown in `locale`.\n\n**A table is still the better source where the page can show one**:\nit reads with no script and before hydration, and a reader can sort and\ncopy it. Data is drawn on the client only, so a server serves the\nframe, and the summary, the keys and the readout are then the whole of\nwhat a screen reader is given. Set a new object to redraw: the chart\ndoes not look inside the one it has."
17484
+ },
17382
17485
  {
17383
17486
  "kind": "field",
17384
17487
  "name": "for",
@@ -17480,6 +17583,15 @@
17480
17583
  "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.",
17481
17584
  "readonly": true
17482
17585
  },
17586
+ {
17587
+ "kind": "field",
17588
+ "name": "readingsKnown",
17589
+ "type": {
17590
+ "text": "boolean"
17591
+ },
17592
+ "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.",
17593
+ "readonly": true
17594
+ },
17483
17595
  {
17484
17596
  "kind": "field",
17485
17597
  "name": "sourceRows",
@@ -17695,6 +17807,175 @@
17695
17807
  }
17696
17808
  ]
17697
17809
  },
17810
+ {
17811
+ "kind": "javascript-module",
17812
+ "path": "src/mixins/mixin-chart-status/mixin-chart-status.ts",
17813
+ "declarations": [
17814
+ {
17815
+ "kind": "class",
17816
+ "description": "The public surface `MixinChartStatus` adds.\n\nDeclared apart from the class for the reason `MixinChartSourceInterface`\ngives: TypeScript cannot emit declarations for an anonymous class with\nprotected members.",
17817
+ "name": "MixinChartStatusInterface",
17818
+ "members": [
17819
+ {
17820
+ "kind": "field",
17821
+ "name": "emptyText",
17822
+ "type": {
17823
+ "text": "string"
17824
+ },
17825
+ "description": "The words shown in place of the drawing when the table has no readings,\nand the fallback of the `empty` slot."
17826
+ },
17827
+ {
17828
+ "kind": "field",
17829
+ "name": "loading",
17830
+ "type": {
17831
+ "text": "boolean"
17832
+ },
17833
+ "description": "Whether the readings are still on their way. The chart shows\n`loading-text` in place of an empty drawing until it has readings."
17834
+ },
17835
+ {
17836
+ "kind": "field",
17837
+ "name": "loadingText",
17838
+ "type": {
17839
+ "text": "string"
17840
+ },
17841
+ "description": "The words shown in place of the drawing while `loading` is set and there\nare no readings yet, and the fallback of the `loading` slot."
17842
+ },
17843
+ {
17844
+ "kind": "method",
17845
+ "name": "renderStatus",
17846
+ "privacy": "protected",
17847
+ "return": {
17848
+ "type": {
17849
+ "text": "TemplateResult | typeof nothing"
17850
+ }
17851
+ },
17852
+ "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."
17853
+ }
17854
+ ],
17855
+ "helix": {}
17856
+ },
17857
+ {
17858
+ "kind": "mixin",
17859
+ "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.",
17860
+ "name": "MixinChartStatus",
17861
+ "members": [
17862
+ {
17863
+ "kind": "field",
17864
+ "name": "words",
17865
+ "type": {
17866
+ "text": "string"
17867
+ },
17868
+ "static": true,
17869
+ "default": "''",
17870
+ "description": "The key the empty and loading words are registered under: the host's\nown, which each chart names with its styled tag, `static words =\n'helix-line-chart'`. A host with none reads its tag name."
17871
+ },
17872
+ {
17873
+ "kind": "field",
17874
+ "name": "emptyText",
17875
+ "type": {
17876
+ "text": "string"
17877
+ },
17878
+ "default": "'No readings'",
17879
+ "description": "The words shown in place of the drawing when the table has no readings,\nand the fallback of the `empty` slot.",
17880
+ "attribute": "empty-text",
17881
+ "helix": {
17882
+ "words": true
17883
+ }
17884
+ },
17885
+ {
17886
+ "kind": "field",
17887
+ "name": "loading",
17888
+ "type": {
17889
+ "text": "boolean"
17890
+ },
17891
+ "default": "false",
17892
+ "description": "Whether the readings are still on their way. The chart shows\n`loading-text` in place of an empty drawing until it has readings.\n\nReflected, so a stylesheet can treat the chart while it waits. A chart\nthat already has readings keeps drawing them, so a refresh does not\nblank it.",
17893
+ "attribute": "loading",
17894
+ "reflects": true
17895
+ },
17896
+ {
17897
+ "kind": "field",
17898
+ "name": "loadingText",
17899
+ "type": {
17900
+ "text": "string"
17901
+ },
17902
+ "default": "'Loading readings'",
17903
+ "description": "The words shown in place of the drawing while `loading` is set and\nthere are no readings yet, and the fallback of the `loading` slot.",
17904
+ "attribute": "loading-text",
17905
+ "helix": {
17906
+ "words": true
17907
+ }
17908
+ },
17909
+ {
17910
+ "kind": "method",
17911
+ "name": "renderStatus",
17912
+ "privacy": "protected",
17913
+ "return": {
17914
+ "type": {
17915
+ "text": "TemplateResult | typeof nothing"
17916
+ }
17917
+ },
17918
+ "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."
17919
+ }
17920
+ ],
17921
+ "attributes": [
17922
+ {
17923
+ "name": "empty-text",
17924
+ "type": {
17925
+ "text": "string"
17926
+ },
17927
+ "default": "'No readings'",
17928
+ "description": "The words shown in place of the drawing when the table has no readings,\nand the fallback of the `empty` slot.",
17929
+ "fieldName": "emptyText"
17930
+ },
17931
+ {
17932
+ "name": "loading",
17933
+ "type": {
17934
+ "text": "boolean"
17935
+ },
17936
+ "default": "false",
17937
+ "description": "Whether the readings are still on their way. The chart shows\n`loading-text` in place of an empty drawing until it has readings.\n\nReflected, so a stylesheet can treat the chart while it waits. A chart\nthat already has readings keeps drawing them, so a refresh does not\nblank it.",
17938
+ "fieldName": "loading"
17939
+ },
17940
+ {
17941
+ "name": "loading-text",
17942
+ "type": {
17943
+ "text": "string"
17944
+ },
17945
+ "default": "'Loading readings'",
17946
+ "description": "The words shown in place of the drawing while `loading` is set and\nthere are no readings yet, and the fallback of the `loading` slot.",
17947
+ "fieldName": "loadingText"
17948
+ }
17949
+ ],
17950
+ "parameters": [
17951
+ {
17952
+ "name": "Base",
17953
+ "type": {
17954
+ "text": "T"
17955
+ }
17956
+ }
17957
+ ]
17958
+ }
17959
+ ],
17960
+ "exports": [
17961
+ {
17962
+ "kind": "js",
17963
+ "name": "MixinChartStatusInterface",
17964
+ "declaration": {
17965
+ "name": "MixinChartStatusInterface",
17966
+ "module": "src/mixins/mixin-chart-status/mixin-chart-status.ts"
17967
+ }
17968
+ },
17969
+ {
17970
+ "kind": "js",
17971
+ "name": "MixinChartStatus",
17972
+ "declaration": {
17973
+ "name": "MixinChartStatus",
17974
+ "module": "src/mixins/mixin-chart-status/mixin-chart-status.ts"
17975
+ }
17976
+ }
17977
+ ]
17978
+ },
17698
17979
  {
17699
17980
  "kind": "javascript-module",
17700
17981
  "path": "src/mixins/mixin-collection/mixin-collection.ts",
@@ -27545,7 +27826,7 @@
27545
27826
  "name": "options",
27546
27827
  "optional": true,
27547
27828
  "type": {
27548
- "text": "Intl.NumberFormatOptions"
27829
+ "text": "Intl.NumberFormatOptions | null"
27549
27830
  }
27550
27831
  }
27551
27832
  ],
@@ -28086,6 +28367,74 @@
28086
28367
  }
28087
28368
  ]
28088
28369
  },
28370
+ {
28371
+ "kind": "javascript-module",
28372
+ "path": "src/utils/read-data/read-data.ts",
28373
+ "declarations": [
28374
+ {
28375
+ "kind": "function",
28376
+ "name": "seriesFromData",
28377
+ "return": {
28378
+ "type": {
28379
+ "text": "Series[]"
28380
+ }
28381
+ },
28382
+ "parameters": [
28383
+ {
28384
+ "name": "data",
28385
+ "type": {
28386
+ "text": "ChartData"
28387
+ }
28388
+ },
28389
+ {
28390
+ "name": "locale",
28391
+ "default": "''"
28392
+ }
28393
+ ],
28394
+ "description": "Reads `data` as series, one per column after the first, as `readSeries`\nreads a table: a column with no numbers in it is not a series, and a blank\nkeeps its row's place."
28395
+ },
28396
+ {
28397
+ "kind": "function",
28398
+ "name": "rowsFromData",
28399
+ "return": {
28400
+ "type": {
28401
+ "text": "Row[]"
28402
+ }
28403
+ },
28404
+ "parameters": [
28405
+ {
28406
+ "name": "data",
28407
+ "type": {
28408
+ "text": "ChartData"
28409
+ }
28410
+ },
28411
+ {
28412
+ "name": "locale",
28413
+ "default": "''"
28414
+ }
28415
+ ],
28416
+ "description": "Reads `data` as rows, as `readRows` reads a table: every cell with its\nwords and its number, including the columns nothing plots."
28417
+ }
28418
+ ],
28419
+ "exports": [
28420
+ {
28421
+ "kind": "js",
28422
+ "name": "seriesFromData",
28423
+ "declaration": {
28424
+ "name": "seriesFromData",
28425
+ "module": "src/utils/read-data/read-data.ts"
28426
+ }
28427
+ },
28428
+ {
28429
+ "kind": "js",
28430
+ "name": "rowsFromData",
28431
+ "declaration": {
28432
+ "name": "rowsFromData",
28433
+ "module": "src/utils/read-data/read-data.ts"
28434
+ }
28435
+ }
28436
+ ]
28437
+ },
28089
28438
  {
28090
28439
  "kind": "javascript-module",
28091
28440
  "path": "src/utils/read-series/read-series.ts",
@@ -28106,7 +28455,7 @@
28106
28455
  }
28107
28456
  }
28108
28457
  ],
28109
- "description": "Reads a table as series, one per data column.\n\n**The table is the chart's accessible representation, not a fallback for\none.** Every chart here takes its numbers from real markup rather than from a\nproperty, so the page is readable with no script at all, sortable by anything\nthat sorts tables, and correct in a screen reader without a parallel\ndescription that has to be kept in step by hand. A `values` array would be\neasier to pass and would make the accessible version the thing nobody\nmaintains.\n\nThe shape is the ordinary one: a header row naming the series, a first column\nof row headers, and numbers in the rest. A single-column table has one series\nand takes its name from the caption, since a header row of one is usually\nabsent.\n\n`data-value` wins over the cell's text, because `$1.2k` is what a reader\nshould see and is not a number. `Number()` rather than `parseFloat`, because\nthe second reads `12abc` as 12: a value that is nearly a number is a mistake\nworth dropping rather than rounding off, and a dropped point is visible where\na silently truncated one is not.\n\n**A dropped point keeps its place.** Each point carries `at`, the row it was\nread from, and a chart draws it at that row's category. Counted by position\nin `points`, a blank in March slid April's reading under March's label, and a\nblank in the first column took a category off the axis for every series.\n`byRow` lays a series out one slot per row with the blanks left empty."
28458
+ "description": "Reads a table as series, one per data column.\n\n**The table is the chart's accessible representation, not a fallback for\none.** Every chart here takes its numbers from real markup by preference, so\nthe page is readable with no script at all, sortable by anything that sorts\ntables, and correct in a screen reader without a parallel description that\nhas to be kept in step by hand. A chart's `data` takes the same shape for a\npage that has no table to show, and is read by `seriesFromData`; the docs\nsteer every page that can show a table to the table.\n\nThe shape is the ordinary one: a header row naming the series, a first column\nof row headers, and numbers in the rest. A single-column table has one series\nand takes its name from the caption, since a header row of one is usually\nabsent.\n\n`data-value` wins over the cell's text, because `$1.2k` is what a reader\nshould see and is not a number. `Number()` rather than `parseFloat`, because\nthe second reads `12abc` as 12: a value that is nearly a number is a mistake\nworth dropping rather than rounding off, and a dropped point is visible where\na silently truncated one is not.\n\n**A dropped point keeps its place.** Each point carries `at`, the row it was\nread from, and a chart draws it at that row's category. Counted by position\nin `points`, a blank in March slid April's reading under March's label, and a\nblank in the first column took a category off the axis for every series.\n`byRow` lays a series out one slot per row with the blanks left empty."
28110
28459
  },
28111
28460
  {
28112
28461
  "kind": "function",
@@ -31881,7 +32230,7 @@
31881
32230
  "type": {
31882
32231
  "text": "string"
31883
32232
  },
31884
- "default": "'0.152.0'",
32233
+ "default": "'0.154.0'",
31885
32234
  "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."
31886
32235
  }
31887
32236
  ],
@@ -32441,6 +32790,6 @@
32441
32790
  ],
32442
32791
  "helix": {
32443
32792
  "package": "@fusion.dev/helix-core",
32444
- "version": "0.152.0"
32793
+ "version": "0.154.0"
32445
32794
  }
32446
32795
  }
@@ -1,4 +1,5 @@
1
1
  import type { ReactiveController, ReactiveControllerHost } from 'lit';
2
+ import { type ChartData } from '../../utils/read-data/read-data.js';
2
3
  import { type Row, type Series } from '../../utils/read-series/read-series.js';
3
4
  /** A host that names the element it reads from. */
4
5
  export interface SourceHost extends ReactiveControllerHost, HTMLElement {
@@ -6,6 +7,14 @@ export interface SourceHost extends ReactiveControllerHost, HTMLElement {
6
7
  * The id of the table or grid to read.
7
8
  */
8
9
  for: string;
10
+ /**
11
+ * Readings given as data, read where `for` names nothing.
12
+ */
13
+ data?: ChartData | undefined;
14
+ /**
15
+ * The locale numbers in `data` are written in.
16
+ */
17
+ locale?: string;
9
18
  }
10
19
  /**
11
20
  * Resolves the element a chart reads from, and follows it.
@@ -69,6 +78,24 @@ export declare class SourceController implements ReactiveController {
69
78
  * a template Lit throws on. The update after it, which this asks for, draws.
70
79
  */
71
80
  get element(): Element | null;
81
+ /** Whether the host is hydrating what a server sent. */
82
+ private get hydrating();
83
+ /**
84
+ * The host's `data`, where it is read: on the client, after hydration, and
85
+ * where `for` names no table.
86
+ *
87
+ * **Never on the server**, so a chart given data serves the frame, as one
88
+ * reading a table does, and the drawing arrives on hydration. A server's
89
+ * element shim has no `closest`, which is how one is told apart, Lit's
90
+ * `isServer` being true in any test runner that resolves its `node` build.
91
+ */
92
+ private get data();
93
+ /**
94
+ * Whether what the chart reads has been looked at: a table resolved or
95
+ * looked for, or data read. False on the server and while hydrating, where
96
+ * neither is, so a chart there says nothing about its readings.
97
+ */
98
+ get known(): boolean;
72
99
  /**
73
100
  * The element `for` names, whatever the host is rendering.
74
101
  *
@@ -98,7 +125,11 @@ export declare class SourceController implements ReactiveController {
98
125
  get series(): Series[];
99
126
  /** Every row it holds, text and number alike, or none. Held the way `series` is. */
100
127
  get rows(): Row[];
101
- /** A held parse where it still describes the table, or a fresh one. */
128
+ /**
129
+ * A held parse where it still describes the table or the data, or a fresh
130
+ * one. `data` is held by identity, so a new object is read again and an
131
+ * object changed in place is not, as a Lit property is.
132
+ */
102
133
  private parsed;
103
134
  /**
104
135
  * The series hidden on the table, by lowercased name. Empty where there is
@@ -1,6 +1,8 @@
1
1
  import { auditSource } from '@fusion.dev/helix-core/audits/source';
2
- import { elementById } from '../../utils/element-by-id/element-by-id.js';
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
4
  import { readRows, readSeries, seriesKey, } from '../../utils/read-series/read-series.js';
5
+ import { wordsLocale } from '../../words/words-locale.js';
4
6
  const HIDDEN = new WeakMap();
5
7
  const READERS = new WeakMap();
6
8
  const NONE = new Set();
@@ -13,12 +15,32 @@ export class SourceController {
13
15
  host.addController(this);
14
16
  }
15
17
  get element() {
16
- if (this.host.hydrating === true) {
17
- this.deferred = this.host.for !== '';
18
+ if (this.hydrating) {
19
+ this.deferred = this.host.for !== '' || this.host.data !== undefined;
18
20
  return null;
19
21
  }
20
22
  return this.resolve();
21
23
  }
24
+ get hydrating() {
25
+ return this.host.hydrating === true;
26
+ }
27
+ get data() {
28
+ const host = this.host;
29
+ if (host.data === undefined || typeof host.closest !== 'function' || this.hydrating) {
30
+ return undefined;
31
+ }
32
+ return host.data;
33
+ }
34
+ get known() {
35
+ const host = this.host;
36
+ if (host.for === '' && host.data === undefined) {
37
+ return true;
38
+ }
39
+ if (typeof host.closest !== 'function' || this.hydrating) {
40
+ return false;
41
+ }
42
+ return host.for === '' || searchableById(host);
43
+ }
22
44
  resolve() {
23
45
  const host = this.host;
24
46
  if (host.for === '') {
@@ -37,19 +59,20 @@ export class SourceController {
37
59
  : null;
38
60
  }
39
61
  get series() {
40
- this.heldSeries = this.parsed(this.heldSeries, (table) => (table ? readSeries(table) : []));
62
+ this.heldSeries = this.parsed(this.heldSeries, (table, data) => table ? readSeries(table) : data ? seriesFromData(data, wordsLocale(this.host)) : []);
41
63
  return this.heldSeries.value;
42
64
  }
43
65
  get rows() {
44
- this.heldRows = this.parsed(this.heldRows, (table) => (table ? readRows(table) : []));
66
+ this.heldRows = this.parsed(this.heldRows, (table, data) => table ? readRows(table) : data ? rowsFromData(data, wordsLocale(this.host)) : []);
45
67
  return this.heldRows.value;
46
68
  }
47
69
  parsed(held, read) {
48
70
  const element = this.element;
49
- if (held && held.element === element && held.version === this.version) {
71
+ const data = element ? undefined : this.data;
72
+ if (held && held.element === element && held.data === data && held.version === this.version) {
50
73
  return held;
51
74
  }
52
- return { element, value: read(element), version: this.version };
75
+ return { data, element, value: read(element, data), version: this.version };
53
76
  }
54
77
  get hidden() {
55
78
  const element = this.element;
package/dist/index.d.ts CHANGED
@@ -37,6 +37,7 @@ export { type ActivateDetail, type AuraFallbackDetail, type BandDetail, type Can
37
37
  export { HelixElement, whenHydrated } from './helix-element/helix-element.js';
38
38
  export { HelixFormElement } from './helix-form-element/helix-form-element.js';
39
39
  export { MixinChartSource, type MixinChartSourceInterface, PALETTE, PLOT_HEIGHT, PLOT_WIDTH, POSITION_LABEL, seriesIndex, seriesNames, } from './mixins/mixin-chart-source/mixin-chart-source.js';
40
+ export { MixinChartStatus, type MixinChartStatusInterface, } from './mixins/mixin-chart-status/mixin-chart-status.js';
40
41
  export { type CollectionElement, MixinCollection, } from './mixins/mixin-collection/mixin-collection.js';
41
42
  export { MixinControllerActive, type MixinControllerActiveInterface, } from './mixins/mixin-controller-active/mixin-controller-active.js';
42
43
  export { MixinControllerChecked, type MixinControllerCheckedInterface, } from './mixins/mixin-controller-checked/mixin-controller-checked.js';
@@ -109,6 +110,7 @@ export { appleTouch, onAndroid, onApple, type Platform } from './utils/platform/
109
110
  export { pressType, type RecentPress } from './utils/press-type/press-type.js';
110
111
  export { rangeAdd } from './utils/range-add/range-add.js';
111
112
  export { type ColorBytes, type ColorBytesAlpha, readColor, readColorAlpha, rgbOf, } from './utils/read-color/read-color.js';
113
+ export { type ChartData, type ChartDatum, rowsFromData, seriesFromData, } from './utils/read-data/read-data.js';
112
114
  export { byRow, type Extremes, extremesOf, type Row, type RowCell, readRows, readSeries, type Series, type SeriesPoint, seriesKey, unbroken, } from './utils/read-series/read-series.js';
113
115
  export { type ElementsProperty, linkDescription, reflectElements, unlinkDescription, } from './utils/reflect-elements/reflect-elements.js';
114
116
  export { type Relative, type RelativeOptions, relativeTime, } from './utils/relative-time/relative-time.js';
package/dist/index.js CHANGED
@@ -37,6 +37,7 @@ export { HelixActionEvent, HelixActivateEvent, HelixAuraFallbackEvent, HelixBand
37
37
  export { HelixElement, whenHydrated } from './helix-element/helix-element.js';
38
38
  export { HelixFormElement } from './helix-form-element/helix-form-element.js';
39
39
  export { MixinChartSource, PALETTE, PLOT_HEIGHT, PLOT_WIDTH, POSITION_LABEL, seriesIndex, seriesNames, } from './mixins/mixin-chart-source/mixin-chart-source.js';
40
+ export { MixinChartStatus, } from './mixins/mixin-chart-status/mixin-chart-status.js';
40
41
  export { MixinCollection, } from './mixins/mixin-collection/mixin-collection.js';
41
42
  export { MixinControllerActive, } from './mixins/mixin-controller-active/mixin-controller-active.js';
42
43
  export { MixinControllerChecked, } from './mixins/mixin-controller-checked/mixin-controller-checked.js';
@@ -109,6 +110,7 @@ export { appleTouch, onAndroid, onApple } from './utils/platform/platform.js';
109
110
  export { pressType } from './utils/press-type/press-type.js';
110
111
  export { rangeAdd } from './utils/range-add/range-add.js';
111
112
  export { readColor, readColorAlpha, rgbOf, } from './utils/read-color/read-color.js';
113
+ export { rowsFromData, seriesFromData, } from './utils/read-data/read-data.js';
112
114
  export { byRow, extremesOf, readRows, readSeries, seriesKey, unbroken, } from './utils/read-series/read-series.js';
113
115
  export { linkDescription, reflectElements, unlinkDescription, } from './utils/reflect-elements/reflect-elements.js';
114
116
  export { relativeTime, } from './utils/relative-time/relative-time.js';
@@ -1,5 +1,6 @@
1
1
  import { ChartPointerController } from '../../controllers/chart-pointer-controller/chart-pointer-controller.js';
2
2
  import type { HelixElement } from '../../helix-element/helix-element.js';
3
+ import type { ChartData } from '../../utils/read-data/read-data.js';
3
4
  import { type Row, type Series } from '../../utils/read-series/read-series.js';
4
5
  type Constructor<T> = new (...args: any[]) => T;
5
6
  /**
@@ -110,6 +111,11 @@ export declare const seriesNames: (list: string | undefined) => string[];
110
111
  * @cssproperty --chart-series-24 - The color of series 24. The styled tier points it at a palette token.
111
112
  */
112
113
  export declare class MixinChartSourceInterface {
114
+ /**
115
+ * The readings as data, in the shape of a table, where the page has no
116
+ * table to point `for` at. `for` wins where both are set.
117
+ */
118
+ data: ChartData | undefined;
113
119
  /**
114
120
  * The id of the table to read.
115
121
  *
@@ -145,6 +151,11 @@ export declare class MixinChartSourceInterface {
145
151
  * {count}`, or to nothing to say no position.
146
152
  */
147
153
  positionLabel: string;
154
+ /**
155
+ * Whether the readings have been looked for: false on the server and while
156
+ * hydrating, where no table is resolved and no data is read.
157
+ */
158
+ get readingsKnown(): boolean;
148
159
  /**
149
160
  * The table's columns, one series each. Empty when there is no table.
150
161
  */
@@ -22,6 +22,7 @@ export const MixinChartSource = (Base) => {
22
22
  super(...arguments);
23
23
  this.source = new SourceController(this);
24
24
  this.pointer = new ChartPointerController(this);
25
+ this.data = undefined;
25
26
  this.for = '';
26
27
  this.label = '';
27
28
  this.locale = '';
@@ -48,11 +49,16 @@ export const MixinChartSource = (Base) => {
48
49
  return this.source.series;
49
50
  }
50
51
  get named() {
52
+ const table = this.source.element;
51
53
  return (this.getAttribute?.('aria-label') ||
52
54
  this.label ||
53
- this.source.element?.querySelector('caption')?.textContent?.trim() ||
55
+ table?.querySelector('caption')?.textContent?.trim() ||
56
+ (table ? '' : this.data?.caption) ||
54
57
  '');
55
58
  }
59
+ get readingsKnown() {
60
+ return this.source.known;
61
+ }
56
62
  get sourceRows() {
57
63
  return this.source.rows;
58
64
  }
@@ -79,6 +85,9 @@ export const MixinChartSource = (Base) => {
79
85
  return this.sourceRows.length;
80
86
  }
81
87
  }
88
+ __decorate([
89
+ property({ attribute: false })
90
+ ], ChartSourceElement.prototype, "data", void 0);
82
91
  __decorate([
83
92
  property()
84
93
  ], ChartSourceElement.prototype, "for", void 0);
@@ -0,0 +1,59 @@
1
+ import { nothing, type TemplateResult } from 'lit';
2
+ import type { HelixElement } from '../../helix-element/helix-element.js';
3
+ type Constructor<T> = new (...args: any[]) => T;
4
+ /** A chart that reads a table or data, and knows whether it has looked yet. */
5
+ interface ChartHost extends HelixElement {
6
+ /** Whether the readings have been looked for, which a server never does. */
7
+ readonly readingsKnown: boolean;
8
+ }
9
+ /**
10
+ * The public surface `MixinChartStatus` adds.
11
+ *
12
+ * Declared apart from the class for the reason `MixinChartSourceInterface`
13
+ * gives: TypeScript cannot emit declarations for an anonymous class with
14
+ * protected members.
15
+ */
16
+ export declare class MixinChartStatusInterface {
17
+ /**
18
+ * The words shown in place of the drawing when the table has no readings,
19
+ * and the fallback of the `empty` slot.
20
+ */
21
+ emptyText: string;
22
+ /**
23
+ * Whether the readings are still on their way. The chart shows
24
+ * `loading-text` in place of an empty drawing until it has readings.
25
+ */
26
+ loading: boolean;
27
+ /**
28
+ * The words shown in place of the drawing while `loading` is set and there
29
+ * are no readings yet, and the fallback of the `loading` slot.
30
+ */
31
+ loadingText: string;
32
+ /**
33
+ * What stands in the plot while there is nothing to draw: the `loading`
34
+ * line, the `empty` line, or nothing while the table cannot be read yet.
35
+ */
36
+ protected renderStatus(): TemplateResult | typeof nothing;
37
+ }
38
+ /**
39
+ * What a chart shows in place of its drawing when it has no readings: words
40
+ * while they load, and words when the table it reads has none.
41
+ *
42
+ * **A blank box said neither.** A chart pointed at an empty table drew its
43
+ * plot at full height with nothing in it, which reads as a chart that failed,
44
+ * and one waiting for a request drew the same. `Table` and `Data Table` take
45
+ * the same pair, `empty-text` and `loading-text` with `empty` and `loading`
46
+ * slots for markup, such as a link to add the first reading.
47
+ *
48
+ * **Nothing at all while the readings cannot be looked for**, which is a
49
+ * server render and the hydrating one after it: there is no document to
50
+ * resolve `for` in and `data` is drawn on the client only, so "No readings"
51
+ * there would be a claim about readings nobody has looked at. `Heatmap` keeps
52
+ * the same rule for its own `empty-text`.
53
+ *
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.
57
+ */
58
+ export declare const MixinChartStatus: <T extends Constructor<ChartHost>>(Base: T) => T & Constructor<MixinChartStatusInterface>;
59
+ export {};
@@ -0,0 +1,40 @@
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 } from 'lit/decorators.js';
9
+ import { words } from '../../words/words.js';
10
+ export const MixinChartStatus = (Base) => {
11
+ class ChartStatusElement extends Base {
12
+ constructor() {
13
+ super(...arguments);
14
+ this.emptyText = 'No readings';
15
+ this.loading = false;
16
+ this.loadingText = 'Loading readings';
17
+ }
18
+ static { this.words = ''; }
19
+ renderStatus() {
20
+ if (this.loading) {
21
+ return html `<p dir="auto" part="loading"><slot name="loading">${this.loadingText}</slot></p>`;
22
+ }
23
+ return this.readingsKnown
24
+ ? html `<p dir="auto" part="empty"><slot name="empty">${this.emptyText}</slot></p>`
25
+ : nothing;
26
+ }
27
+ }
28
+ __decorate([
29
+ words,
30
+ property({ attribute: 'empty-text' })
31
+ ], ChartStatusElement.prototype, "emptyText", void 0);
32
+ __decorate([
33
+ property({ reflect: true, type: Boolean })
34
+ ], ChartStatusElement.prototype, "loading", void 0);
35
+ __decorate([
36
+ words,
37
+ property({ attribute: 'loading-text' })
38
+ ], ChartStatusElement.prototype, "loadingText", void 0);
39
+ return ChartStatusElement;
40
+ };
@@ -8,7 +8,7 @@
8
8
  * build and cheap to call, and a control formats on every update.
9
9
  */
10
10
  /** Writes a number in a locale with `Intl.NumberFormat` options. */
11
- export declare const formatNumber: (value: number, locale?: string, options?: Intl.NumberFormatOptions) => string;
11
+ export declare const formatNumber: (value: number, locale?: string, options?: Intl.NumberFormatOptions | null) => string;
12
12
  /**
13
13
  * A share, from 0 to 1, as a locale writes a whole percentage: `40%`, or
14
14
  * `40 %` in French. For `aria-valuetext`, since a bare `aria-valuenow` is read
@@ -3,7 +3,7 @@ const formatter = (locale, options) => {
3
3
  const key = `${locale}|${JSON.stringify(options ?? {})}`;
4
4
  let found = held.get(key);
5
5
  if (!found) {
6
- found = new Intl.NumberFormat(locale || undefined, options);
6
+ found = new Intl.NumberFormat(locale || undefined, options ?? undefined);
7
7
  held.set(key, found);
8
8
  }
9
9
  return found;
@@ -0,0 +1,50 @@
1
+ import type { Row, Series } from '../read-series/read-series.js';
2
+ /**
3
+ * One cell of a chart's `data`: a number, words, a blank, or a number with
4
+ * the words it is shown as, such as `{ value: 128000, text: '$128k' }`.
5
+ */
6
+ export type ChartDatum = number | string | null | undefined | {
7
+ text: string;
8
+ value: number;
9
+ };
10
+ /**
11
+ * A chart's readings given as data, in the shape a table has.
12
+ *
13
+ * ```js
14
+ * chart.data = {
15
+ * caption: 'Sessions by channel',
16
+ * columns: ['Month', 'Direct', 'Referral'],
17
+ * rows: [
18
+ * ['Jan', 10, 4],
19
+ * ['Feb', 20, 8],
20
+ * ],
21
+ * }
22
+ * ```
23
+ */
24
+ export interface ChartData {
25
+ /**
26
+ * The chart's name where it has no `label`, as a table's caption is, and
27
+ * the series' name where there is one column and no heading for it.
28
+ */
29
+ caption?: string;
30
+ /**
31
+ * The column headings. The first heads the row labels, and each after it
32
+ * names a series.
33
+ */
34
+ columns: string[];
35
+ /**
36
+ * One row per reading: its label first, then a cell per column.
37
+ */
38
+ rows: ReadonlyArray<readonly [label: string, ...cells: ChartDatum[]]>;
39
+ }
40
+ /**
41
+ * Reads `data` as series, one per column after the first, as `readSeries`
42
+ * reads a table: a column with no numbers in it is not a series, and a blank
43
+ * keeps its row's place.
44
+ */
45
+ export declare const seriesFromData: (data: ChartData, locale?: string) => Series[];
46
+ /**
47
+ * Reads `data` as rows, as `readRows` reads a table: every cell with its
48
+ * words and its number, including the columns nothing plots.
49
+ */
50
+ export declare const rowsFromData: (data: ChartData, locale?: string) => Row[];
@@ -0,0 +1,35 @@
1
+ const cellOf = (datum, locale) => {
2
+ if (datum === null || datum === undefined) {
3
+ return { text: '' };
4
+ }
5
+ if (typeof datum === 'object') {
6
+ return Number.isFinite(datum.value)
7
+ ? { text: datum.text, value: datum.value }
8
+ : { text: datum.text };
9
+ }
10
+ if (typeof datum === 'number') {
11
+ return Number.isFinite(datum)
12
+ ? { text: new Intl.NumberFormat(locale || undefined).format(datum), value: datum }
13
+ : { text: '' };
14
+ }
15
+ const value = Number(datum.trim());
16
+ return Number.isFinite(value) && datum.trim() !== '' ? { text: datum, value } : { text: datum };
17
+ };
18
+ export const seriesFromData = (data, locale = '') => {
19
+ const names = data.columns.slice(1);
20
+ const count = Math.max(0, ...data.rows.map((row) => row.length - 1));
21
+ return Array.from({ length: count }, (_, column) => ({
22
+ name: names[column] ?? data.caption ?? '',
23
+ points: data.rows.flatMap((row, at) => {
24
+ const value = cellOf(row[column + 1], locale).value;
25
+ return value === undefined ? [] : [{ at, label: String(row[0] ?? ''), value }];
26
+ }),
27
+ })).filter((one) => one.points.length > 0);
28
+ };
29
+ export const rowsFromData = (data, locale = '') => data.rows.map((row) => ({
30
+ cells: row.slice(1).map((datum, column) => ({
31
+ name: data.columns[column + 1] ?? '',
32
+ ...cellOf(datum, locale),
33
+ })),
34
+ label: String(row[0] ?? ''),
35
+ }));
@@ -38,12 +38,12 @@ export interface Series {
38
38
  * Reads a table as series, one per data column.
39
39
  *
40
40
  * **The table is the chart's accessible representation, not a fallback for
41
- * one.** Every chart here takes its numbers from real markup rather than from a
42
- * property, so the page is readable with no script at all, sortable by anything
43
- * that sorts tables, and correct in a screen reader without a parallel
44
- * description that has to be kept in step by hand. A `values` array would be
45
- * easier to pass and would make the accessible version the thing nobody
46
- * maintains.
41
+ * one.** Every chart here takes its numbers from real markup by preference, so
42
+ * the page is readable with no script at all, sortable by anything that sorts
43
+ * tables, and correct in a screen reader without a parallel description that
44
+ * has to be kept in step by hand. A chart's `data` takes the same shape for a
45
+ * page that has no table to show, and is read by `seriesFromData`; the docs
46
+ * steer every page that can show a table to the table.
47
47
  *
48
48
  * The shape is the ordinary one: a header row naming the series, a first column
49
49
  * of row headers, and numbers in the rest. A single-column table has one series
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.152.0";
9
+ export declare const VERSION = "0.154.0";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.152.0';
1
+ export const VERSION = '0.154.0';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fusion.dev/helix-core",
3
3
  "description": "Helix element primitives: base classes, mixins, decorators, utilities.",
4
- "version": "0.152.0",
4
+ "version": "0.154.0",
5
5
  "license": "MIT",
6
6
  "author": "Ross Sheppard <ross.sheppard@fusion.dev>",
7
7
  "repository": {
@@ -56,11 +56,11 @@
56
56
  "bugs": "https://github.com/project-fusion/fusion/issues",
57
57
  "dependencies": {
58
58
  "@floating-ui/dom": "^1.8.0",
59
- "@fusion.dev/helix-icons": "0.152.0"
59
+ "@fusion.dev/helix-icons": "0.154.0"
60
60
  },
61
61
  "devDependencies": {
62
- "@fusion.dev/helix-cem": "0.152.0",
63
- "@fusion.dev/helix-testing": "0.152.0",
62
+ "@fusion.dev/helix-cem": "0.154.0",
63
+ "@fusion.dev/helix-testing": "0.154.0",
64
64
  "@lit-labs/ssr": "^4.1.0",
65
65
  "lit": "^3.2.1"
66
66
  },