@iyulab/components 1.31.0 → 1.33.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.
Files changed (29) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/components/checkbox/UCheckbox.d.ts +2 -0
  3. package/dist/components/checkbox/UCheckbox.js +6 -0
  4. package/dist/components/date-picker/UDatePicker.d.ts +5 -0
  5. package/dist/components/date-picker/UDatePicker.js +10 -0
  6. package/dist/components/field/UField.d.ts +4 -0
  7. package/dist/components/field/UField.js +11 -5
  8. package/dist/components/radio/URadio.d.ts +5 -0
  9. package/dist/components/radio/URadio.js +9 -0
  10. package/dist/components/rating/URating.d.ts +7 -0
  11. package/dist/components/rating/URating.js +12 -0
  12. package/dist/components/select/USelect.d.ts +5 -0
  13. package/dist/components/select/USelect.js +10 -0
  14. package/dist/components/slider/USlider.d.ts +8 -0
  15. package/dist/components/slider/USlider.js +11 -0
  16. package/dist/components/switch/USwitch.d.ts +2 -0
  17. package/dist/components/switch/USwitch.js +6 -0
  18. package/dist/components/textarea/UTextarea.d.ts +2 -0
  19. package/dist/components/textarea/UTextarea.js +6 -0
  20. package/package.json +1 -1
  21. package/skills/iyulab-components/references/components/checkbox.md +2 -0
  22. package/skills/iyulab-components/references/components/date-picker.md +2 -0
  23. package/skills/iyulab-components/references/components/field.md +6 -0
  24. package/skills/iyulab-components/references/components/radio.md +2 -0
  25. package/skills/iyulab-components/references/components/rating.md +2 -0
  26. package/skills/iyulab-components/references/components/select.md +2 -0
  27. package/skills/iyulab-components/references/components/slider.md +2 -0
  28. package/skills/iyulab-components/references/components/switch.md +2 -0
  29. package/skills/iyulab-components/references/components/textarea.md +2 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.33.0] - 2026-08-21
