@fusion.dev/helix-headless 0.47.1 → 0.49.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.
@@ -19143,6 +19143,28 @@
19143
19143
  "description": "Whether to leave the value out, for a picker whose value is stated elsewhere.\n\nThe row reads the color as text and lets it be typed or pasted, which is\nwhat makes this element complete standing on a page: a square and two ramps\ncan build a color and cannot state one, and a reader who has a hex already\nshould not have to find it by dragging.\n\nColor Input sets this, because its field is that input. Two editable fields\nfor one value is the outcome worth avoiding: two places to type, and no\nanswer to which one is authoritative or which has the caret. Do not close it\nthe other way round, with Color Input dropping its own field: the value\nwould then be unreadable and unpasteable without opening a panel.",
19144
19144
  "attribute": "no-value"
19145
19145
  },
19146
+ {
19147
+ "kind": "field",
19148
+ "name": "readOnly",
19149
+ "type": {
19150
+ "text": "boolean"
19151
+ },
19152
+ "default": "false",
19153
+ "description": "Whether the control refuses changes while still submitting its value.\n\n**Enforced by refusing every request in one place**, which is the seam this\ncomponent already has: `requestValue` is what every path here comes through,\nand gating it once is what stops the next call site from being written\nwithout the check.\n\nNot `disabled`, which is the confusion worth naming: a disabled control\nsubmits nothing and leaves the tab order, and a read-only one submits its\nvalue and stays reachable, which is what a form showing a color somebody\nelse set needs.\n\n**One gate is not enough here, and the second is `move`.** This picker\nholds a model beside its value, which Date Picker and Time Picker do not:\n`moved` is the HSB the square is drawn from, and it is written before the\nvalue is requested. Gating only the request would leave the square under\nthe reader's pointer and the value where it was, which is a control that\nrefuses and looks like it accepted.",
19154
+ "attribute": "readonly",
19155
+ "reflects": true
19156
+ },
19157
+ {
19158
+ "kind": "field",
19159
+ "name": "required",
19160
+ "type": {
19161
+ "text": "boolean"
19162
+ },
19163
+ "default": "false",
19164
+ "description": "Whether the form refuses to submit while no color is chosen.\n\n**A color is exactly as requirable as a date**, which is the comparison\nthat settles it: this element extends `HelixFormElement`, carries `name`\nand reports its value, so a consumer writing\n`<helix-color-picker name=\"brand\" required>` inside a form has every reason\nto expect the form to refuse an empty one. Its two siblings in the same\ncomposition position, Date Picker and Time Picker, both check it.\n\nWritten out rather than taken from `MixinField`, for the reason those two\nrecord: the mixin brings a label, a hint and a feedback region, and this is\na panel composed *into* a field that already has all three, so taking it\nwould give one control two of each. What is left is the validity call and\nthe anchor the browser points its bubble at.\n\n**Emptiness is the only fault reported, and bad input deliberately is\nnot.** The value row is typed into a character at a time and every\nkeystroke reaches `requestValue`, so `#0`, `#00` and `#00f` would each be a\nreported fault on the way to `#00ff00`. Date Picker and Time Picker can\nreport bad input because their value moves only on a commit. Color Input\nreports it for this family instead, once, against the field the reader\ntyped into.",
19165
+ "attribute": "required",
19166
+ "reflects": true
19167
+ },
19146
19168
  {
19147
19169
  "kind": "field",
19148
19170
  "name": "swatches",
@@ -19228,6 +19250,31 @@
19228
19250
  "readonly": true,
19229
19251
  "description": "Takes what was typed, and lets the square follow only a whole color."
19230
19252
  },
19253
+ {
19254
+ "kind": "method",
19255
+ "name": "requestValue",
19256
+ "return": {
19257
+ "type": {
19258
+ "text": "boolean"
19259
+ }
19260
+ },
19261
+ "parameters": [
19262
+ {
19263
+ "name": "next",
19264
+ "type": {
19265
+ "text": "string"
19266
+ }
19267
+ }
19268
+ ],
19269
+ "description": "Refuses every change while read-only. See `readOnly`.",
19270
+ "inheritedFrom": {
19271
+ "name": "MixinControllerValue",
19272
+ "module": "src/mixins/mixin-controller-value/mixin-controller-value.ts"
19273
+ },
19274
+ "type": {
19275
+ "text": "requestValue(next: string) => boolean"
19276
+ }
19277
+ },
19231
19278
  {
19232
19279
  "kind": "field",
19233
19280
  "name": "move",
@@ -19280,7 +19327,18 @@
19280
19327
  "text": "void"
19281
19328
  }
19282
19329
  },
