@limetech/lime-elements 40.1.0 → 40.2.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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## [40.2.0](https://github.com/Lundalogik/lime-elements/compare/v40.1.1...v40.2.0) (2026-09-07)
2
+
3
+ ### Features
4
+
5
+
6
+ * **button-group:** support disabling individual buttons ([3fa1726](https://github.com/Lundalogik/lime-elements/commit/3fa1726c6ea8613ad53732d6fdc4ab1d786711d6))
7
+
8
+ ## [40.1.1](https://github.com/Lundalogik/lime-elements/compare/v40.1.0...v40.1.1) (2026-09-07)
9
+
10
+ ### Bug Fixes
11
+
12
+
13
+ * **picker:** clear the search query when an item is picked ([7692e87](https://github.com/Lundalogik/lime-elements/commit/7692e872e9cad63120349966013cac874437ac0a))
14
+
1
15
  ## [40.1.0](https://github.com/Lundalogik/lime-elements/compare/v40.0.3...v40.1.0) (2026-09-04)
2
16
 
3
17
  ### Features
@@ -3,7 +3,7 @@
3
3
  var index = require('./index-DYg_7kkT.js');
4
4
  var randomString = require('./random-string-BTzDB2ee.js');
5
5
 
6
- const buttonGroupCss = () => `@charset "UTF-8";*,*:before,*:after{box-sizing:border-box}:host(limel-button-group){display:flex;box-sizing:border-box;align-items:center;border-radius:3.75rem;background-color:var(--button-group-background-color, rgb(var(--contrast-400)));width:max-content;max-width:100%;flex-wrap:nowrap}:host([disabled]:not([disabled=false])){cursor:not-allowed;opacity:0.4}.button{transition:color var(--limel-clickable-transition-speed, 0.4s) ease, background-color var(--limel-clickable-transition-speed, 0.4s) ease, box-shadow var(--limel-clickable-transform-speed, 0.4s) ease, transform var(--limel-clickable-transform-speed, 0.4s) var(--limel-clickable-transform-timing-function, ease);cursor:pointer;color:var(--limel-theme-on-surface-color);background-color:transparent}.button:hover,.button:focus,.button:focus-visible{will-change:color, background-color, box-shadow, transform}.button:hover,.button:focus-visible{transform:translate3d(0, -0.04rem, 0);color:var(--limel-theme-on-surface-color);background-color:var(--lime-elevated-surface-background-color)}.button:hover{box-shadow:var(--button-shadow-hovered)}.button:active{--limel-clickable-transform-timing-function:cubic-bezier( 0.83, -0.15, 0.49, 1.16 );transform:translate3d(0, 0.05rem, 0);background-color:var(--limel-theme-surface-background-color);box-shadow:var(--button-shadow-inset-pressed)}.button:hover,.button:active{--limel-clickable-transition-speed:0.2s;--limel-clickable-transform-speed:0.16s}.button:has(input[disabled]):hover{box-shadow:none;transform:none}.button{position:relative;display:inline-grid;grid-auto-flow:column;align-items:center;max-width:100%;min-width:2rem;height:2rem;padding:0 0.0625rem;margin:0.125rem;border-radius:1rem;font-size:var(--limel-theme-default-font-size);user-select:none}.button:not(:last-child):after{content:"";display:block;height:1rem;width:0.125rem;border-radius:0.25rem;background-color:var(--button-group-text-color, rgb(var(--contrast-1200)));opacity:0.1;position:absolute;right:-0.1875rem;top:0;bottom:0;margin:auto}.button label{cursor:pointer;transition:color 0.2s ease;display:flex;align-items:center;flex:1;height:100%;min-width:0}.button label:has(>limel-badge) .button__label{padding-right:0.25rem}.button input[type=radio]{position:absolute;width:0;opacity:0}.button input[type=radio]:focus-visible+label:after{content:"";display:block;position:absolute;top:0;right:0;bottom:0;left:0;border-radius:3.75rem;box-shadow:var(--shadow-depth-8-focused)}.button:focus-within{outline:none;color:var(--lime-primary-color, var(--limel-theme-primary-color))}.button:only-child .button__label{padding-left:0.75rem}.button:first-child .button__label{padding-left:0.75rem}.button.button--selected{background-color:var(--limel-theme-surface-background-color);box-shadow:var(--button-shadow-inset);color:var(--lime-primary-color, var(--limel-theme-primary-color))}.button.button--selected:active{box-shadow:var(--button-shadow-inset-pressed)}.button__label{overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-size:var(--limel-theme-default-small-font-size);padding:0 0.75rem}.button__icon{border-radius:50%;vertical-align:middle}limel-badge{margin-right:0.25rem;pointer-events:none}.button:not(.button--selected){--badge-background-color:rgb(var(--contrast-200))}`;
6
+ const buttonGroupCss = () => `@charset "UTF-8";*,*:before,*:after{box-sizing:border-box}:host(limel-button-group){display:flex;box-sizing:border-box;align-items:center;border-radius:3.75rem;background-color:var(--button-group-background-color, rgb(var(--contrast-400)));width:max-content;max-width:100%;flex-wrap:nowrap}.button{color:var(--limel-theme-on-surface-color)}.button:not(:has(:disabled)){transition:color var(--limel-clickable-transition-speed, 0.4s) ease, background-color var(--limel-clickable-transition-speed, 0.4s) ease, box-shadow var(--limel-clickable-transform-speed, 0.4s) ease, transform var(--limel-clickable-transform-speed, 0.4s) var(--limel-clickable-transform-timing-function, ease);cursor:pointer;color:var(--limel-theme-on-surface-color);background-color:transparent}.button:not(:has(:disabled)):hover,.button:not(:has(:disabled)):focus,.button:not(:has(:disabled)):focus-visible{will-change:color, background-color, box-shadow, transform}.button:not(:has(:disabled)):hover,.button:not(:has(:disabled)):focus-visible{transform:translate3d(0, -0.04rem, 0);color:var(--limel-theme-on-surface-color);background-color:var(--lime-elevated-surface-background-color)}.button:not(:has(:disabled)):hover{box-shadow:var(--button-shadow-hovered)}.button:not(:has(:disabled)):active{--limel-clickable-transform-timing-function:cubic-bezier( 0.83, -0.15, 0.49, 1.16 );transform:translate3d(0, 0.05rem, 0);background-color:var(--limel-theme-surface-background-color);box-shadow:var(--button-shadow-inset-pressed)}.button:not(:has(:disabled)):hover,.button:not(:has(:disabled)):active{--limel-clickable-transition-speed:0.2s;--limel-clickable-transform-speed:0.16s}.button{position:relative;display:inline-grid;grid-auto-flow:column;align-items:center;max-width:100%;min-width:2rem;height:2rem;padding:0 0.0625rem;margin:0.125rem;border-radius:1rem;font-size:var(--limel-theme-default-font-size);user-select:none}.button:not(:last-child):after{content:"";display:block;height:1rem;width:0.125rem;border-radius:0.25rem;background-color:var(--button-group-text-color, rgb(var(--contrast-1200)));opacity:0.1;position:absolute;right:-0.1875rem;top:0;bottom:0;margin:auto}.button label{cursor:pointer;transition:color 0.2s ease;display:flex;align-items:center;flex:1;height:100%;min-width:0}.button label:has(>limel-badge) .button__label{padding-right:0.25rem}.button:has(:disabled) label{cursor:not-allowed;opacity:0.4}.button input[type=radio]{position:absolute;width:0;opacity:0}.button input[type=radio]:focus-visible+label:after{content:"";display:block;position:absolute;top:0;right:0;bottom:0;left:0;border-radius:3.75rem;box-shadow:var(--shadow-depth-8-focused)}.button:focus-within{outline:none;color:var(--lime-primary-color, var(--limel-theme-primary-color))}.button:only-child .button__label{padding-left:0.75rem}.button:first-child .button__label{padding-left:0.75rem}.button.button--selected{background-color:var(--limel-theme-surface-background-color);box-shadow:var(--button-shadow-inset);color:var(--lime-primary-color, var(--limel-theme-primary-color))}.button.button--selected:not(:has(:disabled)):active{box-shadow:var(--button-shadow-inset-pressed)}.button__label{overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-size:var(--limel-theme-default-small-font-size);padding:0 0.75rem}.button__icon{border-radius:50%;vertical-align:middle}limel-badge{margin-right:0.25rem;pointer-events:none}.button:not(.button--selected){--badge-background-color:rgb(var(--contrast-200))}`;
7
7
 
8
8
  const ButtonGroup = class {
9
9
  constructor(hostRef) {
@@ -14,7 +14,10 @@ const ButtonGroup = class {
14
14
  */
15
15
  this.value = [];
16
16
  /**
17
- * True if the button-group should be disabled
17
+ * True if the button-group should be disabled.
18
+ *
19
+ * Disables every button in the group, regardless of the `disabled` value
20
+ * of each individual button.
18
21
  */
19
22
  this.disabled = false;
20
23
  this.radioGroupName = randomString.createRandomString();
@@ -32,17 +35,18 @@ const ButtonGroup = class {
32
35
  this.setSelectedButton();
33
36
  }
34
37
  render() {
35
- return (index.h(index.Host, { key: '867ce929125e09bbe9f0862e6cb68ad10f90a768', role: "radiogroup" }, this.value.map(this.renderButton)));
38
+ return (index.h(index.Host, { key: '7823fd4593ee4eddb6d56ce55e7ff5eb1cc28397', role: "radiogroup" }, this.value.map(this.renderButton)));
36
39
  }
37
40
  renderButton(button) {
38
41
  // Prefix with 'b' because html IDs cannot start with a digit,
39
42
  // and we need to differentiate from the ID on the limel-icon. /Ads
40
43
  const buttonId = `b${button.id}`;
44
+ const isDisabled = this.disabled || button.disabled;
41
45
  const classes = {
42
46
  button: true,
43
47
  'button--selected': this.isButtonChecked(button),
44
48
  };
45
- return (index.h("div", { class: classes }, index.h("input", { type: "radio", name: this.radioGroupName, checked: this.isButtonChecked(button), disabled: this.disabled, id: buttonId, onChange: this.onChange }), index.h("label", { htmlFor: buttonId }, this.renderContent(button), this.renderBadge(button))));
49
+ return (index.h("div", { class: classes }, index.h("input", { type: "radio", name: this.radioGroupName, checked: this.isButtonChecked(button), disabled: isDisabled, "aria-disabled": isDisabled ? 'true' : 'false', id: buttonId, onChange: this.onChange }), index.h("label", { htmlFor: buttonId }, this.renderContent(button), this.renderBadge(button))));
46
50
  }
47
51
  renderContent(button) {
48
52
  if (button.icon) {
@@ -75,10 +79,17 @@ const ButtonGroup = class {
75
79
  event.stopPropagation();
76
80
  const target = event.target;
77
81
  // The ID is prefixed with `b` in the HTML, remember? /Ads
78
- this.selectedButtonId = target.id.slice(1);
82
+ const buttonId = target.id.slice(1);
79
83
  const button = this.value.find((item) => {
80
- return item.id === this.selectedButtonId;
84
+ return item.id === buttonId;
81
85
  });
86
+ // The `disabled` attribute on the input already stops the browser
87
+ // from getting here. This makes it hold even if the attribute is
88
+ // removed from the DOM, or a `change` event is dispatched by hand.
89
+ if (!button || this.disabled || button.disabled) {
90
+ return;
91
+ }
92
+ this.selectedButtonId = buttonId;
82
93
  this.change.emit(button);
83
94
  }
84
95
  valueChanged() {
@@ -182,10 +182,10 @@ const ChipSet = class {
182
182
  * <kbd>Enter</kbd>. The input is then cleared to make room for the
183
183
  * next chip, and help user type a new keyword from scratch.
184
184
  *
185
- * Set to `false` when the input represents a search query over an
186
- * external suggestion list (as in `limel-picker`), so the typed keyword
187
- * survives while chips are added or removed (when user selects items from
188
- * the search results list, while it is open).
185
+ * Set to `false` when the input is a search query over an external
186
+ * suggestion list and the consumer decides when the keyword has served
187
+ * its purpose, clearing it itself via `emptyInput()`, as
188
+ * `limel-picker` does.
189
189
  */
190
190
  this.emptyInputOnChange = true;
191
191
  /**
@@ -374,7 +374,7 @@ const ChipSet = class {
374
374
  });
375
375
  }
376
376
  const value = this.getValue();
377
- return (index.h(index.Host, { key: '18ec100a773d4106fd07dbef12f99708480de638' }, index.h("limel-notched-outline", { key: '016081ecef43a53935276aa5877a0436aadf9e92', labelId: this.labelId, label: this.label, required: this.required, invalid: this.invalid || this.isInvalid(), disabled: this.disabled, readonly: this.readonly, hasValue: !!((_a = this.value) === null || _a === void 0 ? void 0 : _a.length), hasLeadingIcon: !!this.leadingIcon, hasFloatingLabel: this.floatLabelAbove() }, index.h("div", Object.assign({ key: 'eac05f60451ddb37a77337c8ebc998241cb9f906', slot: "content" }, this.getContentProps(), { class: classes }), this.renderContent(value))), this.renderHelperLine()));
377
+ return (index.h(index.Host, { key: 'e800200b0f31ad297fb25ceac46624c7731a9197' }, index.h("limel-notched-outline", { key: 'e5c3b7ec97f7063010af7c9eea45e5a89f00bd65', labelId: this.labelId, label: this.label, required: this.required, invalid: this.invalid || this.isInvalid(), disabled: this.disabled, readonly: this.readonly, hasValue: !!((_a = this.value) === null || _a === void 0 ? void 0 : _a.length), hasLeadingIcon: !!this.leadingIcon, hasFloatingLabel: this.floatLabelAbove() }, index.h("div", Object.assign({ key: '96344e9fd7a12a019cbe9896d5c6ff91925f6ca8', slot: "content" }, this.getContentProps(), { class: classes }), this.renderContent(value))), this.renderHelperLine()));
378
378
  }
379
379
  getContentProps() {
380
380
  if (this.type === 'input') {
@@ -367,7 +367,7 @@ const Picker = class {
367
367
  * @param event - event
368
368
  */
369
369
  handleListChange(event) {
370
- var _a, _b, _c;
370
+ var _a, _b;
371
371
  event.stopPropagation();
372
372
  if (!this.value || this.value !== event.detail) {
373
373
  let newValue = event.detail;
@@ -376,22 +376,23 @@ const Picker = class {
376
376
  newValue = [...currentValue, event.detail];
377
377
  }
378
378
  this.change.emit(newValue);
379
- if (this.multiple) {
380
- const remaining = this.items.filter((item) => item !== event.detail);
381
- this.items = this.hasPickableItems(remaining) ? remaining : [];
382
- }
383
- else {
384
- // Single-pick: the search session ends with the pick, so
385
- // wipe the input. (In multi-pick we deliberately keep the
386
- // typed query so the user can keep adding matches.)
387
- this.items = [];
388
- this.textValue = '';
389
- (_b = this.chipSet) === null || _b === void 0 ? void 0 : _b.emptyInput();
390
- }
379
+ this.updateSearchAfterPick();
391
380
  }
392
381
  if (this.multiple) {
393
- (_c = this.chipSet) === null || _c === void 0 ? void 0 : _c.setFocus();
382
+ (_b = this.chipSet) === null || _b === void 0 ? void 0 : _b.setFocus();
383
+ }
384
+ }
385
+ /**
386
+ * Ends the search session a pick concludes. A single-pick picker is
387
+ * full, so its dropdown has nothing left to show; a multi-pick one
388
+ * starts over from the default suggestions.
389
+ */
390
+ updateSearchAfterPick() {
391
+ if (!this.multiple) {
392
+ this.clearInputField();
393
+ return;
394
394
  }
395
+ this.resetSearchAfterValueChange();
395
396
  }
396
397
  /**
397
398
  * Change handler for the list
@@ -414,9 +415,9 @@ const Picker = class {
414
415
  const query = this.textValue;
415
416
  this.debouncedSearch(query);
416
417
  }
417
- handleChange(event) {
418
+ async handleChange(event) {
419
+ var _a, _b;
418
420
  event.stopPropagation();
419
- this.textValue = '';
420
421
  let newValue = null;
421
422
  if (this.multiple) {
422
423
  const chips = event.detail;
@@ -428,6 +429,14 @@ const Picker = class {
428
429
  });
429
430
  }
430
431
  this.change.emit(newValue);
432
+ const wasEditingInput = await ((_a = this.chipSet) === null || _a === void 0 ? void 0 : _a.getEditMode());
433
+ const wasShowingDropdown = ((_b = this.items) === null || _b === void 0 ? void 0 : _b.length) > 0 || this.textValue !== '';
434
+ if (this.multiple && wasEditingInput && wasShowingDropdown) {
435
+ this.resetSearchAfterValueChange();
436
+ }
437
+ else {
438
+ this.clearInputField();
439
+ }
431
440
  }
432
441
  handleInteract(event) {
433
442
  event.stopPropagation();
@@ -538,6 +547,17 @@ const Picker = class {
538
547
  this.clearTextValue();
539
548
  this.search('');
540
549
  }
550
+ /**
551
+ * Ends the search session and shows the default suggestions once the
552
+ * consumer has had a chance to answer the `change` event this follows.
553
+ * Consumers commonly drop the picked item from `allItems`, and that
554
+ * arrives as a prop on their next render, so searching at once would
555
+ * read the list from before.
556
+ */
557
+ resetSearchAfterValueChange() {
558
+ this.clearTextValue();
559
+ this.debouncedSearch('');
560
+ }
541
561
  /**
542
562
  * Two-stage Esc: first press clears the typed query but keeps the
543
563
  * dropdown open showing the default suggestions; a second press
@@ -123,29 +123,27 @@
123
123
  flex-wrap: nowrap;
124
124
  }
125
125
 
126
- :host([disabled]:not([disabled=false])) {
127
- cursor: not-allowed;
128
- opacity: 0.4;
129
- }
130
-
131
126
  .button {
127
+ color: var(--limel-theme-on-surface-color);
128
+ }
129
+ .button:not(:has(:disabled)) {
132
130
  transition: color var(--limel-clickable-transition-speed, 0.4s) ease, background-color var(--limel-clickable-transition-speed, 0.4s) ease, box-shadow var(--limel-clickable-transform-speed, 0.4s) ease, transform var(--limel-clickable-transform-speed, 0.4s) var(--limel-clickable-transform-timing-function, ease);
133
131
  cursor: pointer;
134
132
  color: var(--limel-theme-on-surface-color);
135
133
  background-color: transparent;
136
134
  }
137
- .button:hover, .button:focus, .button:focus-visible {
135
+ .button:not(:has(:disabled)):hover, .button:not(:has(:disabled)):focus, .button:not(:has(:disabled)):focus-visible {
138
136
  will-change: color, background-color, box-shadow, transform;
139
137
  }
140
- .button:hover, .button:focus-visible {
138
+ .button:not(:has(:disabled)):hover, .button:not(:has(:disabled)):focus-visible {
141
139
  transform: translate3d(0, -0.04rem, 0);
142
140
  color: var(--limel-theme-on-surface-color);
143
141
  background-color: var(--lime-elevated-surface-background-color);
144
142
  }
145
- .button:hover {
143
+ .button:not(:has(:disabled)):hover {
146
144
  box-shadow: var(--button-shadow-hovered);
147
145
  }
148
- .button:active {
146
+ .button:not(:has(:disabled)):active {
149
147
  --limel-clickable-transform-timing-function: cubic-bezier(
150
148
  0.83,
151
149
  -0.15,
@@ -156,14 +154,10 @@
156
154
  background-color: var(--limel-theme-surface-background-color);
157
155
  box-shadow: var(--button-shadow-inset-pressed);
158
156
  }
159
- .button:hover, .button:active {
157
+ .button:not(:has(:disabled)):hover, .button:not(:has(:disabled)):active {
160
158
  --limel-clickable-transition-speed: 0.2s;
161
159
  --limel-clickable-transform-speed: 0.16s;
162
160
  }
163
- .button:has(input[disabled]):hover {
164
- box-shadow: none;
165
- transform: none;
166
- }
167
161
  .button {
168
162
  position: relative;
169
163
  display: inline-grid;
@@ -204,6 +198,10 @@
204
198
  .button label:has(> limel-badge) .button__label {
205
199
  padding-right: 0.25rem;
206
200
  }
201
+ .button:has(:disabled) label {
202
+ cursor: not-allowed;
203
+ opacity: 0.4;
204
+ }
207
205
  .button input[type=radio] {
208
206
  position: absolute;
209
207
  width: 0;
@@ -235,7 +233,7 @@
235
233
  box-shadow: var(--button-shadow-inset);
236
234
  color: var(--lime-primary-color, var(--limel-theme-primary-color));
237
235
  }
238
- .button.button--selected:active {
236
+ .button.button--selected:not(:has(:disabled)):active {
239
237
  box-shadow: var(--button-shadow-inset-pressed);
240
238
  }
241
239
 
@@ -34,6 +34,7 @@ import { createRandomString } from "../../util/random-string";
34
34
  * @exampleComponent limel-example-button-group-basic
35
35
  * @exampleComponent limel-example-button-group-mix
36
36
  * @exampleComponent limel-example-button-group-badges
37
+ * @exampleComponent limel-example-button-group-disabled-item
37
38
  * @exampleComponent limel-example-button-group-composite
38
39
  */
39
40
  export class ButtonGroup {
@@ -43,7 +44,10 @@ export class ButtonGroup {
43
44
  */
44
45
  this.value = [];
45
46
  /**
46
- * True if the button-group should be disabled
47
+ * True if the button-group should be disabled.
48
+ *
49
+ * Disables every button in the group, regardless of the `disabled` value
50
+ * of each individual button.
47
51
  */
48
52
  this.disabled = false;
49
53
  this.radioGroupName = createRandomString();
@@ -61,17 +65,18 @@ export class ButtonGroup {
61
65
  this.setSelectedButton();
62
66
  }
63
67
  render() {
64
- return (h(Host, { key: '867ce929125e09bbe9f0862e6cb68ad10f90a768', role: "radiogroup" }, this.value.map(this.renderButton)));
68
+ return (h(Host, { key: '7823fd4593ee4eddb6d56ce55e7ff5eb1cc28397', role: "radiogroup" }, this.value.map(this.renderButton)));
65
69
  }
66
70
  renderButton(button) {
67
71
  // Prefix with 'b' because html IDs cannot start with a digit,
68
72
  // and we need to differentiate from the ID on the limel-icon. /Ads
69
73
  const buttonId = `b${button.id}`;
74
+ const isDisabled = this.disabled || button.disabled;
70
75
  const classes = {
71
76
  button: true,
72
77
  'button--selected': this.isButtonChecked(button),
73
78
  };
74
- return (h("div", { class: classes }, h("input", { type: "radio", name: this.radioGroupName, checked: this.isButtonChecked(button), disabled: this.disabled, id: buttonId, onChange: this.onChange }), h("label", { htmlFor: buttonId }, this.renderContent(button), this.renderBadge(button))));
79
+ return (h("div", { class: classes }, h("input", { type: "radio", name: this.radioGroupName, checked: this.isButtonChecked(button), disabled: isDisabled, "aria-disabled": isDisabled ? 'true' : 'false', id: buttonId, onChange: this.onChange }), h("label", { htmlFor: buttonId }, this.renderContent(button), this.renderBadge(button))));
75
80
  }
76
81
  renderContent(button) {
77
82
  if (button.icon) {
@@ -104,10 +109,17 @@ export class ButtonGroup {
104
109
  event.stopPropagation();
105
110
  const target = event.target;
106
111
  // The ID is prefixed with `b` in the HTML, remember? /Ads
107
- this.selectedButtonId = target.id.slice(1);
112
+ const buttonId = target.id.slice(1);
108
113
  const button = this.value.find((item) => {
109
- return item.id === this.selectedButtonId;
114
+ return item.id === buttonId;
110
115
  });
116
+ // The `disabled` attribute on the input already stops the browser
117
+ // from getting here. This makes it hold even if the attribute is
118
+ // removed from the DOM, or a `change` event is dispatched by hand.
119
+ if (!button || this.disabled || button.disabled) {
120
+ return;
121
+ }
122
+ this.selectedButtonId = buttonId;
111
123
  this.change.emit(button);
112
124
  }
113
125
  valueChanged() {
@@ -164,7 +176,7 @@ export class ButtonGroup {
164
176
  "optional": false,
165
177
  "docs": {
166
178
  "tags": [],
167
- "text": "True if the button-group should be disabled"
179
+ "text": "True if the button-group should be disabled.\n\nDisables every button in the group, regardless of the `disabled` value\nof each individual button."
168
180
  },
169
181
  "getter": false,
170
182
  "setter": false,
@@ -100,10 +100,10 @@ export class ChipSet {
100
100
  * <kbd>Enter</kbd>. The input is then cleared to make room for the
101
101
  * next chip, and help user type a new keyword from scratch.
102
102
  *
103
- * Set to `false` when the input represents a search query over an
104
- * external suggestion list (as in `limel-picker`), so the typed keyword
105
- * survives while chips are added or removed (when user selects items from
106
- * the search results list, while it is open).
103
+ * Set to `false` when the input is a search query over an external
104
+ * suggestion list and the consumer decides when the keyword has served
105
+ * its purpose, clearing it itself via `emptyInput()`, as
106
+ * `limel-picker` does.
107
107
  */
108
108
  this.emptyInputOnChange = true;
109
109
  /**
@@ -292,7 +292,7 @@ export class ChipSet {
292
292
  });
293
293
  }
294
294
  const value = this.getValue();
295
- return (h(Host, { key: '18ec100a773d4106fd07dbef12f99708480de638' }, h("limel-notched-outline", { key: '016081ecef43a53935276aa5877a0436aadf9e92', labelId: this.labelId, label: this.label, required: this.required, invalid: this.invalid || this.isInvalid(), disabled: this.disabled, readonly: this.readonly, hasValue: !!((_a = this.value) === null || _a === void 0 ? void 0 : _a.length), hasLeadingIcon: !!this.leadingIcon, hasFloatingLabel: this.floatLabelAbove() }, h("div", Object.assign({ key: 'eac05f60451ddb37a77337c8ebc998241cb9f906', slot: "content" }, this.getContentProps(), { class: classes }), this.renderContent(value))), this.renderHelperLine()));
295
+ return (h(Host, { key: 'e800200b0f31ad297fb25ceac46624c7731a9197' }, h("limel-notched-outline", { key: 'e5c3b7ec97f7063010af7c9eea45e5a89f00bd65', labelId: this.labelId, label: this.label, required: this.required, invalid: this.invalid || this.isInvalid(), disabled: this.disabled, readonly: this.readonly, hasValue: !!((_a = this.value) === null || _a === void 0 ? void 0 : _a.length), hasLeadingIcon: !!this.leadingIcon, hasFloatingLabel: this.floatLabelAbove() }, h("div", Object.assign({ key: '96344e9fd7a12a019cbe9896d5c6ff91925f6ca8', slot: "content" }, this.getContentProps(), { class: classes }), this.renderContent(value))), this.renderHelperLine()));
296
296
  }
297
297
  getContentProps() {
298
298
  if (this.type === 'input') {
@@ -778,7 +778,7 @@ export class ChipSet {
778
778
  "optional": false,
779
779
  "docs": {
780
780
  "tags": [],
781
- "text": "Whether the input field should be emptied when the set of chips\nchanges (i.e. when the `value` prop is updated \u2014 a chip is added,\nremoved, or the array is replaced).\n\nDefaults to `true`, which is the right behavior for a standalone\ninput chip-set where the typed text *becomes* the next chip on\n<kbd>Enter</kbd>. The input is then cleared to make room for the\nnext chip, and help user type a new keyword from scratch.\n\nSet to `false` when the input represents a search query over an\nexternal suggestion list (as in `limel-picker`), so the typed keyword\nsurvives while chips are added or removed (when user selects items from\nthe search results list, while it is open)."
781
+ "text": "Whether the input field should be emptied when the set of chips\nchanges (i.e. when the `value` prop is updated \u2014 a chip is added,\nremoved, or the array is replaced).\n\nDefaults to `true`, which is the right behavior for a standalone\ninput chip-set where the typed text *becomes* the next chip on\n<kbd>Enter</kbd>. The input is then cleared to make room for the\nnext chip, and help user type a new keyword from scratch.\n\nSet to `false` when the input is a search query over an external\nsuggestion list and the consumer decides when the keyword has served\nits purpose, clearing it itself via `emptyInput()`, as\n`limel-picker` does."
782
782
  },
783
783
  "getter": false,
784
784
  "setter": false,
@@ -370,7 +370,7 @@ export class Picker {
370
370
  * @param event - event
371
371
  */
372
372
  handleListChange(event) {
373
- var _a, _b, _c;
373
+ var _a, _b;
374
374
  event.stopPropagation();
375
375
  if (!this.value || this.value !== event.detail) {
376
376
  let newValue = event.detail;
@@ -379,22 +379,23 @@ export class Picker {
379
379
  newValue = [...currentValue, event.detail];
380
380
  }
381
381
  this.change.emit(newValue);
382
- if (this.multiple) {
383
- const remaining = this.items.filter((item) => item !== event.detail);
384
- this.items = this.hasPickableItems(remaining) ? remaining : [];
385
- }
386
- else {
387
- // Single-pick: the search session ends with the pick, so
388
- // wipe the input. (In multi-pick we deliberately keep the
389
- // typed query so the user can keep adding matches.)
390
- this.items = [];
391
- this.textValue = '';
392
- (_b = this.chipSet) === null || _b === void 0 ? void 0 : _b.emptyInput();
393
- }
382
+ this.updateSearchAfterPick();
394
383
  }
395
384
  if (this.multiple) {
396
- (_c = this.chipSet) === null || _c === void 0 ? void 0 : _c.setFocus();
385
+ (_b = this.chipSet) === null || _b === void 0 ? void 0 : _b.setFocus();
386
+ }
387
+ }
388
+ /**
389
+ * Ends the search session a pick concludes. A single-pick picker is
390
+ * full, so its dropdown has nothing left to show; a multi-pick one
391
+ * starts over from the default suggestions.
392
+ */
393
+ updateSearchAfterPick() {
394
+ if (!this.multiple) {
395
+ this.clearInputField();
396
+ return;
397
397
  }
398
+ this.resetSearchAfterValueChange();
398
399
  }
399
400
  /**
400
401
  * Change handler for the list
@@ -417,9 +418,9 @@ export class Picker {
417
418
  const query = this.textValue;
418
419
  this.debouncedSearch(query);
419
420
  }
420
- handleChange(event) {
421
+ async handleChange(event) {
422
+ var _a, _b;
421
423
  event.stopPropagation();
422
- this.textValue = '';
423
424
  let newValue = null;
424
425
  if (this.multiple) {
425
426
  const chips = event.detail;
@@ -431,6 +432,14 @@ export class Picker {
431
432
  });
432
433
  }
433
434
  this.change.emit(newValue);
435
+ const wasEditingInput = await ((_a = this.chipSet) === null || _a === void 0 ? void 0 : _a.getEditMode());
436
+ const wasShowingDropdown = ((_b = this.items) === null || _b === void 0 ? void 0 : _b.length) > 0 || this.textValue !== '';
437
+ if (this.multiple && wasEditingInput && wasShowingDropdown) {
438
+ this.resetSearchAfterValueChange();
439
+ }
440
+ else {
441
+ this.clearInputField();
442
+ }
434
443
  }
435
444
  handleInteract(event) {
436
445
  event.stopPropagation();
@@ -541,6 +550,17 @@ export class Picker {
541
550
  this.clearTextValue();
542
551
  this.search('');
543
552
  }
553
+ /**
554
+ * Ends the search session and shows the default suggestions once the
555
+ * consumer has had a chance to answer the `change` event this follows.
556
+ * Consumers commonly drop the picked item from `allItems`, and that
557
+ * arrives as a prop on their next render, so searching at once would
558
+ * read the list from before.
559
+ */
560
+ resetSearchAfterValueChange() {
561
+ this.clearTextValue();
562
+ this.debouncedSearch('');
563
+ }
544
564
  /**
545
565
  * Two-stage Esc: first press clears the typed query but keeps the
546
566
  * dropdown open showing the default suggestions; a second press
@@ -1,7 +1,7 @@
1
1
  import { r as registerInstance, c as createEvent, h, H as Host } from './index-BGxJfR2f.js';
2
2
  import { c as createRandomString } from './random-string-JbKhhoXs.js';
3
3
 
4
- const buttonGroupCss = () => `@charset "UTF-8";*,*:before,*:after{box-sizing:border-box}:host(limel-button-group){display:flex;box-sizing:border-box;align-items:center;border-radius:3.75rem;background-color:var(--button-group-background-color, rgb(var(--contrast-400)));width:max-content;max-width:100%;flex-wrap:nowrap}:host([disabled]:not([disabled=false])){cursor:not-allowed;opacity:0.4}.button{transition:color var(--limel-clickable-transition-speed, 0.4s) ease, background-color var(--limel-clickable-transition-speed, 0.4s) ease, box-shadow var(--limel-clickable-transform-speed, 0.4s) ease, transform var(--limel-clickable-transform-speed, 0.4s) var(--limel-clickable-transform-timing-function, ease);cursor:pointer;color:var(--limel-theme-on-surface-color);background-color:transparent}.button:hover,.button:focus,.button:focus-visible{will-change:color, background-color, box-shadow, transform}.button:hover,.button:focus-visible{transform:translate3d(0, -0.04rem, 0);color:var(--limel-theme-on-surface-color);background-color:var(--lime-elevated-surface-background-color)}.button:hover{box-shadow:var(--button-shadow-hovered)}.button:active{--limel-clickable-transform-timing-function:cubic-bezier( 0.83, -0.15, 0.49, 1.16 );transform:translate3d(0, 0.05rem, 0);background-color:var(--limel-theme-surface-background-color);box-shadow:var(--button-shadow-inset-pressed)}.button:hover,.button:active{--limel-clickable-transition-speed:0.2s;--limel-clickable-transform-speed:0.16s}.button:has(input[disabled]):hover{box-shadow:none;transform:none}.button{position:relative;display:inline-grid;grid-auto-flow:column;align-items:center;max-width:100%;min-width:2rem;height:2rem;padding:0 0.0625rem;margin:0.125rem;border-radius:1rem;font-size:var(--limel-theme-default-font-size);user-select:none}.button:not(:last-child):after{content:"";display:block;height:1rem;width:0.125rem;border-radius:0.25rem;background-color:var(--button-group-text-color, rgb(var(--contrast-1200)));opacity:0.1;position:absolute;right:-0.1875rem;top:0;bottom:0;margin:auto}.button label{cursor:pointer;transition:color 0.2s ease;display:flex;align-items:center;flex:1;height:100%;min-width:0}.button label:has(>limel-badge) .button__label{padding-right:0.25rem}.button input[type=radio]{position:absolute;width:0;opacity:0}.button input[type=radio]:focus-visible+label:after{content:"";display:block;position:absolute;top:0;right:0;bottom:0;left:0;border-radius:3.75rem;box-shadow:var(--shadow-depth-8-focused)}.button:focus-within{outline:none;color:var(--lime-primary-color, var(--limel-theme-primary-color))}.button:only-child .button__label{padding-left:0.75rem}.button:first-child .button__label{padding-left:0.75rem}.button.button--selected{background-color:var(--limel-theme-surface-background-color);box-shadow:var(--button-shadow-inset);color:var(--lime-primary-color, var(--limel-theme-primary-color))}.button.button--selected:active{box-shadow:var(--button-shadow-inset-pressed)}.button__label{overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-size:var(--limel-theme-default-small-font-size);padding:0 0.75rem}.button__icon{border-radius:50%;vertical-align:middle}limel-badge{margin-right:0.25rem;pointer-events:none}.button:not(.button--selected){--badge-background-color:rgb(var(--contrast-200))}`;
4
+ const buttonGroupCss = () => `@charset "UTF-8";*,*:before,*:after{box-sizing:border-box}:host(limel-button-group){display:flex;box-sizing:border-box;align-items:center;border-radius:3.75rem;background-color:var(--button-group-background-color, rgb(var(--contrast-400)));width:max-content;max-width:100%;flex-wrap:nowrap}.button{color:var(--limel-theme-on-surface-color)}.button:not(:has(:disabled)){transition:color var(--limel-clickable-transition-speed, 0.4s) ease, background-color var(--limel-clickable-transition-speed, 0.4s) ease, box-shadow var(--limel-clickable-transform-speed, 0.4s) ease, transform var(--limel-clickable-transform-speed, 0.4s) var(--limel-clickable-transform-timing-function, ease);cursor:pointer;color:var(--limel-theme-on-surface-color);background-color:transparent}.button:not(:has(:disabled)):hover,.button:not(:has(:disabled)):focus,.button:not(:has(:disabled)):focus-visible{will-change:color, background-color, box-shadow, transform}.button:not(:has(:disabled)):hover,.button:not(:has(:disabled)):focus-visible{transform:translate3d(0, -0.04rem, 0);color:var(--limel-theme-on-surface-color);background-color:var(--lime-elevated-surface-background-color)}.button:not(:has(:disabled)):hover{box-shadow:var(--button-shadow-hovered)}.button:not(:has(:disabled)):active{--limel-clickable-transform-timing-function:cubic-bezier( 0.83, -0.15, 0.49, 1.16 );transform:translate3d(0, 0.05rem, 0);background-color:var(--limel-theme-surface-background-color);box-shadow:var(--button-shadow-inset-pressed)}.button:not(:has(:disabled)):hover,.button:not(:has(:disabled)):active{--limel-clickable-transition-speed:0.2s;--limel-clickable-transform-speed:0.16s}.button{position:relative;display:inline-grid;grid-auto-flow:column;align-items:center;max-width:100%;min-width:2rem;height:2rem;padding:0 0.0625rem;margin:0.125rem;border-radius:1rem;font-size:var(--limel-theme-default-font-size);user-select:none}.button:not(:last-child):after{content:"";display:block;height:1rem;width:0.125rem;border-radius:0.25rem;background-color:var(--button-group-text-color, rgb(var(--contrast-1200)));opacity:0.1;position:absolute;right:-0.1875rem;top:0;bottom:0;margin:auto}.button label{cursor:pointer;transition:color 0.2s ease;display:flex;align-items:center;flex:1;height:100%;min-width:0}.button label:has(>limel-badge) .button__label{padding-right:0.25rem}.button:has(:disabled) label{cursor:not-allowed;opacity:0.4}.button input[type=radio]{position:absolute;width:0;opacity:0}.button input[type=radio]:focus-visible+label:after{content:"";display:block;position:absolute;top:0;right:0;bottom:0;left:0;border-radius:3.75rem;box-shadow:var(--shadow-depth-8-focused)}.button:focus-within{outline:none;color:var(--lime-primary-color, var(--limel-theme-primary-color))}.button:only-child .button__label{padding-left:0.75rem}.button:first-child .button__label{padding-left:0.75rem}.button.button--selected{background-color:var(--limel-theme-surface-background-color);box-shadow:var(--button-shadow-inset);color:var(--lime-primary-color, var(--limel-theme-primary-color))}.button.button--selected:not(:has(:disabled)):active{box-shadow:var(--button-shadow-inset-pressed)}.button__label{overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-size:var(--limel-theme-default-small-font-size);padding:0 0.75rem}.button__icon{border-radius:50%;vertical-align:middle}limel-badge{margin-right:0.25rem;pointer-events:none}.button:not(.button--selected){--badge-background-color:rgb(var(--contrast-200))}`;
5
5
 
6
6
  const ButtonGroup = class {
7
7
  constructor(hostRef) {
@@ -12,7 +12,10 @@ const ButtonGroup = class {
12
12
  */
13
13
  this.value = [];
14
14
  /**
15
- * True if the button-group should be disabled
15
+ * True if the button-group should be disabled.
16
+ *
17
+ * Disables every button in the group, regardless of the `disabled` value
18
+ * of each individual button.
16
19
  */
17
20
  this.disabled = false;
18
21
  this.radioGroupName = createRandomString();
@@ -30,17 +33,18 @@ const ButtonGroup = class {
30
33
  this.setSelectedButton();
31
34
  }
32
35
  render() {
33
- return (h(Host, { key: '867ce929125e09bbe9f0862e6cb68ad10f90a768', role: "radiogroup" }, this.value.map(this.renderButton)));
36
+ return (h(Host, { key: '7823fd4593ee4eddb6d56ce55e7ff5eb1cc28397', role: "radiogroup" }, this.value.map(this.renderButton)));
34
37
  }
35
38
  renderButton(button) {
36
39
  // Prefix with 'b' because html IDs cannot start with a digit,
37
40
  // and we need to differentiate from the ID on the limel-icon. /Ads
38
41
  const buttonId = `b${button.id}`;
42
+ const isDisabled = this.disabled || button.disabled;
39
43
  const classes = {
40
44
  button: true,
41
45
  'button--selected': this.isButtonChecked(button),
42
46
  };
43
- return (h("div", { class: classes }, h("input", { type: "radio", name: this.radioGroupName, checked: this.isButtonChecked(button), disabled: this.disabled, id: buttonId, onChange: this.onChange }), h("label", { htmlFor: buttonId }, this.renderContent(button), this.renderBadge(button))));
47
+ return (h("div", { class: classes }, h("input", { type: "radio", name: this.radioGroupName, checked: this.isButtonChecked(button), disabled: isDisabled, "aria-disabled": isDisabled ? 'true' : 'false', id: buttonId, onChange: this.onChange }), h("label", { htmlFor: buttonId }, this.renderContent(button), this.renderBadge(button))));
44
48
  }
45
49
  renderContent(button) {
46
50
  if (button.icon) {
@@ -73,10 +77,17 @@ const ButtonGroup = class {
73
77
  event.stopPropagation();
74
78
  const target = event.target;
75
79
  // The ID is prefixed with `b` in the HTML, remember? /Ads
76
- this.selectedButtonId = target.id.slice(1);
80
+ const buttonId = target.id.slice(1);
77
81
  const button = this.value.find((item) => {
78
- return item.id === this.selectedButtonId;
82
+ return item.id === buttonId;
79
83
  });
84
+ // The `disabled` attribute on the input already stops the browser
85
+ // from getting here. This makes it hold even if the attribute is
86
+ // removed from the DOM, or a `change` event is dispatched by hand.
87
+ if (!button || this.disabled || button.disabled) {
88
+ return;
89
+ }
90
+ this.selectedButtonId = buttonId;
80
91
  this.change.emit(button);
81
92
  }
82
93
  valueChanged() {
@@ -180,10 +180,10 @@ const ChipSet = class {
180
180
  * <kbd>Enter</kbd>. The input is then cleared to make room for the
181
181
  * next chip, and help user type a new keyword from scratch.
182
182
  *
183
- * Set to `false` when the input represents a search query over an
184
- * external suggestion list (as in `limel-picker`), so the typed keyword
185
- * survives while chips are added or removed (when user selects items from
186
- * the search results list, while it is open).
183
+ * Set to `false` when the input is a search query over an external
184
+ * suggestion list and the consumer decides when the keyword has served
185
+ * its purpose, clearing it itself via `emptyInput()`, as
186
+ * `limel-picker` does.
187
187
  */
188
188
  this.emptyInputOnChange = true;
189
189
  /**
@@ -372,7 +372,7 @@ const ChipSet = class {
372
372
  });
373
373
  }
374
374
  const value = this.getValue();
375
- return (h(Host, { key: '18ec100a773d4106fd07dbef12f99708480de638' }, h("limel-notched-outline", { key: '016081ecef43a53935276aa5877a0436aadf9e92', labelId: this.labelId, label: this.label, required: this.required, invalid: this.invalid || this.isInvalid(), disabled: this.disabled, readonly: this.readonly, hasValue: !!((_a = this.value) === null || _a === void 0 ? void 0 : _a.length), hasLeadingIcon: !!this.leadingIcon, hasFloatingLabel: this.floatLabelAbove() }, h("div", Object.assign({ key: 'eac05f60451ddb37a77337c8ebc998241cb9f906', slot: "content" }, this.getContentProps(), { class: classes }), this.renderContent(value))), this.renderHelperLine()));
375
+ return (h(Host, { key: 'e800200b0f31ad297fb25ceac46624c7731a9197' }, h("limel-notched-outline", { key: 'e5c3b7ec97f7063010af7c9eea45e5a89f00bd65', labelId: this.labelId, label: this.label, required: this.required, invalid: this.invalid || this.isInvalid(), disabled: this.disabled, readonly: this.readonly, hasValue: !!((_a = this.value) === null || _a === void 0 ? void 0 : _a.length), hasLeadingIcon: !!this.leadingIcon, hasFloatingLabel: this.floatLabelAbove() }, h("div", Object.assign({ key: '96344e9fd7a12a019cbe9896d5c6ff91925f6ca8', slot: "content" }, this.getContentProps(), { class: classes }), this.renderContent(value))), this.renderHelperLine()));
376
376
  }
377
377
  getContentProps() {
378
378
  if (this.type === 'input') {