@fusion.dev/helix-core 0.13.1 → 0.14.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.
@@ -7045,6 +7045,40 @@
7045
7045
  },
7046
7046
  "description": "How many tokens to show before the rest become a count.\n\n`0` is all of them, and is the default: a cap that guesses is worse than no\ncap, because the number that fits depends on the width of the field and the\nlength of what is in it, neither of which this element knows. What a cap is\ngenuinely for is a field in a row or a table cell, where a set of twelve\nchoices would push everything below it down a line at a time.\n\nThe remainder is counted rather than hidden. `+3` is a fact a reader can act\non, where a row that silently stops is a field that appears to have lost\nwhat was put in it. Date Input and Time Input carry the same property and\nthe same reasoning without taking this mixin, which is why the name is\ntheirs rather than a fourth word for one idea."
7047
7047
  },
7048
+ {
7049
+ "kind": "field",
7050
+ "name": "expandable",
7051
+ "type": {
7052
+ "text": "boolean"
7053
+ },
7054
+ "description": "Whether the count is a control that shows the rest.\n\n**Off by default, and that is about focus rather than caution.** The count\nbecomes a real button when this is set, which is a new stop in the tab\norder inside the field. Combobox's whole arrangement is that focus stays in\nthe text while the arrows work the list, so a button appearing between the\ntags and the caret is a change to its keyboard model and not a detail. A\nhost that wants it asks.\n\nLeft off, the count stays the `<span>` it has always been: no tab stop, no\nbehavior, and nothing a consumer is holding changes."
7055
+ },
7056
+ {
7057
+ "kind": "field",
7058
+ "name": "expandLabel",
7059
+ "type": {
7060
+ "text": "string"
7061
+ },
7062
+ "description": "What the collapsed count is called, since `+3` names nothing."
7063
+ },
7064
+ {
7065
+ "kind": "field",
7066
+ "name": "collapseLabel",
7067
+ "type": {
7068
+ "text": "string"
7069
+ },
7070
+ "description": "What the control says once everything is showing.\n\nA word rather than a count, because there is no longer a number to give:\nexpanded, nothing is hidden. It is the visible text and the accessible name\ntogether, which is why it is a phrase and not a glyph."
7071
+ },
7072
+ {
7073
+ "kind": "field",
7074
+ "name": "shownLimit",
7075
+ "type": {
7076
+ "text": "number"
7077
+ },
7078
+ "privacy": "protected",
7079
+ "readonly": true,
7080
+ "description": "The cap in force right now, which is none while the reader has expanded it.\n\nHosts that slice their own tokens read this rather than `maxTags`, or\nexpanding would hide the very tokens it was asked to show."
7081
+ },
7048
7082
  {
7049
7083
  "kind": "method",
7050
7084
  "name": "renderTokenRow",
@@ -7133,6 +7167,36 @@
7133
7167
  "description": "A row of tokens inside a field, and the behavior that goes with it.\n\n**This owns the row and knows nothing about what a token is.** Not where they\ncame from, not whether they were chosen from a list or invented, not what\nremoving one means. Combobox's tokens are a view of its selection and carry a\n`data-value` it reads back; a filter field's are whatever a reader typed.\nBoth get the same row, the same overflow count and the same Backspace.\n\n**What it does not own is where the tokens come from, and that is not a gap.**\nCombobox renders its tags into its own shadow root from the options it was\ngiven; a filter field takes them as slotted children of the host. A slot\ncannot assign a shadow root's own children, so one route cannot serve both,\nand pretending otherwise would put a mode inside the mixin to hide a\ndifference that is real. The host passes what it has and this draws the row\naround it.\n\n**The control stays in the host's shadow root**, which is why this is a mixin\nand not an element to nest. Combobox reaches `[part=\"control\"]` four times,\nfor focus twice, for the floating anchor's width, and to set the selection\nrange across a completion. A shadow root does not pierce a child's, so a\nnested field holding that control would answer `null` to all four.",
7134
7168
  "name": "MixinTokens",
7135
7169
  "members": [
7170
+ {
7171
+ "kind": "field",
7172
+ "name": "collapseLabel",
7173
+ "type": {
7174
+ "text": "string"
7175
+ },
7176
+ "default": "'Show fewer'",
7177
+ "description": "What the control says once everything is showing.",
7178
+ "attribute": "collapse-label"
7179
+ },
7180
+ {
7181
+ "kind": "field",
7182
+ "name": "expandable",
7183
+ "type": {
7184
+ "text": "boolean"
7185
+ },
7186
+ "default": "false",
7187
+ "description": "Whether the count is a control that shows the rest.",
7188
+ "attribute": "expandable"
7189
+ },
7190
+ {
7191
+ "kind": "field",
7192
+ "name": "expandLabel",
7193
+ "type": {
7194
+ "text": "string"
7195
+ },
7196
+ "default": "'Show all'",
7197
+ "description": "What the collapsed count is called, since `+3` names nothing.",
7198
+ "attribute": "expand-label"
7199
+ },
7136
7200
  {
7137
7201
  "kind": "field",
7138
7202
  "name": "maxTags",
@@ -7143,6 +7207,32 @@
7143
7207
  "description": "How many tokens to show before the rest become a count.",
7144
7208
  "attribute": "max-tags"
7145
7209
  },
7210
+ {
7211
+ "kind": "field",
7212
+ "name": "expanded",
7213
+ "type": {
7214
+ "text": "boolean"
7215
+ },
7216
+ "privacy": "private",
7217
+ "default": "false",
7218
+ "description": "Whether the reader has asked for all of them.\n\nState and private, the way `Input`'s `revealed` is: it is a reader's\nmomentary choice about what is on their own screen, it must not survive\nanything, and a consumer who wants the field opened is asking for\n`max-tags=\"0\"` rather than for a second way to say it."
7219
+ },
7220
+ {
7221
+ "kind": "field",
7222
+ "name": "shownLimit",
7223
+ "type": {
7224
+ "text": "number"
7225
+ },
7226
+ "privacy": "protected",
7227
+ "readonly": true
7228
+ },
7229
+ {
7230
+ "kind": "field",
7231
+ "name": "toggleExpanded",
7232
+ "privacy": "private",
7233
+ "readonly": true,
7234
+ "description": "Turns the cap off and on, which is all expanding is."
7235
+ },
7146
7236
  {
7147
7237
  "kind": "method",
7148
7238
  "name": "renderTokenRow",
@@ -7167,6 +7257,25 @@
7167
7257
  }
7168
7258
  ]
7169
7259
  },