19283
- "description": "Reports the color to the form.\n\n**The one thing a form-associated element owes, and this element owed it\nfrom the start without paying.** It extends `HelixFormElement`, takes\n`MixinControllerValue` and carries `name`, so a consumer writing\n`<helix-color-picker name=\"brand\">` inside a form has every reason to\nexpect an entry, and the form carried none: no error, nothing in the\nmarkup to look at, and a field silently missing from the submission. Its\ntwo siblings in the same composition position, Color Input and Slider,\nboth report.\n\n`null` rather than an empty string where there is no color, which is the\ndifference between a control that submits nothing and one that submits a\nblank. There is no `setValidity` beside it: this element does not take\n`MixinField`, and every validity member of `ElementInternals` is the\nconsumer's to reach through the field that wraps it."
19330
+ "description": "Reports the color to the form.\n\n**The one thing a form-associated element owes, and this element owed it\nfrom the start without paying.** It extends `HelixFormElement`, takes\n`MixinControllerValue` and carries `name`, so a consumer writing\n`<helix-color-picker name=\"brand\">` inside a form has every reason to\nexpect an entry, and the form carried none: no error, nothing in the\nmarkup to look at, and a field silently missing from the submission. Its\ntwo siblings in the same composition position, Color Input and Slider,\nboth report.\n\n`null` rather than an empty string where there is no color, which is the\ndifference between a control that submits nothing and one that submits a\nblank.\n\nThe verdict goes beside it, because `required` is this element's own claim\nand nothing else can answer it: a picker standing on a page has no field\naround it to check on its behalf. What it is *not* is a second opinion on\neverything else, which is why bad input is left to Color Input. See\n`required`."
19331
+ },
19332
+ {
19333
+ "kind": "method",
19334
+ "name": "reportRequired",
19335
+ "privacy": "private",
19336
+ "return": {
19337
+ "type": {
19338
+ "text": "void"
19339
+ }
19340
+ },
19341
+ "description": "Reports \"required and empty\", or clears it.\n\nAnchored on the square, which is where a reader would go to answer: it is\nthe first control in the panel and the one `focus` puts the keyboard on, so\nthe browser's bubble points at the thing to do rather than at the row that\nstates the result."
19284
19342
  },
19285
19343
  {
19286
19344
  "kind": "method",
@@ -19366,6 +19424,31 @@
19366
19424
  ],
19367
19425
  "description": "One axis of the area, taken the way `takeChannel` takes a rail.\n\n**Named rather than inline because of the first line.** The `<input\ntype=\"range\">` behind each axis sends its own composed `input`, which\nreaches a consumer listening on this element beside this element's own\nunless it is stopped: a consumer dragging the area receives the range's\nevent and not this component's."
19368
19426
  },
19427
+ {
19428
+ "kind": "method",
19429
+ "name": "putBack",
19430
+ "privacy": "private",
19431
+ "return": {
19432
+ "type": {
19433
+ "text": "void"
19434
+ }
19435
+ },
19436
+ "parameters": [
19437
+ {
19438
+ "name": "control",
19439
+ "type": {
19440
+ "text": "{ value: string }"
19441
+ }
19442
+ },
19443
+ {
19444
+ "name": "at",
19445
+ "type": {
19446
+ "text": "number"
19447
+ }
19448
+ }
19449
+ ],
19450
+ "description": "Returns a control that moved itself to what the model still holds.\n\n**A refusal has to undo something, and here it is a handle.** Every other\ncontrol in this picker is drawn from the model, so refusing the change\nleaves the picture right on its own. The two area ranges and the two\nchannel sliders are not: each moved before it announced, and each is bound\nwith `.value`, which lit dirty-checks against the value it last committed.\nThe model never changed, so that binding writes nothing and the handle\nstays where the reader dragged it, on a control that refused. Carousel\nsettles the same question the same way: a refusal puts the track back on\nthe slide the index still holds."
19451
+ },
19369
19452
  {
19370
19453
  "kind": "method",
19371
19454
  "name": "takeChannel",
@@ -19473,31 +19556,6 @@
19473
19556
  "module": "src/mixins/mixin-controller-value/mixin-controller-value.ts"
19474
19557
  }
19475
19558
  },
19476
- {
19477
- "kind": "method",
19478
- "name": "requestValue",
19479
- "return": {
19480
- "type": {
19481
- "text": "boolean"
19482
- }
19483
- },
19484
- "parameters": [
19485
- {
19486
- "name": "next",
19487
- "type": {
19488
- "text": "string"
19489
- }
19490
- }
19491
- ],
19492
- "description": "Requests a new value, and reports whether the request stood.\n\nAnnounces before it acts, so `preventDefault()` on the `helix-before-change` is\na refusal and nothing moves. Nobody refusing means the control updates\nitself, whether or not a consumer declared `value`: a declared property\nis a seed and a correction, the way `<input value>` is, and a consumer\nwho wants the change for themselves refuses it and assigns their own.",
19493
- "inheritedFrom": {
19494
- "name": "MixinControllerValue",
19495
- "module": "src/mixins/mixin-controller-value/mixin-controller-value.ts"
19496
- },
19497
- "type": {
19498
- "text": "requestValue(next: string) => boolean"
19499
- }
19500
- },
19501
19559
  {
19502
19560
  "kind": "method",
19503
19561
  "name": "formResetCallback",
@@ -20134,6 +20192,24 @@
20134
20192
  "description": "Whether to leave the value out, for a picker whose value is stated elsewhere.\n\nThe row reads the color as text and lets it be typed or pasted, which is\nwhat makes this element complete standing on a page: a square and two ramps\ncan build a color and cannot state one, and a reader who has a hex already\nshould not have to find it by dragging.\n\nColor Input sets this, because its field is that input. Two editable fields\nfor one value is the outcome worth avoiding: two places to type, and no\nanswer to which one is authoritative or which has the caret. Do not close it\nthe other way round, with Color Input dropping its own field: the value\nwould then be unreadable and unpasteable without opening a panel.",
20135
20193
  "fieldName": "noValue"
20136
20194
  },
