@iyulab/components 1.31.0 → 1.32.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,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.32.0] - 2026-08-21
4
+
5
+ ### Added
6
+
7
+ - **`u-select`, `u-date-picker`, `u-textarea`, `u-checkbox`, `u-switch`, and `u-slider` now
8
+ support `.focus()`/`.blur()` on the host element**, matching `u-input`'s existing contract.
9
+ Previously only `u-input` delegated `.focus()` to its actual interactive element; calling
10
+ `.focus()` on any of the others was a silent no-op, since the host element itself carries no
11
+ `tabindex` — only an internal element does (the container, the native `<input>`/`<textarea>`,
12
+ or, for `u-slider`, the first thumb). Any consumer building a generic "scroll to and focus
13
+ the first invalid field" flow now gets consistent behavior across every form control this
14
+ library ships, instead of having to special-case each one.
15
+ - **`u-field` now exposes `.focus()` publicly**, delegating to the first focusable slotted
16
+ child — the same lookup its label-click handler already used internally, now available to
17
+ consumers slotting an arbitrary form control (not one of this library's own) directly into
18
+ `<u-field>`.
19
+
3
20
  ## [1.31.0] - 2026-08-20
4
21
 
5
22
  ### 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,
@@ -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.32.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 |
@@ -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