@iyulab/components 1.39.0 → 1.40.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 (106) hide show
  1. package/CHANGELOG.md +160 -0
  2. package/README.md +24 -0
  3. package/dist/_virtual/{_@oxc-project_runtime@0.148.0 → _@oxc-project_runtime@0.149.0}/helpers/esm/decorate.js +1 -1
  4. package/dist/_virtual/{_@oxc-project_runtime@0.148.0 → _@oxc-project_runtime@0.149.0}/helpers/esm/decorateMetadata.js +1 -1
  5. package/dist/_virtual/_glob-assets_raw/dark.css.e8ac080d.js +1 -1
  6. package/dist/_virtual/_glob-assets_raw/light.css.637cb2f8.js +1 -1
  7. package/dist/assets/locales/ar.json.js +6 -2
  8. package/dist/assets/locales/de.json.js +6 -2
  9. package/dist/assets/locales/en.json.js +6 -2
  10. package/dist/assets/locales/es.json.js +6 -2
  11. package/dist/assets/locales/fr.json.js +6 -2
  12. package/dist/assets/locales/id.json.js +6 -2
  13. package/dist/assets/locales/ja.json.js +6 -2
  14. package/dist/assets/locales/ko.json.js +6 -2
  15. package/dist/assets/locales/pt-BR.json.js +6 -2
  16. package/dist/assets/locales/ru.json.js +6 -2
  17. package/dist/assets/locales/th.json.js +6 -2
  18. package/dist/assets/locales/vi.json.js +6 -2
  19. package/dist/assets/locales/zh-CN.json.js +6 -2
  20. package/dist/assets/locales/zh-TW.json.js +6 -2
  21. package/dist/components/UFloatingElement.js +2 -2
  22. package/dist/components/UFormControlElement.js +2 -2
  23. package/dist/components/UOverlayElement.js +2 -2
  24. package/dist/components/alert/UAlert.js +2 -2
  25. package/dist/components/avatar/UAvatar.js +2 -2
  26. package/dist/components/badge/UBadge.js +2 -2
  27. package/dist/components/breadcrumb/UBreadcrumb.js +1 -1
  28. package/dist/components/breadcrumb/UBreadcrumb.styles.js +1 -1
  29. package/dist/components/breadcrumb-item/UBreadcrumbItem.js +2 -2
  30. package/dist/components/breadcrumb-item/UBreadcrumbItem.styles.js +1 -1
  31. package/dist/components/button/UButton.js +2 -2
  32. package/dist/components/button-group/UButtonGroup.js +2 -2
  33. package/dist/components/card/UCard.js +2 -2
  34. package/dist/components/carousel/UCarousel.js +2 -2
  35. package/dist/components/checkbox/UCheckbox.js +2 -2
  36. package/dist/components/checkbox/UCheckbox.styles.js +7 -1
  37. package/dist/components/chip/UChip.js +2 -2
  38. package/dist/components/chip/UChip.styles.js +7 -0
  39. package/dist/components/copy-button/UCopyButton.js +2 -2
  40. package/dist/components/date-picker/UDatePicker.js +2 -2
  41. package/dist/components/date-picker/UDatePicker.styles.js +2 -2
  42. package/dist/components/dialog/UDialog.js +2 -2
  43. package/dist/components/divider/UDivider.js +2 -2
  44. package/dist/components/divider/UDivider.styles.js +1 -1
  45. package/dist/components/drawer/UDrawer.js +2 -2
  46. package/dist/components/expander/UExpander.js +2 -2
  47. package/dist/components/expander/UExpander.styles.js +1 -1
  48. package/dist/components/field/UField.js +2 -2
  49. package/dist/components/field/UField.styles.js +1 -1
  50. package/dist/components/file-input/UFileInput.js +2 -2
  51. package/dist/components/file-input/UFileInput.styles.js +2 -2
  52. package/dist/components/form/UForm.js +2 -2
  53. package/dist/components/icon/UIcon.js +3 -3
  54. package/dist/components/icon-button/UIconButton.js +2 -2
  55. package/dist/components/input/UInput.d.ts +15 -1
  56. package/dist/components/input/UInput.js +62 -12
  57. package/dist/components/input/UInput.styles.js +25 -6
  58. package/dist/components/menu/UMenu.js +2 -2
  59. package/dist/components/menu-item/UMenuItem.js +2 -2
  60. package/dist/components/option/UOption.js +2 -2
  61. package/dist/components/panel/UPanel.js +2 -2
  62. package/dist/components/popover/UPopover.js +23 -7
  63. package/dist/components/progress-bar/UProgressBar.js +2 -2
  64. package/dist/components/progress-ring/UProgressRing.js +2 -2
  65. package/dist/components/radio/URadio.js +2 -2
  66. package/dist/components/rating/URating.js +2 -2
  67. package/dist/components/rating/URating.styles.js +6 -0
  68. package/dist/components/select/USelect.d.ts +7 -1
  69. package/dist/components/select/USelect.js +19 -8
  70. package/dist/components/select/USelect.styles.js +18 -5
  71. package/dist/components/skeleton/USkeleton.js +2 -2
  72. package/dist/components/slider/USlider.d.ts +3 -1
  73. package/dist/components/slider/USlider.js +2 -2
  74. package/dist/components/slider/USlider.styles.js +19 -2
  75. package/dist/components/spinner/USpinner.js +2 -2
  76. package/dist/components/split-panel/USplitPanel.d.ts +37 -2
  77. package/dist/components/split-panel/USplitPanel.js +120 -11
  78. package/dist/components/split-panel/USplitPanel.styles.js +37 -6
  79. package/dist/components/switch/USwitch.js +2 -2
  80. package/dist/components/switch/USwitch.styles.js +8 -3
  81. package/dist/components/tab/UTab.js +2 -2
  82. package/dist/components/tab/UTab.styles.js +1 -1
  83. package/dist/components/tab-panel/UTabPanel.js +3 -3
  84. package/dist/components/tag/UTag.js +2 -2
  85. package/dist/components/text/UText.js +2 -2
  86. package/dist/components/text/UText.styles.js +1 -1
  87. package/dist/components/textarea/UTextarea.d.ts +5 -0
  88. package/dist/components/textarea/UTextarea.js +2 -2
  89. package/dist/components/textarea/UTextarea.styles.js +6 -3
  90. package/dist/components/tooltip/UTooltip.js +2 -2
  91. package/dist/components/tree/UTree.js +2 -2
  92. package/dist/components/tree-item/UTreeItem.js +2 -2
  93. package/dist/components/tree-item/UTreeItem.styles.js +50 -13
  94. package/dist/styles/dark.css +4 -1
  95. package/dist/styles/light.css +7 -4
  96. package/dist/utilities/Locale.d.ts +1 -1
  97. package/dist/utilities/Toast.d.ts +34 -0
  98. package/dist/utilities/Toast.js +57 -0
  99. package/package.json +4 -4
  100. package/skills/iyulab-components/references/components/dialog.md +19 -0
  101. package/skills/iyulab-components/references/components/input.md +17 -0
  102. package/skills/iyulab-components/references/components/panel.md +12 -0
  103. package/skills/iyulab-components/references/components/select.md +9 -0
  104. package/skills/iyulab-components/references/components/slider.md +1 -1
  105. package/skills/iyulab-components/references/components/split-panel.md +41 -3
  106. package/skills/iyulab-components/references/components/tab-panel.md +18 -0