20195
+ {
20196
+ "name": "readonly",
20197
+ "type": {
20198
+ "text": "boolean"
20199
+ },
20200
+ "default": "false",
20201
+ "description": "Whether the control refuses changes while still submitting its value.\n\n**Enforced by refusing every request in one place**, which is the seam this\ncomponent already has: `requestValue` is what every path here comes through,\nand gating it once is what stops the next call site from being written\nwithout the check.\n\nNot `disabled`, which is the confusion worth naming: a disabled control\nsubmits nothing and leaves the tab order, and a read-only one submits its\nvalue and stays reachable, which is what a form showing a color somebody\nelse set needs.\n\n**One gate is not enough here, and the second is `move`.** This picker\nholds a model beside its value, which Date Picker and Time Picker do not:\n`moved` is the HSB the square is drawn from, and it is written before the\nvalue is requested. Gating only the request would leave the square under\nthe reader's pointer and the value where it was, which is a control that\nrefuses and looks like it accepted.",
20202
+ "fieldName": "readOnly"
20203
+ },
20204
+ {
20205
+ "name": "required",
20206
+ "type": {
20207
+ "text": "boolean"
20208
+ },
20209
+ "default": "false",
20210
+ "description": "Whether the form refuses to submit while no color is chosen.\n\n**A color is exactly as requirable as a date**, which is the comparison\nthat settles it: this element extends `HelixFormElement`, carries `name`\nand reports its value, so a consumer writing\n`<helix-color-picker name=\"brand\" required>` inside a form has every reason\nto expect the form to refuse an empty one. Its two siblings in the same\ncomposition position, Date Picker and Time Picker, both check it.\n\nWritten out rather than taken from `MixinField`, for the reason those two\nrecord: the mixin brings a label, a hint and a feedback region, and this is\na panel composed *into* a field that already has all three, so taking it\nwould give one control two of each. What is left is the validity call and\nthe anchor the browser points its bubble at.\n\n**Emptiness is the only fault reported, and bad input deliberately is\nnot.** The value row is typed into a character at a time and every\nkeystroke reaches `requestValue`, so `#0`, `#00` and `#00f` would each be a\nreported fault on the way to `#00ff00`. Date Picker and Time Picker can\nreport bad input because their value moves only on a commit. Color Input\nreports it for this family instead, once, against the field the reader\ntyped into.",
20211
+ "fieldName": "required"
20212
+ },
20137
20213
  {
20138
20214
  "name": "swatches",
20139
20215
  "type": {
@@ -64299,7 +64375,7 @@
64299
64375
  "helix": {
64300
64376
  "category": "navigation",
64301
64377
  "since": "0.1.0",
64302
- "status": "experimental",
64378
+ "status": "stable",
64303
64379
  "documentation": "https://fusion.dev/helix/components/outline",
64304
64380
  "aliases": [
64305
64381
  "Table of contents",
@@ -72111,11 +72187,11 @@
72111
72187
  "name": "--rating-part"
72112
72188
  },
72113
72189
  {
72114
- "description": "The width of one step, as a fraction of a mark.",
72190
+ "description": "Which step along the row a target is, counting from one, whatever `min` leaves open.",
72115
72191
  "name": "--rating-step"
72116
72192
  },
72117
72193
  {
72118
- "description": "How many steps one mark is divided into.",
72194
+ "description": "How many steps the whole row holds, which is `max` over `step`.",
72119
72195
  "name": "--rating-steps"
72120
72196
  }
72121
72197
  ],
@@ -72124,6 +72200,10 @@
72124
72200
  "description": "What is wrong with it.",
72125
72201
  "name": "feedback"
72126
72202
  },
72203
+ {
72204
+ "description": "The row: the marks, and the score beside them.",
72205
+ "name": "field"
72206
+ },
72127
72207
  {
72128
72208
  "description": "The filled marks, clipped to the score.",
72129
72209
  "name": "fill"
@@ -72190,7 +72270,7 @@
72190
72270
  "text": "number"
72191
72271
  },
72192
72272
  "default": "0",
72193
- "description": "The lowest score a reader can give, which is what clearing returns to.\n\nZero rather than one, because \"no rating\" and \"the worst rating\" are\ndifferent answers and a control that cannot express the first has to\npretend the second. A survey that requires an answer says so with\n`required`, not by making one unreachable.",
72273
+ "description": "The lowest score a reader can give, which is what clearing returns to.\n\nZero rather than one, because \"no rating\" and \"the worst rating\" are\ndifferent answers and a control that cannot express the first has to\npretend the second. A survey that requires an answer says so with\n`required`, not by making one unreachable.\n\n**It is the floor of the scale, and every edge follows from that.**\n\n*Clearing* returns the row to it. An empty rating, a value nothing can\nparse, and a form reset with nothing declared are all the same situation,\nand all three draw `min` filled: at the default of zero that is the empty\nrow this always drew, and at `min=\"2\"` it is two marks, the way an empty\n`Slider` sits at its own minimum rather than at zero. The value is still\n`\"\"` through all of it, so `required` still refuses it and `valueAsNumber`\nis still `NaN`: what the row shows is the bottom of the scale, not an\nanswer somebody gave.\n\n*The steps* are the scores from `step` up to `max` that are not below it,\nso `min=\"2\"` on five marks offers 2 to 5 and leaves the first two marks\nfilled and unpressable. At zero nothing is dropped, which is why the\ndefault row is unchanged.\n\n*The keyboard* moves through those steps and no others. `wrap` is already\nfalse here, so arrowing down from the lowest one holds its ground rather\nthan reaching a score this property forbids, and the tab stop an unrated\nrating carries sits on the lowest score offered rather than on the first\nmark.\n\n*A `min` between two steps* is a floor and not a step: `min=\"1.5\"` with\nwhole marks clamps the value at 1.5 and offers 2 upward, since 1.5 is not\nsomewhere the row can be pressed. That is a real thing to ask for and is\nleft to mean what it says.\n\n*A `min` outside the marks is refused rather than obeyed*, and says so\nonce on the console. Below zero and anything that is not a number are read\nas zero, there being no mark to draw for a negative score; above `max` is\nread as `max`, which collapses the scale to its top score rather than\nleaving a rating with nothing to press. A property that silently accepts\nnonsense is the fault this pays off, so neither case is quietly ignored.",
72194
72274
  "attribute": "min"
72195
72275
  },