4
+
5
+ ### Added
6
+
7
+ - **`u-radio` and `u-rating` now support `.focus()`/`.blur()` on the host element too**,
8
+ completing the same contract added to the rest of the form controls in `1.32.0`. Both are
9
+ option/symbol *groups* rather than a single target, so `.focus()` delegates to the first
10
+ option (`u-radio`) or the first symbol (`u-rating`) regardless of the current selection —
11
+ the same "one fixed delegation target" contract every other control in this library now
12
+ shares, rather than adding value-aware branching.
13
+
14
+ ## [1.32.0] - 2026-08-21
15
+
16
+ ### Added
17
+
18
+ - **`u-select`, `u-date-picker`, `u-textarea`, `u-checkbox`, `u-switch`, and `u-slider` now
19
+ support `.focus()`/`.blur()` on the host element**, matching `u-input`'s existing contract.
20
+ Previously only `u-input` delegated `.focus()` to its actual interactive element; calling
21
+ `.focus()` on any of the others was a silent no-op, since the host element itself carries no
22
+ `tabindex` — only an internal element does (the container, the native `<input>`/`<textarea>`,
23
+ or, for `u-slider`, the first thumb). Any consumer building a generic "scroll to and focus
24
+ the first invalid field" flow now gets consistent behavior across every form control this
25
+ library ships, instead of having to special-case each one.
26
+ - **`u-field` now exposes `.focus()` publicly**, delegating to the first focusable slotted
27
+ child — the same lookup its label-click handler already used internally, now available to
28
+ consumers slotting an arbitrary form control (not one of this library's own) directly into
29
+ `<u-field>`.
30
+
3
31
  ## [1.31.0] - 2026-08-20
4
32
 
5
33
  ### Added
@@ -42,6 +42,8 @@ export declare class UCheckbox extends UFormControlElement<string> {
42
42
  render(): import('lit-html').TemplateResult<1>;
43
43
  protected setValidity(): void;
44
44
  reset(): void;
45
+ focus(options?: FocusOptions): void;
46
+ blur(): void;
45
47
  private handleInputChange;
46
48
  }
47
49
  declare global {
@@ -74,6 +74,12 @@ var UCheckbox = class UCheckbox extends UFormControlElement {
74
74
  this.indeterminate = false;
75
75
  this.invalid = false;
76
76
  }
77
+ focus(options) {
78
+ this.inputEl?.focus(options);
79
+ }
80
+ blur() {
81
+ this.inputEl?.blur();
82
+ }
77
83
  };
78
84
  __decorate([property({
79
85
  type: String,
@@ -66,6 +66,11 @@ export declare class UDatePicker extends UFormControlElement<string> {
66
66
  private emitChange;
67
67
  protected setValidity(): void;
68
68
  reset(): void;
69
+ /** `.container`는 div라 네이티브 `disabled`가 없다 — disabled일 때 `tabindex="-1"`로만
70
+ * Tab 순서에서 빠지고 프로그램적 `.focus()`는 여전히 통과하므로, `UInput.focus()`가
71
+ * 네이티브 `disabled` `<input>`에서 얻는 것과 같은 no-op을 여기서 직접 재현한다. */
72
+ focus(options?: FocusOptions): void;
73
+ blur(): void;
69
74
  }
70
75
  declare global {
71
76
  interface HTMLElementTagNameMap {
@@ -297,6 +297,16 @@ var UDatePicker = class UDatePicker extends UFormControlElement {
297
297
  this.value = void 0;
298
298
  this.invalid = false;
299
299
  }
300
+ /** `.container`는 div라 네이티브 `disabled`가 없다 — disabled일 때 `tabindex="-1"`로만
301
+ * Tab 순서에서 빠지고 프로그램적 `.focus()`는 여전히 통과하므로, `UInput.focus()`가
302
+ * 네이티브 `disabled` `<input>`에서 얻는 것과 같은 no-op을 여기서 직접 재현한다. */
303
+ focus(options) {
304
+ if (this.disabled) return;
305
+ this.containerEl?.focus(options);
306
+ }
307
+ blur() {
308
+ this.containerEl?.blur();
309
+ }
300
310
  };
301
311
  __decorate([property({ type: String }), __decorateMetadata("design:type", String)], UDatePicker.prototype, "min", void 0);
302
312
  __decorate([property({ type: String }), __decorateMetadata("design:type", String)], UDatePicker.prototype, "max", void 0);
@@ -29,6 +29,10 @@ export declare class UField extends UElement {
29
29
  validationMessage?: string;
30
30
  render(): import('lit-html').TemplateResult<1>;
31
31
  private renderFooter;
32
+ /** 슬롯에 꽂힌 첫 포커스 가능 자식으로 위임한다 — 라벨 클릭과 같은 대상 탐색 로직을
33
+ * 공개 API로도 노출해, 소비자가 임의의 폼 컨트롤(자체 웹 컴포넌트가 아닌 것 포함)을
34
+ * 슬롯에 꽂아도 host의 `.focus()`가 `UInput.focus()`와 같은 계약으로 동작하게 한다. */
35
+ focus(options?: FocusOptions): void;
32
36
  private handleLabelClick;
33
37
  }
34
38
  declare global {
@@ -13,11 +13,7 @@ var UField = class UField extends UElement {
13
13
  this.required = false;
14
14
  this.invalid = false;
15
15
  this.handleLabelClick = () => {
16
- const nodes = (this.shadowRoot?.querySelector("slot:not([name])"))?.assignedElements({ flatten: true }) || [];
17
- for (const node of nodes) if (isFocusable(node)) {
18
- node.focus();
19
- return;
20
- }
16
+ this.focus();
21
17
  };
22
18
  }
23
19
  static {
@@ -43,6 +39,16 @@ var UField = class UField extends UElement {
43
39
  if (!message) return nothing;
44
40
  return html`<div class="footer">${message}</div>`;
45
41
  }
42
+ /** 슬롯에 꽂힌 첫 포커스 가능 자식으로 위임한다 — 라벨 클릭과 같은 대상 탐색 로직을
43
+ * 공개 API로도 노출해, 소비자가 임의의 폼 컨트롤(자체 웹 컴포넌트가 아닌 것 포함)을
44
+ * 슬롯에 꽂아도 host의 `.focus()`가 `UInput.focus()`와 같은 계약으로 동작하게 한다. */
45
+ focus(options) {
46
+ const nodes = (this.shadowRoot?.querySelector("slot:not([name])"))?.assignedElements({ flatten: true }) || [];
47
+ for (const node of nodes) if (isFocusable(node)) {
48
+ node.focus(options);
49
+ return;
50
+ }
51
+ }
46
52
  };
47
53
  __decorate([property({
48
54
  type: Boolean,
@@ -31,6 +31,11 @@ export declare class URadio extends UFormControlElement<string> {
31
31
  render(): import('lit-html').TemplateResult<1>;
32
32
  protected setValidity(): void;
33
33
  reset(): void;
34
+ /** 그룹의 첫 포커스 가능 옵션으로 위임한다(선택된 값과 무관 — 나머지 폼 컨트롤과
35
+ * 같은 "위임 대상이 하나로 고정된 단순 계약"을 유지한다). 옵션 자신이 host에
36
+ * 실제 tabindex를 갖는 커스텀 엘리먼트라 `UOption.focus()`가 이미 동작한다. */
37
+ focus(options?: FocusOptions): void;
38
+ blur(): void;
34
39
  private setup;
35
40
  private cleanup;
36
41
  private onChangeValue;
@@ -108,6 +108,15 @@ var URadio = class URadio extends UFormControlElement {
108
108
  this.value = void 0;
109
109
  this.invalid = false;
110
110
  }
111
+ /** 그룹의 첫 포커스 가능 옵션으로 위임한다(선택된 값과 무관 — 나머지 폼 컨트롤과
112
+ * 같은 "위임 대상이 하나로 고정된 단순 계약"을 유지한다). 옵션 자신이 host에
113
+ * 실제 tabindex를 갖는 커스텀 엘리먼트라 `UOption.focus()`가 이미 동작한다. */
114
+ focus(options) {
115
+ this.options.find((o) => !o.disabled)?.focus(options);
116
+ }
117
+ blur() {
118
+ (this.shadowRoot?.activeElement)?.blur();
119
+ }
111
120
  setup(options) {
112
121
  for (const option of options) {
113
122
  option.removeEventListener("click", this.handleOptionClick);
@@ -37,6 +37,13 @@ export declare class URating extends UFormControlElement<number> {
37
37
  protected setValidity(): void;
38
38
  private isStepMismatch;
39
39
  reset(): void;
40
+ /** 첫(최저 점수) 심볼로 위임한다(선택된 값과 무관 — 나머지 폼 컨트롤과 같은
41
+ * "위임 대상이 하나로 고정된 단순 계약"을 유지한다). 심볼은 shadow DOM 안의
42
+ * `<span>`이라 disabled/readonly일 때 `tabindex="-1"`(=`!interactive`)이고,
43
+ * 다른 컴포넌트의 `.container`/thumb와 같은 이유로 프로그램적 focus는 여전히
44
+ * 통과하므로 직접 가드한다. */
45
+ focus(options?: FocusOptions): void;
46
+ blur(): void;
40
47
  private onChangeValue;
41
48
  /** 사용자 상호작용으로 값이 바뀐 경로에서만 호출한다 — 프로그램적 value 세팅은
42
49
  * 네이티브 폼 컨트롤과 동일하게 change를 발화하지 않는다.
@@ -175,6 +175,18 @@ var URating = class URating extends UFormControlElement {
175
175
  this.value = 0;
176
176
  this.invalid = false;
177
177
  }
178
+ /** 첫(최저 점수) 심볼로 위임한다(선택된 값과 무관 — 나머지 폼 컨트롤과 같은
179
+ * "위임 대상이 하나로 고정된 단순 계약"을 유지한다). 심볼은 shadow DOM 안의
180
+ * `<span>`이라 disabled/readonly일 때 `tabindex="-1"`(=`!interactive`)이고,
181
+ * 다른 컴포넌트의 `.container`/thumb와 같은 이유로 프로그램적 focus는 여전히
182
+ * 통과하므로 직접 가드한다. */
183
+ focus(options) {
184
+ if (!this.interactive) return;
185
+ this.renderRoot.querySelector(".symbol")?.focus(options);
186
+ }
187
+ blur() {
188
+ (this.shadowRoot?.activeElement)?.blur();
189
+ }
178
190
  onChangeValue() {
179
191
  this.buffer = -1;
180
192
  this.internals?.setFormValue(this.value?.toString() || "");
@@ -62,6 +62,11 @@ export declare class USelect extends UFormControlElement<string | string[]> {
62
62
  private renderContent;
63
63
  protected setValidity(): void;
64
64
  reset(): void;
65
+ /** `.container`는 div라 네이티브 `disabled`가 없다 — disabled일 때 `tabindex="-1"`로만
66
+ * Tab 순서에서 빠지고 프로그램적 `.focus()`는 여전히 통과하므로, `UInput.focus()`가
67
+ * 네이티브 `disabled` `<input>`에서 얻는 것과 같은 no-op을 여기서 직접 재현한다. */
68
+ focus(options?: FocusOptions): void;
69
+ blur(): void;
65
70
  private setup;
66
71
  private cleanup;
67
72
  private onChangeValue;
@@ -265,6 +265,16 @@ var USelect = class USelect extends UFormControlElement {
265
265
  this.value = this.multiple ? [] : "";
266
266
  this.invalid = false;
267
267
  }
268
+ /** `.container`는 div라 네이티브 `disabled`가 없다 — disabled일 때 `tabindex="-1"`로만
269
+ * Tab 순서에서 빠지고 프로그램적 `.focus()`는 여전히 통과하므로, `UInput.focus()`가
270
+ * 네이티브 `disabled` `<input>`에서 얻는 것과 같은 no-op을 여기서 직접 재현한다. */
271
+ focus(options) {
272
+ if (this.disabled) return;
273
+ this.containerEl?.focus(options);
274
+ }
275
+ blur() {
276
+ this.containerEl?.blur();
277
+ }
268
278
  setup(options) {
269
279
  for (const option of options) {
270
280
  option.removeEventListener("click", this.handleOptionClick);
@@ -60,6 +60,9 @@ export declare class USlider extends UFormControlElement<number | number[]> {
60
60
  private dragging;
61
61
  private trackEl;
62
62
  private containerEl?;
63
+ /** `.container`는 포인터 이벤트 좌표 계산용 래퍼일 뿐 tabindex가 없다 — 실제
64
+ * 포커스 가능한 대상은 min thumb(단일 모드의 유일한 thumb, range 모드의 시작값)다. */
65
+ private minThumbEl?;
63
66
  protected shouldValidate(changed: PropertyValues): boolean;
64
67
  get valueAsNumber(): number;
65
68
  get valueAsArray(): number[];
@@ -74,6 +77,11 @@ export declare class USlider extends UFormControlElement<number | number[]> {
74
77
  render(): TemplateResult<1>;
75
78
  protected setValidity(): void;
76
79
  reset(): void;
80
+ /** thumb는 div라 네이티브 `disabled`가 없다 — disabled일 때 `tabindex="-1"`로만 Tab
81
+ * 순서에서 빠지고 프로그램적 `.focus()`는 여전히 통과하므로, `UInput.focus()`가
82
+ * 네이티브 `disabled` `<input>`에서 얻는 것과 같은 no-op을 여기서 직접 재현한다. */
83
+ focus(options?: FocusOptions): void;
84
+ blur(): void;
77
85
  private onChangeValue;
78
86
  /** 사용자 상호작용으로 값이 확정된 경로에서만 호출한다 — 프로그램적 value 세팅은
79
87
  * 네이티브 폼 컨트롤과 동일하게 change를 발화하지 않는다. */
@@ -200,6 +200,16 @@ var USlider = class USlider extends UFormControlElement {
200
200
  else this.value = this.offset ?? this.min;
201
201
  this.invalid = false;
202
202
  }
203
+ /** thumb는 div라 네이티브 `disabled`가 없다 — disabled일 때 `tabindex="-1"`로만 Tab
204
+ * 순서에서 빠지고 프로그램적 `.focus()`는 여전히 통과하므로, `UInput.focus()`가
205
+ * 네이티브 `disabled` `<input>`에서 얻는 것과 같은 no-op을 여기서 직접 재현한다. */
206
+ focus(options) {
207
+ if (this.disabled) return;
208
+ this.minThumbEl?.focus(options);
209
+ }
210
+ blur() {
211
+ this.minThumbEl?.blur();
212
+ }
203
213
  onChangeValue() {
204
214
  if (Array.isArray(this.value)) this.value = [this.snap(this.value[0]), this.snap(this.value[1])];
205
215
  else if (typeof this.value === "number") this.value = this.snap(this.value);
@@ -300,6 +310,7 @@ __decorate([property({ converter: { fromAttribute: (value) => {
300
310
  __decorate([state(), __decorateMetadata("design:type", Object)], USlider.prototype, "dragging", void 0);
301
311
  __decorate([query(".track"), __decorateMetadata("design:type", typeof HTMLElement === "undefined" ? Object : HTMLElement)], USlider.prototype, "trackEl", void 0);
302
312
  __decorate([query(".container"), __decorateMetadata("design:type", typeof HTMLElement === "undefined" ? Object : HTMLElement)], USlider.prototype, "containerEl", void 0);
313
+ __decorate([query(".thumb[data-thumb=\"min\"]"), __decorateMetadata("design:type", typeof HTMLElement === "undefined" ? Object : HTMLElement)], USlider.prototype, "minThumbEl", void 0);
303
314
  USlider = __decorate([customElement("u-slider")], USlider);
304
315
  //#endregion
305
316
  export { USlider };
@@ -41,6 +41,8 @@ export declare class USwitch extends UFormControlElement<string> {
41
41
  render(): import('lit-html').TemplateResult<1>;
42
42
  protected setValidity(): void;
43
43
  reset(): void;
44
+ focus(options?: FocusOptions): void;
45
+ blur(): void;
44
46
  private handleInputChange;
45
47
  }
46
48
  declare global {
@@ -78,6 +78,12 @@ var USwitch = class USwitch extends UFormControlElement {
78
78
  this.checked = false;
79
79
  this.invalid = false;
80
80
  }
81
+ focus(options) {
82
+ this.inputEl?.focus(options);
83
+ }
84
+ blur() {
85
+ this.inputEl?.blur();
86
+ }
81
87
  };
82
88
  __decorate([property({
83
89
  type: Boolean,
@@ -53,6 +53,8 @@ export declare class UTextarea extends UFormControlElement<string> {
53
53
  render(): import('lit-html').TemplateResult<1>;
54
54
  protected setValidity(): void;
55
55
  reset(): void;
56
+ focus(options?: FocusOptions): void;
57
+ blur(): void;
56
58
  private resizeTextarea;
57
59
  private handleTextareaInput;
58
60
  private handleTextareaChange;
@@ -106,6 +106,12 @@ var UTextarea = class UTextarea extends UFormControlElement {
106
106
  this.value = void 0;
107
107
  this.invalid = false;
108
108
  }
109
+ focus(options) {
110
+ this.textareaEl?.focus(options);
111
+ }
112
+ blur() {
113
+ this.textareaEl?.blur();
114
+ }
109
115
  resizeTextarea() {
110
116
  const textarea = this.textareaEl;
111
117
  if (!textarea) return;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@iyulab/components",
3
3
  "description": "web-components library based on lit-element made by iyulab",
4
- "version": "1.31.0",
4
+ "version": "1.33.0",
5
5
  "keywords": [
6
6
  "iyulab",
7
7
  "components",
@@ -50,6 +50,8 @@ Checkbox with `indeterminate` state support. Form-associated (`formAssociated =
50
50
  |--------|---------|-------------|
51
51
  | `validate()` | `boolean` | Run validation; sets `invalid` |
52
52
  | `reset()` | `void` | Clear value and validation state |
53
+ | `focus(options?)` | `void` | Focus the checkbox input |
54
+ | `blur()` | `void` | Blur the checkbox input |
53
55
 
54
56
  ## CSS Parts
55
57
 
@@ -49,6 +49,8 @@ Single-date selection with a popover calendar. The value follows the same conven
49
49
  |--------|--------------|
50
50
  | `validate()` | Validate; sets `invalid` |
51
51
  | `reset()` | Reset value |
52
+ | `focus(options?)` | Focus the trigger |
53
+ | `blur()` | Blur the trigger |
52
54
 
53
55
  ## CSS Parts
54
56
 
@@ -40,3 +40,9 @@ Layout wrapper for form controls. Renders label, required marker, description te
40
40
  | `required` | `boolean` | `false` | ✓ | Show required marker |
41
41
  | `invalid` | `boolean` | `false` | ✓ | Show validation error state |
42
42
  | `disabled` | `boolean` | `false` | ✓ | Disable state forwarding |
43
+
44
+ ## Methods
45
+
46
+ | Method | Description |
47
+ |--------|-------------|
48
+ | `focus(options?)` | Focus the first focusable slotted child |
@@ -58,6 +58,8 @@ Radio group built from `u-option` children. Form-associated.
58
58
  |--------|-------------|
59
59
  | `validate()` | Validate; sets `invalid` |
60
60
  | `reset()` | Reset selection |
61
+ | `focus(options?)` | Focus the first option (regardless of selection) |
62
+ | `blur()` | Blur the currently focused option, if any |
61
63
 
62
64
  ## CSS Parts
63
65
 
@@ -56,6 +56,8 @@ Star/custom-symbol rating input. Supports fractional precision and custom symbol
56
56
  |--------|-------------|
57
57
  | `validate()` | Validate; sets `invalid` |
58
58
  | `reset()` | Reset value |
59
+ | `focus(options?)` | Focus the first symbol (regardless of value) |
60
+ | `blur()` | Blur the currently focused symbol, if any |
59
61
 
60
62
  ## CSS Parts
61
63
 
@@ -66,6 +66,8 @@ Dropdown select with single or multiple selection, search, and clear support. Fo
66
66
  |--------|-------------|
67
67
  | `validate()` | Validate; sets `invalid` |
68
68
  | `reset()` | Reset value |
69
+ | `focus(options?)` | Focus the trigger |
70
+ | `blur()` | Blur the trigger |
69
71
 
70
72
  ## CSS Parts
71
73
 
@@ -60,6 +60,8 @@ Range slider. Single-thumb or dual-thumb (range) mode. Supports marks, value dis
60
60
  |--------|-------------|
61
61
  | `validate()` | Validate; sets `invalid` |
62
62
  | `reset()` | Reset value |
63
+ | `focus(options?)` | Focus the first (min) thumb |
64
+ | `blur()` | Blur the first (min) thumb |
63
65
 
64
66
  ## CSS Parts
65
67
 
@@ -55,6 +55,8 @@ Toggle switch (on/off). Supports custom track and thumb content via slots. Form-
55
55
  |--------|-------------|
56
56
  | `validate()` | Validate; sets `invalid` |
57
57
  | `reset()` | Reset state |
58
+ | `focus(options?)` | Focus the switch input |
59
+ | `blur()` | Blur the switch input |
58
60
 
59
61
  ## CSS Parts
60
62
 
@@ -61,6 +61,8 @@ Multi-line text input with auto-resize and optional character counter. Form-asso
61
61
  |--------|-------------|
62
62
  | `validate()` | Validate; sets `invalid` |
63
63
  | `reset()` | Reset value |
64
+ | `focus(options?)` | Focus the textarea |
65
+ | `blur()` | Blur the textarea |
64
66
 
65
67
  ## CSS Parts
66
68