@@ -1,5 +1,5 @@
1
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
2
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
1
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
2
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorate.js";
3
3
  import { UFormControlElement } from "../UFormControlElement.js";
4
4
  import "../spinner/USpinner.js";
5
5
  import "../icon/UIcon.js";
@@ -30,8 +30,8 @@ var UInput = class UInput extends UFormControlElement {
30
30
  const slot = e.target;
31
31
  this.options = slot.assignedElements({ flatten: true }).filter((el) => el instanceof UOption);
32
32
  this.setup(this.options);
33
- if (this.options.length > 0) this.popoverEl?.show(this.containerEl);
34
- else this.popoverEl?.hide();
33
+ if (this.options.length === 0) this.popoverEl?.hide();
34
+ else if (this.matches(":focus-within")) this.popoverEl?.show(this.containerEl);
35
35
  };
36
36
  this.composing = false;
37
37
  this.handleCompositionStart = () => {
@@ -60,6 +60,10 @@ var UInput = class UInput extends UFormControlElement {
60
60
  }));
61
61
  };
62
62
  this.handleInputKeydown = (e) => {
63
+ if (e.key === "Enter") {
64
+ this.handleImplicitSubmission(e);
65
+ return;
66
+ }
63
67
  if (this.options.length === 0) return;
64
68
  switch (e.key) {
65
69
  case "ArrowDown":
@@ -148,9 +152,6 @@ var UInput = class UInput extends UFormControlElement {
148
152
  this.handlePopoverShow = (e) => {
149
153
  if (this.options.length === 0) e.preventDefault();
150
154
  };
151
- this.handlePopoverHide = (e) => {
152
- if (this.options.length === 0) e.preventDefault();
153
- };
154
155
  }
155
156
  static {
156
157
  this.styles = [super.styles, styles];
@@ -160,6 +161,10 @@ var UInput = class UInput extends UFormControlElement {
160
161
  super.disconnectedCallback();
161
162
  }
162
163
  render() {
164
+ const editable = !this.disabled && !this.readonly;
165
+ const showToggle = this.type === "password" && editable;
166
+ const showClear = this.clearable && editable && !!this.value;
167
+ const showStepper = this.type === "number" && editable;
163
168
  return html`
164
169
  <u-field part="field"
165
170
  ?required=${this.required}
@@ -209,7 +214,7 @@ var UInput = class UInput extends UFormControlElement {
209
214
  <slot name="suffix"></slot>
210
215
 
211
216
  <u-icon class="suffix-item"
212
- ?hidden=${this.type !== "password" || this.disabled || this.readonly}
217
+ ?hidden=${!showToggle}
213
218
  role="button"
214
219
  tabindex="0"
215
220
  aria-label=${Locale.getValue(this.showPassword ? "hidePassword" : "showPassword")}
@@ -219,7 +224,7 @@ var UInput = class UInput extends UFormControlElement {
219
224
  @keydown=${this.handleSuffixIconKeydown(this.handlePasswordTogglerClick)}
220
225
  ></u-icon>
221
226
  <u-icon class="suffix-item"
222
- ?hidden=${!this.clearable || this.disabled || this.readonly || !this.value}
227
+ ?hidden=${!showClear}
223
228
  role="button"
224
229
  tabindex="0"
225
230
  aria-label=${Locale.getValue("clear")}
@@ -229,7 +234,7 @@ var UInput = class UInput extends UFormControlElement {
229
234
  @keydown=${this.handleSuffixIconKeydown(this.handleClearButtonClick)}
230
235
  ></u-icon>
231
236
  <u-icon class="suffix-item stepper-btn"
232
- ?hidden=${this.type !== "number" || this.disabled || this.readonly}
237
+ ?hidden=${!showStepper}
233
238
  role="button"
234
239
  tabindex="0"
235
240
  aria-disabled=${!this.canDecrement}
@@ -240,7 +245,7 @@ var UInput = class UInput extends UFormControlElement {
240
245
  @keydown=${this.handleSuffixIconKeydown(this.handleStepperClick(-1))}
241
246
  ></u-icon>
242
247
  <u-icon class="suffix-item stepper-btn"
243
- ?hidden=${this.type !== "number" || this.disabled || this.readonly}
248
+ ?hidden=${!showStepper}
244
249
  role="button"
245
250
  tabindex="0"
246
251
  aria-disabled=${!this.canIncrement}
@@ -262,7 +267,6 @@ var UInput = class UInput extends UFormControlElement {
262
267
  placement="bottom-start"
263
268
  offset="1"
264
269
  @show=${this.handlePopoverShow}
265
- @hide=${this.handlePopoverHide}
266
270
  >
267
271
  <slot @slotchange=${this.handleSlotChange}></slot>
268
272
  </u-popover>
@@ -335,6 +339,29 @@ var UInput = class UInput extends UFormControlElement {
335
339
  option.removeEventListener("keydown", this.handleOptionKeydown);
336
340
  }
337
341
  }
342
+ /**
343
+ * 네이티브 단일 행 `<input>` 의 **암묵 제출**(HTML «implicit submission»)을 흉내 낸다 — 내부 `<input>` 은 섀도
344
+ * 안에 있어 바깥 `<form>` 의 폼 소유자가 아니므로, 흉내 내지 않으면 Enter 가 아무것도 하지 않는다.
345
+ *
346
+ * - **한 틱(태스크) 뒤에 판정한다.** 이 리스너는 내부 입력에서 가장 먼저 돌므로, 소비자가 호스트나 폼에서
347
+ * `keydown` 을 취소했는지는 디스패치가 끝난 뒤에야 안다(마이크로태스크는 다음 리스너보다 먼저 돈다).
348
+ * 취소됐으면 제출하지 않는다 — 네이티브와 같다.
349
+ * - IME 조합을 확정하는 Enter 와 수정 키가 붙은 Enter 는 제출하지 않는다.
350
+ * - 명세의 순서 그대로: 폼에 제출 버튼이 있으면 **첫 제출 버튼을 누른다**(비활성이면 버튼이 스스로 거른다 ·
351
+ * 네이티브 버튼이면 `submitter` 도 그것이 된다). 없으면 암묵 제출을 막는 필드가 둘 이상일 때 제출하지 않고,
352
+ * 아니면 `requestSubmit()` 한다(검증도 그 안에서 돈다).
353
+ */
354
+ handleImplicitSubmission(e) {
355
+ if (e.isComposing || this.composing || e.shiftKey || e.ctrlKey || e.altKey || e.metaKey) return;
356
+ setTimeout(() => {
357
+ const form = this.form;
358
+ if (e.defaultPrevented || !form || !this.isConnected) return;
359
+ const elements = Array.from(form.elements);
360
+ const submitter = elements.find(isSubmitButton);
361
+ if (submitter) submitter.click();
362
+ else if (elements.filter(blocksImplicitSubmission).length <= 1) form.requestSubmit();
363
+ });
364
+ }
338
365
  /** 경계(min/max)에 걸리면 시각적으로 흐리고 마우스 클릭을 막는다(CSS `pointer-events:
339
366
  * none`) — 네이티브 stepUp/stepDown 자체가 경계에서 조용히 no-op이라 값이 튀지는
340
367
  * 않지만, 헛클릭 피드백을 준다. 키보드 Enter/Space는 `pointer-events`의 영향을 받지
@@ -381,5 +408,28 @@ __decorate([query("input", true), __decorateMetadata("design:type", typeof HTMLI
381
408
  __decorate([query("u-popover", true), __decorateMetadata("design:type", typeof UPopover === "undefined" ? Object : UPopover)], UInput.prototype, "popoverEl", void 0);
382
409
  __decorate([state(), __decorateMetadata("design:type", Boolean)], UInput.prototype, "showPassword", void 0);
383
410
  UInput = __decorate([customElement("u-input")], UInput);
411
+ /** HTML 명세의 «암묵 제출을 막는 필드» — 네이티브 단일 행 입력의 이 type 들. `u-input` 의 type 은 전부 여기에 든다. */
412
+ var BLOCKING_TYPES = /* @__PURE__ */ new Set([
413
+ "text",
414
+ "search",
415
+ "email",
416
+ "url",
417
+ "tel",
418
+ "password",
419
+ "date",
420
+ "month",
421
+ "week",
422
+ "time",
423
+ "datetime-local",
424
+ "number"
425
+ ]);
426
+ /** 제출 버튼 — 네이티브 `<button>`(기본 type 이 submit)·`<input type=submit|image>`, 그리고 `type` 이 submit 인 폼 연동 커스텀 버튼. */
427
+ function isSubmitButton(el) {
428
+ const type = el.type;
429
+ return type === "submit" || el instanceof HTMLInputElement && type === "image";
430
+ }
431
+ function blocksImplicitSubmission(el) {
432
+ return (el instanceof HTMLInputElement || el instanceof UInput) && BLOCKING_TYPES.has(el.type);
433
+ }
384
434
  //#endregion
385
435
  export { UInput };
@@ -114,13 +114,16 @@ var styles = css`
114
114
  /* 네이티브 input */
115
115
  input {
116
116
  all: unset;
117
- flex: 1 0 auto;
117
+ /* 줄어들 수 있어야 한다 — 수축 0 이면 min-width: 0 이 무력해 입력이 자기 고유 폭(size 속성 기본 20자)
118
+ 아래로 줄지 않고, 좁은 필드에서는 뒤따르는 접미 아이콘(지우기·토글·스테퍼)이 컨테이너 밖으로 밀려나
119
+ overflow 에 잘린다 — 보이지도 눌리지도 않는다. */
120
+ flex: 1 1 auto;
118
121
  min-width: 0;
119
122
  font-size: 1em;
120
123
  line-height: 1.5;
121
124
  }
122
125
  input::placeholder {
123
- color: var(--u-txt-color-weak, #757575);
126
+ color: var(--u-txt-color-weak, #616161);
124
127
  }
125
128
  input:disabled {
126
129
  cursor: not-allowed;
@@ -152,7 +155,8 @@ var styles = css`
152
155
  margin-left: 0.25em;
153
156
  }
154
157
 
155
- /* 아이콘 영역 (clear, password toggle 등) */
158
+ /* 아이콘 영역 (clear, password toggle 등). ⚠포인터를 받는 아이콘(role=button)의 간격은 이 값이 아니라 아래
159
+ [role="button"] 규칙이 정한다 — 그 규칙의 margin 단축 선언이 이 margin-left 를 덮는다. */
156
160
  .suffix-item {
157
161
  margin-left: 0.25em;
158
162
  color: var(--u-icon-color, #616161);
@@ -166,14 +170,29 @@ var styles = css`
166
170
  .suffix-item:active {
167
171
  color: var(--u-icon-color-active, #1565C0);
168
172
  }
169
- .stepper-btn {
170
- font-size: 0.85em;
171
- }
172
173
  .stepper-btn[aria-disabled="true"] {
173
174
  opacity: 0.35;
174
175
  pointer-events: none;
175
176
  }
176
177
 
178
+ /* 포인터를 받는 접미 아이콘(지우기 · 비밀번호 토글 · 스테퍼) — 글리프는 1em 그대로, 받는 영역은 **모두** 24px(1.5em)다
179
+ (WCAG 2.2 SC 2.5.8 · 사람 결정 HD-57 ⒜). 기전: 좌우 0.25em 패딩으로 1em 글리프를 1.5em(24px) 상자로 감싸고,
180
+ 왼쪽 여백 0.25em · 오른쪽 여백 -0.25em 으로 이웃과 **맞닿되 겹치지 않게** 놓는다 — 그 결과 글리프 사이가 0.5em(8px)이 된다.
181
+ 세로는 음수 여백으로 컨테이너 높이를 그대로 둔다.
182
+ ⚠종전에는 간격이 0.25em 이라 «맨 뒤 아이콘만» 24 였고 나란히 놓인 쌍·스테퍼는 24 에 못 미쳤다 — 그것을 고치는 대가가
183
+ 아이콘 사이가 4px 벌어지는 것이다(스테퍼 글리프도 0.85em → 1em).
184
+ content-box 는 아이콘 자신의 box-sizing 과 무관하게 글리프를 1em 로 둔다. */
185
+ .suffix-item[role="button"] {
186
+ box-sizing: content-box;
187
+ margin: -0.25em -0.25em -0.25em 0.25em;
188
+ padding: 0.25em;
189
+ }
190
+ /* 좌우 여백이 0 인 변형(underlined · borderless)은 맨 뒤 아이콘이 오른쪽으로 넓힐 자리가 없다 — 컨테이너에 0.25em 을 준다(그만큼만). */
191
+ :host([variant="underlined"]) .container,
192
+ :host([variant="borderless"]) .container {
193
+ padding-right: 0.25em;
194
+ }
195
+
177
196
  u-popover {
178
197
  width: var(--input-popover-width, var(--anchor-width, 100%));
179
198
  min-height: var(--input-popover-min-height);
@@ -1,6 +1,6 @@
1
1
  import { UElement } from "../UElement.js";
2
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
3
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
2
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
3
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorate.js";
4
4
  import { UMenuItem } from "../menu-item/UMenuItem.js";
5
5
  import { styles } from "./UMenu.styles.js";
6
6
  import { html } from "lit";
@@ -1,6 +1,6 @@
1
1
  import { UElement } from "../UElement.js";
2
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
3
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
2
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
3
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorate.js";
4
4
  import "../icon/UIcon.js";
5
5
  import "../popover/UPopover.js";
6
6
  import { UDivider } from "../divider/UDivider.js";
@@ -1,6 +1,6 @@
1
1
  import { UElement } from "../UElement.js";
2
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
3
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
2
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
3
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorate.js";
4
4
  import "../icon/UIcon.js";
5
5
  import { styles } from "./UOption.styles.js";
6
6
  import { html, nothing } from "lit";
@@ -1,6 +1,6 @@
1
1
  import { UElement } from "../UElement.js";
2
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
3
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
2
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
3
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorate.js";
4
4
  import { styles } from "./UPanel.styles.js";
5
5
  import { html } from "lit";
6
6
  import { customElement, property } from "lit/decorators.js";
@@ -1,6 +1,6 @@
1
1
  import { isCoarsePointer } from "../../utilities/elements.js";
2
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
3
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
2
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
3
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorate.js";
4
4
  import { UFloatingElement } from "../UFloatingElement.js";
5
5
  import { arrayAttrConverter } from "../../utilities/converters.js";
6
6
  import { styles } from "./UPopover.styles.js";
@@ -8,6 +8,24 @@ import { html } from "lit";
8
8
  import { customElement, property } from "lit/decorators.js";
9
9
  //#region src/components/popover/UPopover.ts
10
10
  var _UPopover;
11
+ var FOCUSABLE = "a, button, input, select, textarea, [tabindex]:not([tabindex=\"-1\"])";
12
+ /**
13
+ * 슬롯에 꽂힌 요소와 **그 자손**(안쪽 `<slot>` 에 다시 꽂힌 요소까지)에서 포커스 가능한 것을 문서 순서로 모은다 —
14
+ * `autofocus`·`focusTo()` 가 약속하는 «내부의» 포커스 가능한 요소. 종전에는 꽂힌 요소 자신만 봐서, 감싼 요소
15
+ * (`<div>`) 안의 입력은 찾지 못하고 팝오버 자신에 포커스했다. `disabled`·`hidden` 인 요소는 하위 전체를 건너뛴다.
16
+ */
17
+ function collectFocusables(elements, out) {
18
+ for (const el of elements) {
19
+ if (!(el instanceof HTMLElement)) continue;
20
+ if (el.hasAttribute("disabled") || el.hasAttribute("hidden")) continue;
21
+ if (el instanceof HTMLSlotElement) {
22
+ collectFocusables(el.assignedElements({ flatten: true }), out);
23
+ continue;
24
+ }
25
+ if (el.matches(FOCUSABLE)) out.push(el);
26
+ collectFocusables(Array.from(el.children), out);
27
+ }
28
+ }
11
29
  var UPopover = class UPopover extends UFloatingElement {
12
30
  static {
13
31
  _UPopover = this;
@@ -141,11 +159,9 @@ var UPopover = class UPopover extends UFloatingElement {
141
159
  return html`<slot></slot>`;
142
160
  }
143
161
  focusTo(index) {
144
- const focusables = this.renderRoot.querySelector("slot")?.assignedElements({ flatten: true }).filter((el) => {
145
- if (!(el instanceof HTMLElement)) return false;
146
- if (el.hasAttribute("disabled") || el.hasAttribute("hidden")) return false;
147
- return el.matches("a, button, input, select, textarea, [tabindex]:not([tabindex=\"-1\"])");
148
- });
162
+ const slot = this.renderRoot.querySelector("slot");
163
+ const focusables = [];
164
+ if (slot) collectFocusables(slot.assignedElements({ flatten: true }), focusables);
149
165
  if (!focusables || focusables.length === 0) {
150
166
  this.focus();
151
167
  return;
@@ -1,6 +1,6 @@
1
1
  import { UElement } from "../UElement.js";
2
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
3
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
2
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
3
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorate.js";
4
4
  import { styles } from "./UProgressBar.styles.js";
5
5
  import { html } from "lit";
6
6
  import { customElement, property, state } from "lit/decorators.js";
@@ -1,6 +1,6 @@
1
1
  import { UElement } from "../UElement.js";
2
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
3
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
2
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
3
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorate.js";
4
4
  import { styles } from "./UProgressRing.styles.js";
5
5
  import { html } from "lit";
6
6
  import { customElement, property, state } from "lit/decorators.js";
@@ -1,5 +1,5 @@
1
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
2
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
1
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
2
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorate.js";
3
3
  import { UFormControlElement } from "../UFormControlElement.js";
4
4
  import { Locale } from "../../utilities/Locale.js";
5
5
  import "../field/UField.js";
@@ -1,5 +1,5 @@
1
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
2
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
1
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
2
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorate.js";
3
3
  import { UFormControlElement } from "../UFormControlElement.js";
4
4
  import "../icon/UIcon.js";
5
5
  import { Locale } from "../../utilities/Locale.js";
@@ -26,6 +26,12 @@ var styles = css`
26
26
  align-items: center;
27
27
  justify-content: center;
28
28
  font-size: 1.2em;
29
+ /* WCAG 2.2 SC 2.5.8 — 심볼은 «각각이» 포인터 타깃이고 서로 인접해 있어
30
+ 간격 예외를 받지 못한다(중심 간 24px 미만). ⚠글리프 크기(font-size)는
31
+ 그대로 두고 **패딩으로 타깃만** 24×24 이상으로 만든다 — 별이 커지면
32
+ 평점 위젯의 시각적 무게가 달라지고, 그것은 이 처방이 피하려는 것이다.
33
+ 패딩이 늘면 중심 간 거리도 함께 벌어져 겹침도 생기지 않는다. */
34
+ padding: 0.16em;
29
35
  outline: none;
30
36
  border-radius: 0.15em;
31
37
  color: var(--rating-symbol-off-color);
@@ -21,6 +21,10 @@ export type SelectVariant = 'outlined' | 'filled' | 'underlined' | 'borderless';
21
21
  * @csspart popover - 옵션 목록이 표시되는 팝오버 요소
22
22
  * @csspart search-input - 검색 입력 영역
23
23
  *
24
+ * @cssprop --u-select-display - 호스트의 display (기본값: inline-block). 폼/그리드 셀에서
25
+ * 컨테이너 폭을 채우려면 `block`으로 지정한다.
26
+ * @cssprop --u-select-width - 호스트의 width (기본값: auto). flex 컨테이너처럼 block만으로는
27
+ * 늘어나지 않는 맥락에서 `100%`로 지정한다.
24
28
  * @cssprop --select-popover-width - 팝오버의 너비 (기본값: 앵커(트리거) 너비, strategy와 무관하게 동일).
25
29
  * 옵션 텍스트가 길어도 이 값을 넘겨 넓어지지 않으며, 넘치는 텍스트는 UOption에서 ellipsis 처리된다.
26
30
  * @cssprop --select-popover-min-height - 팝오버의 최소 높이 (기본값: 0px)
@@ -57,8 +61,10 @@ export declare class USelect extends UFormControlElement<string | string[]> {
57
61
  private options;
58
62
  /** aria-expanded 배선용 팝오버 열림 상태 */
59
63
  private open;
60
- /** combobox → listbox 연결용 고유 id (aria-controls) */
64
+ /** combobox → 팝업 연결용 고유 id (aria-controls). 검색 가능이면 팝업은 dialog 이고 그 안에 listbox 가 있다. */
65
+ private readonly idBase;
61
66
  private readonly listboxId;
67
+ private readonly popupId;
62
68
  protected shouldValidate(changed: PropertyValues): boolean;
63
69
  private get valueAsString();
64
70
  private get valueAsArray();
@@ -1,5 +1,5 @@
1
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
2
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
1
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
2
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorate.js";
3
3
  import { UFormControlElement } from "../UFormControlElement.js";
4
4
  import "../spinner/USpinner.js";
5
5
  import "../icon/UIcon.js";
@@ -22,7 +22,9 @@ var USelect = class USelect extends UFormControlElement {
22
22
  this.loading = false;
23
23
  this.options = [];
24
24
  this.open = false;
25
- this.listboxId = `u-select-listbox-${Math.random().toString(36).slice(2, 8)}`;
25
+ this.idBase = `u-select-${Math.random().toString(36).slice(2, 8)}`;
26
+ this.listboxId = `${this.idBase}-listbox`;
27
+ this.popupId = `${this.idBase}-popup`;
26
28
  this.handleSlotChange = (e) => {
27
29
  this.cleanup(this.options);
28
30
  const slot = e.target;
@@ -173,9 +175,9 @@ var USelect = class USelect extends UFormControlElement {
173
175
  role="combobox"
174
176
  aria-label=${ifDefined(this.label)}
175
177
  aria-description=${ifDefined(this.description)}
176
- aria-haspopup="listbox"
178
+ aria-haspopup=${this.searchable ? "dialog" : "listbox"}
177
179
  aria-expanded=${this.open}
178
- aria-controls=${this.listboxId}>
180
+ aria-controls=${this.searchable ? this.popupId : this.listboxId}>
179
181
  <slot name="prefix"></slot>
180
182
  <slot name="display">${this.renderContent()}</slot>
181
183
  <slot name="suffix"></slot>
@@ -201,9 +203,13 @@ var USelect = class USelect extends UFormControlElement {
201
203
  </div>
202
204
  </u-field>
203
205
 
206
+ <!-- listbox 는 option(과 group)만 담는다 — 검색 입력은 그 밖에 둔다. 검색 가능이면 입력과 목록을 함께 담는
207
+ 팝업이 dialog 가 된다(WAI-ARIA APG «combobox with dialog popup» — 포커스가 팝업 안으로 들어가는 지금의
208
+ 키보드 모델 그대로다). 검색 불가면 팝업은 역할 없는 컨테이너이고 목록만 담는다. -->
204
209
  <u-popover part="popover"
205
- id=${this.listboxId}
206
- role="listbox"
210
+ id=${this.popupId}
211
+ role=${ifDefined(this.searchable ? "dialog" : void 0)}
212
+ aria-label=${ifDefined(this.searchable ? this.label || Locale.getValue("search") : void 0)}
207
213
  scrollable
208
214
  autofocus
209
215
  for=".container"
@@ -219,11 +225,16 @@ var USelect = class USelect extends UFormControlElement {
219
225
  <u-icon lib="internal" name="search"></u-icon>
220
226
  <input
221
227
  type="text"
228
+ aria-label=${Locale.getValue("search")}
229
+ aria-controls=${this.listboxId}
222
230
  @input=${this.handleSearchInput}
223
231
  @keydown=${this.handleSearchKeydown}
224
232
  />
225
233
  </div>
226
- <slot @slotchange=${this.handleSlotChange}></slot>
234
+ <div class="listbox" role="listbox" id=${this.listboxId}
235
+ aria-multiselectable=${ifDefined(this.multiple ? "true" : void 0)}>
236
+ <slot @slotchange=${this.handleSlotChange}></slot>
237
+ </div>
227
238
  </u-popover>
228
239
  `;
229
240
  }
@@ -10,7 +10,10 @@ var styles = css`
10
10
 
11
11
  :host {
12
12
  position: relative;
13
- display: inline-block;
13
+ /* 폼/그리드 셀에서 컨테이너 폭을 채우려면 소비자가 --u-select-display: block 을 준다.
14
+ flex 컨테이너처럼 block 만으로 늘어나지 않는 맥락을 위해 width 경로도 함께 연다. */
15
+ display: var(--u-select-display, inline-block);
16
+ width: var(--u-select-width, auto);
14
17
  color: var(--u-txt-color, #212121);
15
18
  font-size: inherit;
16
19
  font-family: var(--u-font-base);
@@ -118,7 +121,7 @@ var styles = css`
118
121
  }
119
122
 
120
123
  .count {
121
- color: var(--u-txt-color-weak, #757575);
124
+ color: var(--u-txt-color-weak, #616161);
122
125
  line-height: 1.25;
123
126
  /* n / m 은 제자리에서 n 이 바뀐다 — 비례폭이면 선택할 때마다 라벨 줄이 흔들린다 */
124
127
  font-variant-numeric: tabular-nums;
@@ -133,7 +136,7 @@ var styles = css`
133
136
  text-overflow: ellipsis;
134
137
  }
135
138
  .text-content.placeholder {
136
- color: var(--u-txt-color-weak, #757575);
139
+ color: var(--u-txt-color-weak, #616161);
137
140
  }
138
141
 
139
142
  .chips-content {
@@ -165,6 +168,16 @@ var styles = css`
165
168
  color: var(--u-icon-color-active, #1565C0);
166
169
  }
167
170
 
171
+ /* 지우기 «x» — 글리프는 1em 그대로, 받는 영역만 24px(1.5em)로 넓힌다(WCAG 2.2 SC 2.5.8). 왼쪽은 자기 앞
172
+ 간격을 패딩으로, 오른쪽은 뒤따르는 펼침 화살표(트리거의 일부인 장식 — 별도 타깃이 아니다)의 간격으로,
173
+ 위아래는 컨테이너 여백(0.3em) 쪽으로 넓힌다. 음수 여백이라 배치·트리거 높이는 그대로다 — 트리거의 줄은
174
+ 1em 이라 세로 패딩만 주면 트리거가 8px 커진다. */
175
+ .suffix-item[role="button"] {
176
+ box-sizing: content-box;
177
+ margin: -0.25em -0.25em -0.25em 0;
178
+ padding: 0.25em;
179
+ }
180
+
168
181
  /* 드롭다운 패널 — 옵션 텍스트가 길어도 팝오버가 앵커보다 넓어지지 않도록 고정 너비로
169
182
  맞춘다(긴 텍스트는 UOption 자체의 ellipsis로 처리). */
170
183
  u-popover {
@@ -185,7 +198,7 @@ var styles = css`
185
198
  align-items: center;
186
199
  gap: 0.4em;
187
200
  padding: 0.3em 0.6em;
188
- color: var(--u-txt-color-weak, #757575);
201
+ color: var(--u-txt-color-weak, #616161);
189
202
  border-bottom: 1px solid var(--u-border-color, #E0E0E0);
190
203
  }
191
204
  .search-input input {
@@ -195,7 +208,7 @@ var styles = css`
195
208
  line-height: 1.5;
196
209
  }
197
210
  .search-input input::placeholder {
198
- color: var(--u-txt-color-weak, #757575);
211
+ color: var(--u-txt-color-weak, #616161);
199
212
  }
200
213
  `;
201
214
  //#endregion
@@ -1,6 +1,6 @@
1
1
  import { UElement } from "../UElement.js";
2
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
3
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
2
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
3
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorate.js";
4
4
  import { styles } from "./USkeleton.styles.js";
5
5
  import { html } from "lit";
6
6
  import { customElement, property } from "lit/decorators.js";
@@ -25,7 +25,9 @@ export type SliderFormatter = (value: number) => string;
25
25
  * @cssprop --slider-fill-color - 활성화 영역 색상
26
26
  * @cssprop --slider-track-height - 트랙 높이
27
27
  * @cssprop --slider-track-color - 트랙 배경 색상
28
- * @cssprop --slider-thumb-size - thumb 크기
28
+ * @cssprop --slider-thumb-size - thumb 크기 (**보이는 원의 지름**. 잡히는 영역은 이것과
29
+ * 별개로 24×24 하한을 가진다 — WCAG 2.2 SC 2.5.8. 이 값을 24px 아래로 낮춰도 포인터 타깃은
30
+ * 줄지 않는다.)
29
31
  * @cssprop --slider-thumb-color - thumb 색상
30
32
  * @cssprop --slider-thumb-border-color - thumb 테두리 색상
31
33
  * @cssprop --slider-mark-size - 마크 크기
@@ -1,5 +1,5 @@
1
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
2
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
1
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
2
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorate.js";
3
3
  import { UFormControlElement } from "../UFormControlElement.js";
4
4
  import { Locale } from "../../utilities/Locale.js";
5
5
  import "../tooltip/UTooltip.js";
@@ -31,7 +31,11 @@ var styles = css`
31
31
 
32
32
  .container {
33
33
  position: relative;
34
- height: var(--slider-thumb-size);
34
+ /* ⚠실제 포인터 타깃은 thumb 이 아니라 이 컨테이너다 — pointerdown 이 여기 걸려 있고
35
+ 트랙 어디를 눌러도 값이 바뀐다. WCAG 2.2 SC 2.5.8 의 24px 하한은 그래서 여기에도 든다.
36
+ 보이는 트랙(6px)·thumb 치수는 그대로이고, 늘어나는 것은 «잡히는 높이»뿐이다.
37
+ ⚠주석에 백틱을 쓰지 말 것 — 태그드 템플릿이 그 자리에서 끝난다. */
38
+ height: max(var(--slider-thumb-size), 24px);
35
39
  touch-action: none;
36
40
  user-select: none;
37
41
  -webkit-user-select: none;
@@ -64,6 +68,19 @@ var styles = css`
64
68
  top: 50%;
65
69
  transform: translate(-50%, -50%);
66
70
  outline: none;
71
+ /* 🔴히트·포커스 영역만 24px 하한으로 넓힌다 — 보이는 원은 .thumb-content 가 그대로 그린다
72
+ (WCAG 2.2 SC 2.5.8). 박스가 커져도 중심은 그대로다: 콘텐츠를 가운데 놓으므로
73
+ left:% + translate(-50%) 가 가리키는 점이 곧 보이는 원의 중심이다.
74
+ ⚠**여백(padding)으로 넓히지 않는다** — 24px 은 여백 스케일의 2xl 과 같은 값이라
75
+ 「축 B 는 스케일 리터럴을 여백에 쓰지 않는다」 규칙(space-scale)에 걸리고, 그렇다고
76
+ --u-space-2xl 로 배선하면 ***접근성 하한이 여백 스케일을 손볼 때 따라 움직인다.***
77
+ 하한은 여백이 아니므로 여백이 아닌 자리에 적는다.
78
+ ⚠툴팁은 이 박스의 레이아웃에 참여하지 않는다(플로팅이라 흐름 밖) — 그래서 grid 로
79
+ 바꿔도 콘텐츠와 겹치지 않는다. */
80
+ display: grid;
81
+ place-items: center;
82
+ min-width: 24px;
83
+ min-height: 24px;
67
84
  }
68
85
 
69
86
  .thumb-content {
@@ -125,7 +142,7 @@ var styles = css`
125
142
  position: absolute;
126
143
  transform: translateX(-50%);
127
144
  font-size: 0.75em;
128
- color: var(--u-txt-color-weak, #757575);
145
+ color: var(--u-txt-color-weak, #616161);
129
146
  white-space: nowrap;
130
147
  }
131
148
  `;
@@ -1,6 +1,6 @@
1
1
  import { UElement } from "../UElement.js";
2
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
3
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
2
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
3
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorate.js";
4
4
  import { styles } from "./USpinner.styles.js";
5
5
  import { html } from "lit";
6
6
  import { customElement, property, state } from "lit/decorators.js";