72196
72276
  {
@@ -72272,6 +72352,16 @@
72272
72352
  "readonly": true,
72273
72353
  "default": "new KeyboardNavigationController(this, { items: () => this.options, selects: () => true, wrap: false, })"
72274
72354
  },
72355
+ {
72356
+ "kind": "field",
72357
+ "name": "floor",
72358
+ "type": {
72359
+ "text": "number"
72360
+ },
72361
+ "privacy": "private",
72362
+ "description": "The floor the row actually uses, which is `min` where `min` is one.\n\nBetween zero and `max`, and zero for anything that is not a number, per\nthe note on `min`. Derived on read rather than corrected into the property,\nso `rating.min` still reports what the consumer set and the warning below\ncan tell the two apart.",
72363
+ "readonly": true
72364
+ },
72275
72365
  {
72276
72366
  "kind": "field",
72277
72367
  "name": "score",
@@ -72281,6 +72371,16 @@
72281
72371
  "description": "The score, as a number, clamped to what the marks can show.",
72282
72372
  "readonly": true
72283
72373
  },
72374
+ {
72375
+ "kind": "field",
72376
+ "name": "positions",
72377
+ "type": {
72378
+ "text": "number[]"
72379
+ },
72380
+ "privacy": "private",
72381
+ "description": "Every score the marks can show, which is `max / step` of them.\n\nThe whole row rather than the part `min` leaves open, because this is what\nthe stylesheet lays the targets out against: `--rating-steps` is the pitch\nand a step's own place in this list is where it sits along the row. Counted\nfrom the part a reader can press instead, a floor above zero would widen\nevery target and slide the row left.",
72382
+ "readonly": true
72383
+ },
72284
72384
  {
72285
72385
  "kind": "field",
72286
72386
  "name": "steps",
@@ -72288,7 +72388,7 @@
72288
72388
  "text": "number[]"
72289
72389
  },
72290
72390
  "privacy": "private",
72291
- "description": "Every score a reader can choose, which is `max / step` of them.",
72391
+ "description": "Every score a reader can choose, which is the positions `min` leaves open.",
72292
72392
  "readonly": true
72293
72393
  },
72294
72394
  {
@@ -72403,7 +72503,7 @@
72403
72503
  "text": "void"
72404
72504
  }
72405
72505
  },
