@fusion.dev/helix-core 0.12.1 → 0.12.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4529,6 +4529,22 @@
4529
4529
  "module": "./mixins/mixin-field/mixin-field.js"
4530
4530
  }
4531
4531
  },
4532
+ {
4533
+ "kind": "js",
4534
+ "name": "MixinTokens",
4535
+ "declaration": {
4536
+ "name": "MixinTokens",
4537
+ "module": "./mixins/mixin-tokens/mixin-tokens.js"
4538
+ }
4539
+ },
4540
+ {
4541
+ "kind": "js",
4542
+ "name": "MixinTokensInterface",
4543
+ "declaration": {
4544
+ "name": "MixinTokensInterface",
4545
+ "module": "./mixins/mixin-tokens/mixin-tokens.js"
4546
+ }
4547
+ },
4532
4548
  {
4533
4549
  "kind": "js",
4534
4550
  "name": "anchorElement",
@@ -7012,6 +7028,238 @@
7012
7028
  }
7013
7029
  ]
7014
7030
  },
7031
+ {
7032
+ "kind": "javascript-module",
7033
+ "path": "src/mixins/mixin-tokens/mixin-tokens.ts",
7034
+ "declarations": [
7035
+ {
7036
+ "kind": "class",
7037
+ "description": "The public surface `MixinTokens` adds.\n\nDeclared separately for the reason `MixinFieldInterface` gives: TypeScript\ncannot emit declarations for an anonymous class holding private members.",
7038
+ "name": "MixinTokensInterface",
7039
+ "members": [
7040
+ {
7041
+ "kind": "field",
7042
+ "name": "maxTags",
7043
+ "type": {
7044
+ "text": "number"
7045
+ },
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
+ },
7048
+ {
7049
+ "kind": "method",
7050
+ "name": "renderTokenRow",
7051
+ "privacy": "protected",
7052
+ "return": {
7053
+ "type": {
7054
+ "text": "TemplateResult | typeof nothing"
7055
+ }
7056
+ },
7057
+ "parameters": [
7058
+ {
7059
+ "name": "content",
7060
+ "type": {
7061
+ "text": "unknown"
7062
+ }
7063
+ },
7064
+ {
7065
+ "name": "over",
7066
+ "type": {
7067
+ "text": "number"
7068
+ }
7069
+ }
7070
+ ],
7071
+ "description": "The row, given what goes in it and how many did not fit.\n\nThe host supplies the content because the two hosts get their tokens from\ndifferent places, and neither can use the other's route."
7072
+ },
7073
+ {
7074
+ "kind": "method",
7075
+ "name": "hideOverflow",
7076
+ "privacy": "protected",
7077
+ "return": {
7078
+ "type": {
7079
+ "text": "number"
7080
+ }
7081
+ },
7082
+ "parameters": [
7083
+ {
7084
+ "name": "slot",
7085
+ "type": {
7086
+ "text": "HTMLSlotElement | null | undefined"
7087
+ }
7088
+ }
7089
+ ],
7090
+ "description": "Hides what is over the limit, for a host whose tokens are slotted.\n\n`hidden` rather than removal, because these are the consumer's own nodes\nand this mixin is not entitled to take them out of their document. Returns\nhow many were hidden, which is what the row's count renders."
7091
+ },
7092
+ {
7093
+ "kind": "method",
7094
+ "name": "tokenKeydown",
7095
+ "privacy": "protected",
7096
+ "return": {
7097
+ "type": {
7098
+ "text": "boolean"
7099
+ }
7100
+ },
7101
+ "parameters": [
7102
+ {
7103
+ "name": "event",
7104
+ "type": {
7105
+ "text": "KeyboardEvent"
7106
+ }
7107
+ },
7108
+ {
7109
+ "name": "typed",
7110
+ "type": {
7111
+ "text": "string"
7112
+ }
7113
+ }
7114
+ ],
7115
+ "description": "Backspace on an empty control, answered.\n\n**`typed` is passed rather than read off the control, and that is not\ntidiness.** Combobox writes completions into its own control behind Lit's\nback, so `control.value` holds a suggestion while nothing has been typed:\nreading the control there would refuse a Backspace the reader is entitled\nto. The host knows which of the two it means.\n\nReturns whether it took the key, so a host carries on to its own handling\nwhen it did not."
7116
+ },
7117
+ {
7118
+ "kind": "method",
7119
+ "name": "removeLastToken",
7120
+ "privacy": "protected",
7121
+ "return": {
7122
+ "type": {
7123
+ "text": "void"
7124
+ }
7125
+ },
7126
+ "description": "What the host does when the last token is asked to go.\n\nThe policy is shared and the mechanism is not: a combobox drops a value out\nof its selection, and a filter field takes a token out of its own list."
7127
+ }
7128
+ ],
7129
+ "helix": {}
7130
+ },
7131
+ {
7132
+ "kind": "mixin",
7133
+ "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
+ "name": "MixinTokens",
7135
+ "members": [
7136
+ {
7137
+ "kind": "field",
7138
+ "name": "maxTags",
7139
+ "type": {
7140
+ "text": "number"
7141
+ },
7142
+ "default": "0",
7143
+ "description": "How many tokens to show before the rest become a count.",
7144
+ "attribute": "max-tags"
7145
+ },
7146
+ {
7147
+ "kind": "method",
7148
+ "name": "renderTokenRow",
7149
+ "privacy": "protected",
7150
+ "return": {
7151
+ "type": {
7152
+ "text": "TemplateResult | typeof nothing"
7153
+ }
7154
+ },
7155
+ "parameters": [
7156
+ {
7157
+ "name": "content",
7158
+ "type": {
7159
+ "text": "unknown"
7160
+ }
7161
+ },
7162
+ {
7163
+ "name": "over",
7164
+ "type": {
7165
+ "text": "number"
7166
+ }
7167
+ }
7168
+ ]
7169
+ },
7170
+ {
7171
+ "kind": "method",
7172
+ "name": "hideOverflow",
7173
+ "privacy": "protected",
7174
+ "return": {
7175
+ "type": {
7176
+ "text": "number"
7177
+ }
7178
+ },
7179
+ "parameters": [
7180
+ {
7181
+ "name": "slot",
7182
+ "type": {
7183
+ "text": "HTMLSlotElement | null | undefined"
7184
+ }
7185
+ }
7186
+ ]
7187
+ },
7188
+ {
7189
+ "kind": "method",
7190
+ "name": "tokenKeydown",
7191
+ "privacy": "protected",
7192
+ "return": {
7193
+ "type": {
7194
+ "text": "boolean"
7195
+ }
7196
+ },
7197
+ "parameters": [
7198
+ {
7199
+ "name": "event",
7200
+ "type": {
7201
+ "text": "KeyboardEvent"
7202
+ }
7203
+ },
7204
+ {
7205
+ "name": "typed",
7206
+ "type": {
7207
+ "text": "string"
7208
+ }
7209
+ }
7210
+ ]
7211
+ },
7212
+ {
7213
+ "kind": "method",
7214
+ "name": "removeLastToken",
7215
+ "privacy": "protected",
7216
+ "return": {
7217
+ "type": {
7218
+ "text": "void"
7219
+ }
7220
+ }
7221
+ }
7222
+ ],
7223
+ "attributes": [
7224
+ {
7225
+ "name": "max-tags",
7226
+ "type": {
7227
+ "text": "number"
7228
+ },
7229
+ "default": "0",
7230
+ "description": "How many tokens to show before the rest become a count.",
7231
+ "fieldName": "maxTags"
7232
+ }
7233
+ ],
7234
+ "parameters": [
7235
+ {
7236
+ "name": "Base",
7237
+ "type": {
7238
+ "text": "T"
7239
+ }
7240
+ }
7241
+ ]
7242
+ }
7243
+ ],
7244
+ "exports": [
7245
+ {
7246
+ "kind": "js",
7247
+ "name": "MixinTokensInterface",
7248
+ "declaration": {
7249
+ "name": "MixinTokensInterface",
7250
+ "module": "src/mixins/mixin-tokens/mixin-tokens.ts"
7251
+ }
7252
+ },
7253
+ {
7254
+ "kind": "js",
7255
+ "name": "MixinTokens",
7256
+ "declaration": {
7257
+ "name": "MixinTokens",
7258
+ "module": "src/mixins/mixin-tokens/mixin-tokens.ts"
7259
+ }
7260
+ }
7261
+ ]
7262
+ },
7015
7263
  {
7016
7264
  "kind": "javascript-module",
7017
7265
  "path": "src/utils/anchor-element/anchor-element.ts",
package/dist/index.d.ts CHANGED
@@ -25,6 +25,7 @@ export { MixinControllerOpen, type MixinControllerOpenInterface, } from './mixin
25
25
  export { MixinControllerValue, type MixinControllerValueInterface, } from './mixins/mixin-controller-value/mixin-controller-value.js';
26
26
  export { type DismissibleElement, MixinDismissible, } from './mixins/mixin-dismissible/mixin-dismissible.js';
27
27
  export { MixinField, type MixinFieldInterface } from './mixins/mixin-field/mixin-field.js';
28
+ export { MixinTokens, type MixinTokensInterface } from './mixins/mixin-tokens/mixin-tokens.js';
28
29
  export { anchorElement } from './utils/anchor-element/anchor-element.js';
29
30
  export { capturePointer } from './utils/capture-pointer/capture-pointer.js';
30
31
  export { closeMarkdown } from './utils/close-markdown/close-markdown.js';
package/dist/index.js CHANGED
@@ -25,6 +25,7 @@ export { MixinControllerOpen, } from './mixins/mixin-controller-open/mixin-contr
25
25
  export { MixinControllerValue, } from './mixins/mixin-controller-value/mixin-controller-value.js';
26
26
  export { MixinDismissible, } from './mixins/mixin-dismissible/mixin-dismissible.js';
27
27
  export { MixinField } from './mixins/mixin-field/mixin-field.js';
28
+ export { MixinTokens } from './mixins/mixin-tokens/mixin-tokens.js';
28
29
  export { anchorElement } from './utils/anchor-element/anchor-element.js';
29
30
  export { capturePointer } from './utils/capture-pointer/capture-pointer.js';
30
31
  export { closeMarkdown } from './utils/close-markdown/close-markdown.js';
@@ -0,0 +1,87 @@
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
+ /**
5
+ * The public surface `MixinTokens` adds.
6
+ *
7
+ * Declared separately for the reason `MixinFieldInterface` gives: TypeScript
8
+ * cannot emit declarations for an anonymous class holding private members.
9
+ */
10
+ export declare class MixinTokensInterface {
11
+ /**
12
+ * How many tokens to show before the rest become a count.
13
+ *
14
+ * `0` is all of them, and is the default: a cap that guesses is worse than no
15
+ * cap, because the number that fits depends on the width of the field and the
16
+ * length of what is in it, neither of which this element knows. What a cap is
17
+ * genuinely for is a field in a row or a table cell, where a set of twelve
18
+ * choices would push everything below it down a line at a time.
19
+ *
20
+ * The remainder is counted rather than hidden. `+3` is a fact a reader can act
21
+ * on, where a row that silently stops is a field that appears to have lost
22
+ * what was put in it. Date Input and Time Input carry the same property and
23
+ * the same reasoning without taking this mixin, which is why the name is
24
+ * theirs rather than a fourth word for one idea.
25
+ */
26
+ maxTags: number;
27
+ /**
28
+ * The row, given what goes in it and how many did not fit.
29
+ *
30
+ * The host supplies the content because the two hosts get their tokens from
31
+ * different places, and neither can use the other's route.
32
+ */
33
+ protected renderTokenRow(content: unknown, over: number): TemplateResult | typeof nothing;
34
+ /**
35
+ * Hides what is over the limit, for a host whose tokens are slotted.
36
+ *
37
+ * `hidden` rather than removal, because these are the consumer's own nodes
38
+ * and this mixin is not entitled to take them out of their document. Returns
39
+ * how many were hidden, which is what the row's count renders.
40
+ */
41
+ protected hideOverflow(slot: HTMLSlotElement | null | undefined): number;
42
+ /**
43
+ * Backspace on an empty control, answered.
44
+ *
45
+ * **`typed` is passed rather than read off the control, and that is not
46
+ * tidiness.** Combobox writes completions into its own control behind Lit's
47
+ * back, so `control.value` holds a suggestion while nothing has been typed:
48
+ * reading the control there would refuse a Backspace the reader is entitled
49
+ * to. The host knows which of the two it means.
50
+ *
51
+ * Returns whether it took the key, so a host carries on to its own handling
52
+ * when it did not.
53
+ */
54
+ protected tokenKeydown(event: KeyboardEvent, typed: string): boolean;
55
+ /**
56
+ * What the host does when the last token is asked to go.
57
+ *
58
+ * The policy is shared and the mechanism is not: a combobox drops a value out
59
+ * of its selection, and a filter field takes a token out of its own list.
60
+ */
61
+ protected removeLastToken(): void;
62
+ }
63
+ /**
64
+ * A row of tokens inside a field, and the behavior that goes with it.
65
+ *
66
+ * **This owns the row and knows nothing about what a token is.** Not where they
67
+ * came from, not whether they were chosen from a list or invented, not what
68
+ * removing one means. Combobox's tokens are a view of its selection and carry a
69
+ * `data-value` it reads back; a filter field's are whatever a reader typed.
70
+ * Both get the same row, the same overflow count and the same Backspace.
71
+ *
72
+ * **What it does not own is where the tokens come from, and that is not a gap.**
73
+ * Combobox renders its tags into its own shadow root from the options it was
74
+ * given; a filter field takes them as slotted children of the host. A slot
75
+ * cannot assign a shadow root's own children, so one route cannot serve both,
76
+ * and pretending otherwise would put a mode inside the mixin to hide a
77
+ * difference that is real. The host passes what it has and this draws the row
78
+ * around it.
79
+ *
80
+ * **The control stays in the host's shadow root**, which is why this is a mixin
81
+ * and not an element to nest. Combobox reaches `[part="control"]` four times,
82
+ * for focus twice, for the floating anchor's width, and to set the selection
83
+ * range across a completion. A shadow root does not pierce a child's, so a
84
+ * nested field holding that control would answer `null` to all four.
85
+ */
86
+ export declare const MixinTokens: <T extends Constructor<HelixElement>>(Base: T) => Constructor<MixinTokensInterface> & T;
87
+ export {};
@@ -0,0 +1,44 @@
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
+ export const MixinTokens = (Base) => {
10
+ class TokensElement extends Base {
11
+ constructor() {
12
+ super(...arguments);
13
+ this.maxTags = 0;
14
+ }
15
+ renderTokenRow(content, over) {
16
+ return html `<span part="tags"
17
+ >${content}${over > 0 ? html `<span part="rest">${`+${over}`}</span>` : nothing}</span
18
+ >`;
19
+ }
20
+ hideOverflow(slot) {
21
+ if (!slot) {
22
+ return 0;
23
+ }
24
+ const tokens = slot.assignedElements({ flatten: true });
25
+ for (const [at, token] of tokens.entries()) {
26
+ token.toggleAttribute('hidden', this.maxTags > 0 && at >= this.maxTags);
27
+ }
28
+ return this.maxTags > 0 ? Math.max(0, tokens.length - this.maxTags) : 0;
29
+ }
30
+ tokenKeydown(event, typed) {
31
+ if (event.key !== 'Backspace' || typed !== '') {
32
+ return false;
33
+ }
34
+ event.preventDefault();
35
+ this.removeLastToken();
36
+ return true;
37
+ }
38
+ removeLastToken() { }
39
+ }
40
+ __decorate([
41
+ property({ attribute: 'max-tags', type: Number })
42
+ ], TokensElement.prototype, "maxTags", void 0);
43
+ return TokensElement;
44
+ };
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.12.1",
4
+ "version": "0.12.3",
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.12.1"
37
+ "@fusion.dev/helix-icons": "0.12.3"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@custom-elements-manifest/analyzer": "^0.11.0",
41
- "@fusion.dev/helix-cem": "0.12.1",
42
- "@fusion.dev/helix-testing": "0.12.1",
41
+ "@fusion.dev/helix-cem": "0.12.3",
42
+ "@fusion.dev/helix-testing": "0.12.3",
43
43
  "lit": "^3.2.1"
44
44
  },
45
45
  "homepage": "https://fusion.dev",