7260
+ {
7261
+ "kind": "method",
7262
+ "name": "renderRest",
7263
+ "privacy": "private",
7264
+ "return": {
7265
+ "type": {
7266
+ "text": "TemplateResult | typeof nothing"
7267
+ }
7268
+ },
7269
+ "parameters": [
7270
+ {
7271
+ "name": "over",
7272
+ "type": {
7273
+ "text": "number"
7274
+ }
7275
+ }
7276
+ ],
7277
+ "description": "The count, as a control where the host asked for one and a word otherwise.\n\n**`part=\"rest\"` either way**, so a consumer's `::part(rest)` keeps working\nacross the change. The element under it is different, which is the point:\na `<span>` that looks like an affordance and answers nothing is the fault\nthis exists to fix, and the way to fix it is a real button rather than a\nclick handler on text.\n\n**Expanded, it renders on no count.** `shownLimit` is zero while expanded,\nso `over` is zero too, and gating the control on the number would take\naway the only way back."
7278
+ },
7170
7279
  {
7171
7280
  "kind": "method",
7172
7281
  "name": "hideOverflow",
@@ -7221,6 +7330,33 @@
7221
7330
  }
7222
7331
  ],
7223
7332
  "attributes": [
7333
+ {
7334
+ "name": "collapse-label",
7335
+ "type": {
7336
+ "text": "string"
7337
+ },
7338
+ "default": "'Show fewer'",
7339
+ "description": "What the control says once everything is showing.",
7340
+ "fieldName": "collapseLabel"
7341
+ },
7342
+ {
7343
+ "name": "expandable",
7344
+ "type": {
7345
+ "text": "boolean"
7346
+ },
7347
+ "default": "false",
7348
+ "description": "Whether the count is a control that shows the rest.",
7349
+ "fieldName": "expandable"
7350
+ },
7351
+ {
7352
+ "name": "expand-label",
7353
+ "type": {
7354
+ "text": "string"
7355
+ },
7356
+ "default": "'Show all'",
7357
+ "description": "What the collapsed count is called, since `+3` names nothing.",
7358
+ "fieldName": "expandLabel"
7359
+ },
7224
7360
  {
7225
7361
  "name": "max-tags",
7226
7362
  "type": {
@@ -24,6 +24,39 @@ export declare class MixinTokensInterface {
24
24
  * theirs rather than a fourth word for one idea.
25
25
  */
26
26
  maxTags: number;
27
+ /**
28
+ * Whether the count is a control that shows the rest.
29
+ *
30
+ * **Off by default, and that is about focus rather than caution.** The count
31
+ * becomes a real button when this is set, which is a new stop in the tab
32
+ * order inside the field. Combobox's whole arrangement is that focus stays in
33
+ * the text while the arrows work the list, so a button appearing between the
34
+ * tags and the caret is a change to its keyboard model and not a detail. A
35
+ * host that wants it asks.
36
+ *
37
+ * Left off, the count stays the `<span>` it has always been: no tab stop, no
38
+ * behavior, and nothing a consumer is holding changes.
39
+ */
40
+ expandable: boolean;
41
+ /**
42
+ * What the collapsed count is called, since `+3` names nothing.
43
+ */
44
+ expandLabel: string;
45
+ /**
46
+ * What the control says once everything is showing.
47
+ *
48
+ * A word rather than a count, because there is no longer a number to give:
49
+ * expanded, nothing is hidden. It is the visible text and the accessible name
50
+ * together, which is why it is a phrase and not a glyph.
51
+ */
52
+ collapseLabel: string;
53
+ /**
54
+ * The cap in force right now, which is none while the reader has expanded it.
55
+ *
56
+ * Hosts that slice their own tokens read this rather than `maxTags`, or
57
+ * expanding would hide the very tokens it was asked to show.
58
+ */
59
+ protected readonly shownLimit: number;
27
60
  /**
28
61
  * The row, given what goes in it and how many did not fit.
29
62
  *
@@ -5,16 +5,40 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
7
  import { html, nothing } from 'lit';
8
- import { property } from 'lit/decorators.js';
8
+ import { property, state } from 'lit/decorators.js';
9
9
  export const MixinTokens = (Base) => {
10
10
  class TokensElement extends Base {
11
11
  constructor() {
12
12
  super(...arguments);
13
+ this.collapseLabel = 'Show fewer';
14
+ this.expandable = false;
15
+ this.expandLabel = 'Show all';
13
16
  this.maxTags = 0;
17
+ this.expanded = false;
18
+ this.toggleExpanded = () => {
19
+ this.expanded = !this.expanded;
20
+ };
21
+ }
22
+ get shownLimit() {
23
+ return this.expanded ? 0 : this.maxTags;
14
24
  }
15
25
  renderTokenRow(content, over) {
16
- return html `<span part="tags"
17
- >${content}${over > 0 ? html `<span part="rest">${`+${over}`}</span>` : nothing}</span
26
+ return html `<span part="tags">${content}${this.renderRest(over)}</span>`;
27
+ }
28
+ renderRest(over) {
29
+ if (!this.expandable) {
30
+ return over > 0 ? html `<span part="rest">${`+${over}`}</span>` : nothing;
31
+ }
32
+ if (over < 1 && !this.expanded) {
33
+ return nothing;
34
+ }
35
+ return html `<button
36
+ part="rest"
37
+ type="button"
38
+ aria-expanded=${this.expanded ? 'true' : 'false'}
39
+ aria-label=${this.expanded ? nothing : this.expandLabel}
40
+ @click=${this.toggleExpanded}
41
+ >${this.expanded ? this.collapseLabel : `+${over}`}</button
18
42
  >`;
19
43
  }
20
44
  hideOverflow(slot) {
@@ -22,10 +46,11 @@ export const MixinTokens = (Base) => {
22
46
  return 0;
23
47
  }
24
48
  const tokens = slot.assignedElements({ flatten: true });
49
+ const limit = this.shownLimit;
25
50
  for (const [at, token] of tokens.entries()) {
26
- token.toggleAttribute('hidden', this.maxTags > 0 && at >= this.maxTags);
51
+ token.toggleAttribute('hidden', limit > 0 && at >= limit);
27
52
  }
28
- return this.maxTags > 0 ? Math.max(0, tokens.length - this.maxTags) : 0;
53
+ return limit > 0 ? Math.max(0, tokens.length - limit) : 0;
29
54
  }
30
55
  tokenKeydown(event, typed) {
31
56
  if (event.key !== 'Backspace' || typed !== '') {
@@ -37,8 +62,20 @@ export const MixinTokens = (Base) => {
37
62
  }
38
63
  removeLastToken() { }
39
64
  }
65
+ __decorate([
66
+ property({ attribute: 'collapse-label' })
67
+ ], TokensElement.prototype, "collapseLabel", void 0);
68
+ __decorate([
69
+ property({ type: Boolean })
70
+ ], TokensElement.prototype, "expandable", void 0);
71
+ __decorate([
72
+ property({ attribute: 'expand-label' })
73
+ ], TokensElement.prototype, "expandLabel", void 0);
40
74
  __decorate([
41
75
  property({ attribute: 'max-tags', type: Number })
42
76
  ], TokensElement.prototype, "maxTags", void 0);
77
+ __decorate([
78
+ state()
79
+ ], TokensElement.prototype, "expanded", void 0);
43
80
  return TokensElement;
44
81
  };
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.13.1",
4
+ "version": "0.14.0",
5
5
  "license": "MIT",
6
6
  "author": "Ross Sheppard <ross.sheppard@fusion.dev>",
7
7
  "repository": {
@@ -34,12 +34,12 @@
34
34
  "bugs": "https://github.com/project-fusion/fusion/issues",
35
35
  "dependencies": {
36
36
  "@floating-ui/dom": "^1.8.0",
37
- "@fusion.dev/helix-icons": "0.13.1"
37
+ "@fusion.dev/helix-icons": "0.14.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@custom-elements-manifest/analyzer": "^0.11.0",
41
- "@fusion.dev/helix-cem": "0.13.1",
42
- "@fusion.dev/helix-testing": "0.13.1",
41
+ "@fusion.dev/helix-cem": "0.14.0",
42
+ "@fusion.dev/helix-testing": "0.14.0",
43
43
  "lit": "^3.2.1"
44
44
  },
45
45
  "homepage": "https://fusion.dev",