72406
- "description": "Reports the value, and whether the form may go without one.\n\n**`required` arrives from the mixin, so it prints on the reference page\nwhether or not anything here enforces it.** Without a `setValidity` call a\nform holding an unrated required rating submits, `:valid` true and the\nentry present but empty. `reportRequired` is what makes the property real.\n\n**Empty is `value === ''` rather than a score of zero**, because zero is a\nrating a reader can deliberately give when `min` allows it, and refusing to\nsubmit it would be the control disagreeing with the property that permits\nit. What `required` asks is whether they answered at all.\n\n`Slider` is deliberately not given the same treatment: a native\n`<input type=\"range\" required>` is always valid, because a range always\nholds a number, and matching the platform there is the point."
72506
+ "description": "Reports the value, and whether the form may go without one.\n\n**`required` arrives from the mixin, so it prints on the reference page\nwhether or not anything here enforces it.** Without a `setValidity` call a\nform holding an unrated required rating submits, `:valid` true and the\nentry present but empty. `reportRequired` is what makes the property real.\n\n**Empty is `value === ''` rather than a score at the floor**, and the two\nare not the same thing: an unanswered row draws `min`, per the note on that\nproperty, so a control that read its own picture would call every rating\nwith `min=\"2\"` answered the moment it rendered. What `required` asks is\nwhether the reader answered at all, which only the value knows. A consumer\nwho writes `value=\"0\"` has answered, and is not refused.\n\n`Slider` is deliberately not given the same treatment: a native\n`<input type=\"range\" required>` is always valid, because a range always\nholds a number, and matching the platform there is the point."
72407
72507
  },
72408
72508
  {
72409
72509
  "kind": "field",
@@ -72423,6 +72523,16 @@
72423
72523
  "privacy": "private",
72424
72524
  "readonly": true
72425
72525
  },
72526
+ {
72527
+ "kind": "field",
72528
+ "name": "warned",
72529
+ "type": {
72530
+ "text": "boolean"
72531
+ },
72532
+ "privacy": "private",
72533
+ "default": "false",
72534
+ "description": "Whether the console has already been told about a `min` off the scale."
72535
+ },
72426
72536
  {
72427
72537
  "kind": "field",
72428
72538
  "name": "valueAsNumber",
@@ -73301,7 +73411,7 @@
73301
73411
  "text": "number"
73302
73412
  },
73303
73413
  "default": "0",
73304
- "description": "The lowest score a reader can give, which is what clearing returns to.\n\nZero rather than one, because \"no rating\" and \"the worst rating\" are\ndifferent answers and a control that cannot express the first has to\npretend the second. A survey that requires an answer says so with\n`required`, not by making one unreachable.",
73414
+ "description": "The lowest score a reader can give, which is what clearing returns to.\n\nZero rather than one, because \"no rating\" and \"the worst rating\" are\ndifferent answers and a control that cannot express the first has to\npretend the second. A survey that requires an answer says so with\n`required`, not by making one unreachable.\n\n**It is the floor of the scale, and every edge follows from that.**\n\n*Clearing* returns the row to it. An empty rating, a value nothing can\nparse, and a form reset with nothing declared are all the same situation,\nand all three draw `min` filled: at the default of zero that is the empty\nrow this always drew, and at `min=\"2\"` it is two marks, the way an empty\n`Slider` sits at its own minimum rather than at zero. The value is still\n`\"\"` through all of it, so `required` still refuses it and `valueAsNumber`\nis still `NaN`: what the row shows is the bottom of the scale, not an\nanswer somebody gave.\n\n*The steps* are the scores from `step` up to `max` that are not below it,\nso `min=\"2\"` on five marks offers 2 to 5 and leaves the first two marks\nfilled and unpressable. At zero nothing is dropped, which is why the\ndefault row is unchanged.\n\n*The keyboard* moves through those steps and no others. `wrap` is already\nfalse here, so arrowing down from the lowest one holds its ground rather\nthan reaching a score this property forbids, and the tab stop an unrated\nrating carries sits on the lowest score offered rather than on the first\nmark.\n\n*A `min` between two steps* is a floor and not a step: `min=\"1.5\"` with\nwhole marks clamps the value at 1.5 and offers 2 upward, since 1.5 is not\nsomewhere the row can be pressed. That is a real thing to ask for and is\nleft to mean what it says.\n\n*A `min` outside the marks is refused rather than obeyed*, and says so\nonce on the console. Below zero and anything that is not a number are read\nas zero, there being no mark to draw for a negative score; above `max` is\nread as `max`, which collapses the scale to its top score rather than\nleaving a rating with nothing to press. A property that silently accepts\nnonsense is the fault this pays off, so neither case is quietly ignored.",
73305
73415
  "fieldName": "min"
73306
73416
  },
73307
73417
  {
@@ -88002,7 +88112,7 @@
88002
88112
  "summary": "The content shown for one tab.",
88003
88113
  "helix": {
88004
88114
  "since": "0.1.0",
88005
- "status": "experimental",
88115
+ "status": "stable",
88006
88116
  "documentation": "https://fusion.dev/helix/components/tabs",
88007
88117
  "aliases": [
88008
88118
  "TabPanel"
@@ -88686,7 +88796,7 @@
88686
88796
  "helix": {
88687
88797
  "category": "navigation",
88688
88798
  "since": "0.1.0",
88689
- "status": "experimental",
88799
+ "status": "stable",
88690
88800
  "documentation": "https://fusion.dev/helix/components/tabs",
88691
88801
  "aliases": [
88692
88802
  "TabList",
@@ -89201,7 +89311,7 @@
89201
89311
  "summary": "One tab in a tab list.",
89202
89312
  "helix": {
89203
89313
  "since": "0.1.0",
89204
- "status": "experimental",
89314
+ "status": "stable",
89205
89315
  "documentation": "https://fusion.dev/helix/components/tabs",
89206
89316
  "aliases": [
89207
89317
  "Tab"
@@ -93661,7 +93771,7 @@
93661
93771
  },
93662
93772
  {
93663
93773
  "kind": "class",
93664
- "description": "**A list rather than three wheels**, and the reason is what a list already\nis. `helix-listbox` brings the keyboard, the typeahead, the scroll into view\nand the announcement of a set of options, and a time at a step is a set of\noptions. Three little wheels for hours, minutes and a meridiem are three\ncontrols to build, three to test, and a tab stop each for something a reader\nanswers once.\n\n**A step is what makes it a list at all.** Fifteen minutes is ninety-six rows,\nwhich a reader scrolls or types into; one minute is one thousand four hundred\nand forty, which is a listbox in name only. So the list is for the times a\nproduct actually offers, and arbitrary times are the field's job in Input\nTime, the same division Date Input draws between typing a date and using the\ncalendar.\n\n**The value is `HH:mm` on a twenty-four hour clock, always.** That is what a\nform submits and what `min` and `max` compare against, and it sorts as a\nstring, which is the whole reason the shape is that one. What each row *says*\nis the reader's own convention, from `Intl`.",
93774
+ "description": "**A list rather than three wheels**, and the reason is what a list already\nis. `helix-listbox` brings the keyboard, the typeahead, the scroll into view\nand the announcement of a set of options, and a time at a step is a set of\noptions. Three little wheels for hours, minutes and a meridiem are three\ncontrols to build, three to test, and a tab stop each for something a reader\nanswers once.\n\n**A step is what makes it a list at all.** Fifteen minutes is ninety-six rows,\nwhich a reader scrolls or types into; one minute is one thousand four hundred\nand forty, which is a listbox in name only. So the list is for the times a\nproduct actually offers, and arbitrary times are the field's job in Input\nTime, the same division Date Input draws between typing a date and using the\ncalendar.\n\n**The value is `HH:mm` on a twenty-four hour clock, always.** That is what a\nform submits and what `min` and `max` compare against, and it sorts as a\nstring, which is the whole reason the shape is that one. What each row *says*\nis the reader's own convention, from `Intl`.\n\n**The chosen row is marked in the markup, not worked out on upgrade.** The\nlistbox is handed the answer as `.value`, and lit-ssr serializes no property\nbinding, so a served `value=\"09:30\"` shipped ninety-six rows with nothing on\nany of them saying which one it was: the mark arrived when the listbox\nupgraded and ran `place`. So each option is given `selected` as well, which\nis an attribute binding and does serve, and which the listbox then agrees\nwith rather than fights, `place` writing the same answer from the same value.\n`selected` is the declaration a consumer writes on `Option` anywhere else in\nthe library, and Date Picker expresses its own choice as markup for the same\nreason: a picker whose answer is invisible until a script runs is a picker\nwhose answer is not served at all. Both readings do it, the list and the\nwheels, a wheel being a listbox like the list is.",
93665
93775
  "name": "HelixHeadlessTimePicker",
93666
93776
  "cssParts": [
93667
93777
  {
@@ -101344,7 +101454,7 @@
101344
101454
  "helix": {
101345
101455
  "category": "overlays",
101346
101456
  "since": "0.1.0",
101347
- "status": "experimental",
101457
+ "status": "stable",
101348
101458
  "documentation": "https://fusion.dev/helix/components/tour",
101349
101459
  "aliases": [
101350
101460
  "Coachmark",
@@ -102076,7 +102186,7 @@
102076
102186
  "helix": {
102077
102187
  "category": "overlays",
102078
102188
  "since": "0.1.0",
102079
- "status": "experimental",
102189
+ "status": "stable",
102080
102190
  "documentation": "https://fusion.dev/helix/components/tour",
102081
102191
  "tier": "headless",
102082
102192
  "group": "BaseTour",
@@ -128,6 +128,52 @@ export declare class HelixHeadlessColorPicker extends HelixHeadlessColorPicker_b
128
128
  * would then be unreadable and unpasteable without opening a panel.
129
129
  */
130
130
  noValue: boolean;
131
+ /**
132
+ * Whether the control refuses changes while still submitting its value.
133
+ *
134
+ * **Enforced by refusing every request in one place**, which is the seam this
135
+ * component already has: `requestValue` is what every path here comes through,
136
+ * and gating it once is what stops the next call site from being written
137
+ * without the check.
138
+ *
139
+ * Not `disabled`, which is the confusion worth naming: a disabled control
140
+ * submits nothing and leaves the tab order, and a read-only one submits its
141
+ * value and stays reachable, which is what a form showing a color somebody
142
+ * else set needs.
143
+ *
144
+ * **One gate is not enough here, and the second is `move`.** This picker
145
+ * holds a model beside its value, which Date Picker and Time Picker do not:
146
+ * `moved` is the HSB the square is drawn from, and it is written before the
147
+ * value is requested. Gating only the request would leave the square under
148
+ * the reader's pointer and the value where it was, which is a control that
149
+ * refuses and looks like it accepted.
150
+ */
151
+ readOnly: boolean;
152
+ /**
153
+ * Whether the form refuses to submit while no color is chosen.
154
+ *
155
+ * **A color is exactly as requirable as a date**, which is the comparison
156
+ * that settles it: this element extends `HelixFormElement`, carries `name`
157
+ * and reports its value, so a consumer writing
158
+ * `<helix-color-picker name="brand" required>` inside a form has every reason
159
+ * to expect the form to refuse an empty one. Its two siblings in the same
160
+ * composition position, Date Picker and Time Picker, both check it.
161
+ *
162
+ * Written out rather than taken from `MixinField`, for the reason those two
163
+ * record: the mixin brings a label, a hint and a feedback region, and this is
164
+ * a panel composed *into* a field that already has all three, so taking it
165
+ * would give one control two of each. What is left is the validity call and
166
+ * the anchor the browser points its bubble at.
167
+ *
168
+ * **Emptiness is the only fault reported, and bad input deliberately is
169
+ * not.** The value row is typed into a character at a time and every
170
+ * keystroke reaches `requestValue`, so `#0`, `#00` and `#00f` would each be a
171
+ * reported fault on the way to `#00ff00`. Date Picker and Time Picker can
172
+ * report bad input because their value moves only on a commit. Color Input
173
+ * reports it for this family instead, once, against the field the reader
174
+ * typed into.
175
+ */
176
+ required: boolean;
131
177
  /**
132
178
  * Colors offered under the picker, as a comma-separated list.
133
179
  */
@@ -183,6 +229,8 @@ export declare class HelixHeadlessColorPicker extends HelixHeadlessColorPicker_b
183
229
  private settled;
184
230
  /** Takes what was typed, and lets the square follow only a whole color. */
185
231
  private readonly handleValueInput;
232
+ /** Refuses every change while read-only. See `readOnly`. */
233
+ requestValue(next: string): boolean;
186
234
  /** Moves part of the model and announces the value that follows from it. */
187
235
  private readonly move;
188
236
  /**
@@ -237,11 +285,24 @@ export declare class HelixHeadlessColorPicker extends HelixHeadlessColorPicker_b
237
285
  *
238
286
  * `null` rather than an empty string where there is no color, which is the
239
287
  * difference between a control that submits nothing and one that submits a
240
- * blank. There is no `setValidity` beside it: this element does not take
241
- * `MixinField`, and every validity member of `ElementInternals` is the
242
- * consumer's to reach through the field that wraps it.
288
+ * blank.
289
+ *
290
+ * The verdict goes beside it, because `required` is this element's own claim
291
+ * and nothing else can answer it: a picker standing on a page has no field
292
+ * around it to check on its behalf. What it is *not* is a second opinion on
293
+ * everything else, which is why bad input is left to Color Input. See
294
+ * `required`.
243
295
  */
244
296
  private sync;
297
+ /**
298
+ * Reports "required and empty", or clears it.
299
+ *
300
+ * Anchored on the square, which is where a reader would go to answer: it is
301
+ * the first control in the panel and the one `focus` puts the keyboard on, so
302
+ * the browser's bubble points at the thing to do rather than at the row that
303
+ * states the result.
304
+ */
305
+ private reportRequired;
245
306
  protected updated(changed: import('lit').PropertyValues): void;
246
307
  /** Puts the keyboard on the square, which is the control a reader wants. */
247
308
  focus(options?: FocusOptions): void;
@@ -262,6 +323,20 @@ export declare class HelixHeadlessColorPicker extends HelixHeadlessColorPicker_b
262
323
  * event and not this component's.
263
324
  */
264
325
  private takeArea;
326
+ /**
327
+ * Returns a control that moved itself to what the model still holds.
328
+ *
329
+ * **A refusal has to undo something, and here it is a handle.** Every other
330
+ * control in this picker is drawn from the model, so refusing the change
331
+ * leaves the picture right on its own. The two area ranges and the two
332
+ * channel sliders are not: each moved before it announced, and each is bound
333
+ * with `.value`, which lit dirty-checks against the value it last committed.
334
+ * The model never changed, so that binding writes nothing and the handle
335
+ * stays where the reader dragged it, on a control that refused. Carousel
336
+ * settles the same question the same way: a refusal puts the track back on
337
+ * the slide the index still holds.
338
+ */
339
+ private putBack;
265
340
  /**
266
341
  * A channel's new value, read off the slider that just applied it.
267
342
  *
@@ -24,12 +24,17 @@ let HelixHeadlessColorPicker = class HelixHeadlessColorPicker extends MixinContr
24
24
  this.noEyedropper = false;
25
25
  this.noFormat = false;
26
26
  this.noValue = false;
27
+ this.readOnly = false;
28
+ this.required = false;
27
29
  this.swatches = '';
28
30
  this.sampling = false;
29
31
  this.owned = false;
30
32
  this.settled = '';
31
33
  this.handleValueInput = (event) => {
32
34
  event.stopPropagation();
35
+ if (this.readOnly) {
36
+ return;
37
+ }
33
38
  const typed = event.target.value;
34
39
  const parsed = parseColor(typed);
35
40
  if (parsed) {
@@ -39,6 +44,9 @@ let HelixHeadlessColorPicker = class HelixHeadlessColorPicker extends MixinContr
39
44
  this.requestValue(typed);
40
45
  };
41
46
  this.move = (next) => {
47
+ if (this.readOnly) {
48
+ return;
49
+ }
42
50
  this.moved = { ...this.color, ...next };
43
51
  this.owned = true;
44
52
  this.requestValue(formatColor(this.moved, this.format ?? 'hex', this.alpha));
@@ -51,6 +59,9 @@ let HelixHeadlessColorPicker = class HelixHeadlessColorPicker extends MixinContr
51
59
  }
52
60
  };
53
61
  this.handleFormat = () => {
62
+ if (this.readOnly) {
63
+ return;
64
+ }
54
65
  this.format = FORMATS[(FORMATS.indexOf(this.format ?? 'hex') + 1) % FORMATS.length] ?? 'hex';
55
66
  if (this.value !== '') {
56
67
  this.move({});
@@ -102,12 +113,26 @@ let HelixHeadlessColorPicker = class HelixHeadlessColorPicker extends MixinContr
102
113
  get presets() {
103
114
  return splitColors(this.swatches).filter((one) => parseColor(one) !== null);
104
115
  }
116
+ requestValue(next) {
117
+ if (this.readOnly) {
118
+ return false;
119
+ }
120
+ return super.requestValue(next);
121
+ }
105
122
  firstUpdated(changed) {
106
123
  super.firstUpdated(changed);
107
124
  this.finishHydration();
108
125
  }
109
126
  sync() {
110
127
  this.internals.setFormValue(this.value === '' ? null : this.value);
128
+ this.reportRequired();
129
+ }
130
+ reportRequired() {
131
+ if (this.required && this.value === '') {
132
+ this.internals.setValidity({ valueMissing: true }, 'Choose a color.', this.partNamed('grid') ?? undefined);
133
+ return;
134
+ }
135
+ this.internals.setValidity({});
111
136
  }
112
137
  updated(changed) {
113
138
  super.updated(changed);
@@ -145,13 +170,24 @@ let HelixHeadlessColorPicker = class HelixHeadlessColorPicker extends MixinContr
145
170
  }
146
171
  takeArea(axis, event) {
147
172
  event.stopPropagation();
148
- const { value } = event.target;
149
- this.move({ [axis]: Number(value) });
173
+ const control = event.target;
174
+ if (this.readOnly) {
175
+ this.putBack(control, this.color[axis]);
176
+ return;
177
+ }
178
+ this.move({ [axis]: Number(control.value) });
179
+ }
180
+ putBack(control, at) {
181
+ control.value = String(at);
150
182
  }
151
183
  takeChannel(channel, event) {
152
184
  event.stopPropagation();
153
- const { value } = event.target;
154
- this.move({ [channel]: Number(value) });
185
+ const control = event.target;
186
+ if (this.readOnly) {
187
+ this.putBack(control, this.color[channel]);
188
+ return;
189
+ }
190
+ this.move({ [channel]: Number(control.value) });
155
191
  }
156
192
  willUpdate(changed) {
157
193
  super.willUpdate(changed);
@@ -248,6 +284,7 @@ let HelixHeadlessColorPicker = class HelixHeadlessColorPicker extends MixinContr
248
284
  spellcheck="false"
249
285
  .value=${this.value}
250
286
  ?disabled=${this.isDisabled}
287
+ ?readonly=${this.readOnly}
251
288
  @input=${this.handleValueInput}
252
289
  />`}
253
290
  ${this.tools ? html `${this.renderTools()}` : nothing}
@@ -292,6 +329,12 @@ __decorate([
292
329
  __decorate([
293
330
  property({ attribute: 'no-value', type: Boolean })
294
331
  ], HelixHeadlessColorPicker.prototype, "noValue", void 0);
332
+ __decorate([
333
+ property({ attribute: 'readonly', reflect: true, type: Boolean })
334
+ ], HelixHeadlessColorPicker.prototype, "readOnly", void 0);
335
+ __decorate([
336
+ property({ reflect: true, type: Boolean })
337
+ ], HelixHeadlessColorPicker.prototype, "required", void 0);
295
338
  __decorate([
296
339
  property()
297
340
  ], HelixHeadlessColorPicker.prototype, "swatches", void 0);
@@ -17,7 +17,7 @@ import { type PropertyValues, type TemplateResult } from 'lit';
17
17
  * with headings close together several are in it at once.
18
18
  *
19
19
  * @since 0.1.0
20
- * @status experimental
20
+ * @status stable
21
21
  * @category navigation
22
22
  * @aliases Table of contents, On this page, Outline
23
23
  * @documentation https://fusion.dev/helix/components/outline